Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to configure file permissions of written files (closes #183) #184

Merged
merged 26 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
465e7c2
Add ability to configure file permissions of written files (closes #183)
keeganwitt Sep 11, 2024
adc599c
Switch new conf options from string to int
keeganwitt Sep 13, 2024
9660059
Update pkg/disk/json_test.go
keeganwitt Sep 14, 2024
3ab3464
Update cmd/spiffe-helper/config/config.go
keeganwitt Sep 14, 2024
bb7937c
Update pkg/disk/x509.go
keeganwitt Sep 14, 2024
d647592
Update README.md
keeganwitt Sep 14, 2024
2d32c8c
Fix casing
keeganwitt Sep 14, 2024
4b678a4
Fix spacing
keeganwitt Sep 14, 2024
7a99776
Fix spelling
keeganwitt Sep 14, 2024
e219ba3
Match casing
keeganwitt Sep 14, 2024
d99586c
Match casing
keeganwitt Sep 14, 2024
dfaec9c
Change file mode configs to not be a pointer
keeganwitt Sep 17, 2024
790e7b1
Update pkg/sidecar/config.go
keeganwitt Sep 17, 2024
04fdc67
Update pkg/sidecar/sidecar.go
keeganwitt Sep 17, 2024
9789401
Update pkg/sidecar/sidecar.go
keeganwitt Sep 17, 2024
8535e1b
Update pkg/sidecar/workloadapi.go
keeganwitt Sep 17, 2024
98db7fe
Update cmd/spiffe-helper/config/config.go
keeganwitt Sep 17, 2024
575844b
Update cmd/spiffe-helper/config/config.go
keeganwitt Sep 17, 2024
8dafb32
Update pkg/sidecar/workloadapi.go
keeganwitt Sep 17, 2024
6b7bca5
Fix casing
keeganwitt Sep 17, 2024
771d2ad
Fix casing
keeganwitt Sep 17, 2024
3a4bee4
go fmt
keeganwitt Sep 17, 2024
f8fecf6
Move defaulting logic to ValidateConfig
keeganwitt Sep 18, 2024
35756fc
Ignore linting errors
keeganwitt Sep 18, 2024
544f6e5
Merge branch 'main' into configurable-file-permissions
faisal-memon Sep 18, 2024
c7b4625
Update README.md
keeganwitt Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ The configuration file is an [HCL](https://github.com/hashicorp/hcl) formatted f
| `jwt_svids` | An array with the audience and file name to store the JWT SVIDs. File is Base64-encoded string). | `[{jwt_audience="your-audience", jwt_svid_file_name="jwt_svid.token"}]` |
| `jwt_bundle_file_name` | File name to be used to store JWT Bundle in JSON format. | `"jwt_bundle.json"` |
| `include_federated_domains` | Include trust domains from federated servers in the CA bundle. | `true` |

| `cert_file_mode` | The octal file mode to use when saving the X.509 public certificate file. | `0644` |
| `key_file_mode` | The octal file mode to use when saving the X.509 private key file. | `0600` |
| `jwt_bundle_file_mode` | The octal file mode to use when saving a JWT Bundle file. | `0600` |
| `jwt_svid_file_mode` | The octal file mode to use when saving a JWT SVID file. | `0600` |

### Configuration example
```
Expand All @@ -47,6 +50,10 @@ svid_key_file_name = "svid_key.pem"
svid_bundle_file_name = "svid_bundle.pem"
jwt_svids = [{jwt_audience="your-audience", jwt_svid_file_name="jwt_svid.token"}]
jwt_bundle_file_name = "bundle.json"
cert_file_mode = "0444"
key_file_mode = "0444"
jwt_bundle_file_mode = "0444"
jwt_svid_file_mode = "0444"
```

### Windows example
Expand Down
36 changes: 35 additions & 1 deletion cmd/spiffe-helper/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"errors"
"flag"
"io/fs"
"os"

"github.com/hashicorp/hcl"
Expand All @@ -11,7 +12,11 @@ import (
)

const (
defaultAgentAddress = "/tmp/spire-agent/public/api.sock"
defaultAgentAddress = "/tmp/spire-agent/public/api.sock"
defaultCertFileMode = 0644
defaultKeyFileMode = 0600
defaultJWTBundleFileMode = 0600
defaultJWTSVIDFileMode = 0600
)

type Config struct {
Expand All @@ -24,6 +29,10 @@ type Config struct {
CmdArgsDeprecated string `hcl:"cmdArgs"`
CertDir string `hcl:"cert_dir"`
CertDirDeprecated string `hcl:"certDir"`
CertFileMode int `hcl:"cert_file_mode"`
KeyFileMode int `hcl:"key_file_mode"`
JWTBundleFileMode int `hcl:"jwt_bundle_file_mode"`
JWTSVIDFileMode int `hcl:"jwt_svid_file_mode"`
IncludeFederatedDomains bool `hcl:"include_federated_domains"`
RenewSignal string `hcl:"renew_signal"`
RenewSignalDeprecated string `hcl:"renewSignal"`
Expand Down Expand Up @@ -164,6 +173,27 @@ func (c *Config) ValidateConfig(log logrus.FieldLogger) error {
return errors.New("at least one of the sets ('svid_file_name', 'svid_key_file_name', 'svid_bundle_file_name'), 'jwt_svids', or 'jwt_bundle_file_name' must be fully specified")
}

if c.CertFileMode < 0 {
return errors.New("cert file mode must be positive")
} else if c.CertFileMode == 0 {
c.CertFileMode = defaultCertFileMode
}
if c.KeyFileMode < 0 {
return errors.New("key file mode must be positive")
} else if c.KeyFileMode == 0 {
c.KeyFileMode = defaultKeyFileMode
}
if c.JWTBundleFileMode < 0 {
return errors.New("jwt bundle file mode must be positive")
} else if c.JWTBundleFileMode == 0 {
c.JWTBundleFileMode = defaultJWTBundleFileMode
}
if c.JWTSVIDFileMode < 0 {
return errors.New("jwt svid file mode must be positive")
} else if c.JWTSVIDFileMode == 0 {
c.JWTSVIDFileMode = defaultJWTSVIDFileMode
}

return nil
}

Expand All @@ -174,6 +204,10 @@ func NewSidecarConfig(config *Config, log logrus.FieldLogger) *sidecar.Config {
Cmd: config.Cmd,
CmdArgs: config.CmdArgs,
CertDir: config.CertDir,
CertFileMode: fs.FileMode(config.CertFileMode), //nolint:gosec,G115
KeyFileMode: fs.FileMode(config.KeyFileMode), //nolint:gosec,G115
JWTBundleFileMode: fs.FileMode(config.JWTBundleFileMode), //nolint:gosec,G115
JWTSVIDFileMode: fs.FileMode(config.JWTSVIDFileMode), //nolint:gosec,G115
IncludeFederatedDomains: config.IncludeFederatedDomains,
JWTBundleFilename: config.JWTBundleFilename,
Log: log,
Expand Down
4 changes: 4 additions & 0 deletions cmd/spiffe-helper/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ func TestParseConfig(t *testing.T) {
assert.Equal(t, expectedJWTBundleFileName, c.JWTBundleFilename)
assert.Equal(t, expectedJWTAudience, c.JWTSVIDs[0].JWTAudience)
assert.True(t, c.AddIntermediatesToBundle)
assert.Equal(t, 444, c.CertFileMode)
assert.Equal(t, 444, c.KeyFileMode)
assert.Equal(t, 444, c.JWTBundleFileMode)
assert.Equal(t, 444, c.JWTSVIDFileMode)
}

func TestValidateConfig(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions cmd/spiffe-helper/config/testdata/helper.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ agent_address = "/tmp/spire-agent/public/api.sock"
cmd = "hot-restarter.py"
cmd_args = "start_envoy.sh"
cert_dir = "certs"
cert_file_mode = 444
key_file_mode = 444
jwt_bundle_file_mode = 444
jwt_svid_file_mode = 444
renew_signal = "SIGHUP"
svid_file_name = "svid.pem"
svid_key_file_name = "svid_key.pem"
Expand Down
13 changes: 7 additions & 6 deletions pkg/disk/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"path"

Expand All @@ -13,7 +14,7 @@ import (
)

// WriteJWTBundleSet write the given JWT bundles to disk
func WriteJWTBundleSet(jwkSet *jwtbundle.Set, dir string, jwtBundleFilename string) error {
func WriteJWTBundleSet(jwkSet *jwtbundle.Set, dir string, jwtBundleFilename string, jwtBundleFileMode fs.FileMode) error {
var errs []error
bundles := make(map[string]interface{})
for _, bundle := range jwkSet.Bundles() {
Expand All @@ -25,27 +26,27 @@ func WriteJWTBundleSet(jwkSet *jwtbundle.Set, dir string, jwtBundleFilename stri
bundles[bundle.TrustDomain().Name()] = base64.StdEncoding.EncodeToString(bytes)
}

if err := writeJSON(bundles, dir, jwtBundleFilename); err != nil {
if err := writeJSON(bundles, dir, jwtBundleFilename, jwtBundleFileMode); err != nil {
errs = append(errs, fmt.Errorf("unable to write JSON file: %w", err))
}

return errors.Join(errs...)
}

// WriteJWTBundle write the given JWT SVID to disk
func WriteJWTSVID(jwtSVID *jwtsvid.SVID, dir, jwtSVIDFilename string) error {
func WriteJWTSVID(jwtSVID *jwtsvid.SVID, dir, jwtSVIDFilename string, jwtSVIDFileMode fs.FileMode) error {
filePath := path.Join(dir, jwtSVIDFilename)

return os.WriteFile(filePath, []byte(jwtSVID.Marshal()), 0600)
return os.WriteFile(filePath, []byte(jwtSVID.Marshal()), jwtSVIDFileMode)
}

func writeJSON(certs map[string]any, dir, filename string) error {
func writeJSON(certs map[string]any, dir, filename string, fileMode fs.FileMode) error {
file, err := json.Marshal(certs)
if err != nil {
return err
}

filePath := path.Join(dir, filename)

return os.WriteFile(filePath, file, 0600)
return os.WriteFile(filePath, file, fileMode)
}
7 changes: 5 additions & 2 deletions pkg/disk/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/rand"
"crypto/rsa"
"fmt"
"io/fs"
"os"
"path"
"testing"
Expand All @@ -24,6 +25,8 @@ import (
const (
jwtBundleFilename = "jwt_bundle.json"
jwtSVIDFilename = "jwt.json"
jwtBundleFileMode = fs.FileMode(0600)
jwtSVIDFileMode = fs.FileMode(0600)
)

func TestWriteJWTBundleSet(t *testing.T) {
Expand All @@ -34,7 +37,7 @@ func TestWriteJWTBundleSet(t *testing.T) {

tempDir := t.TempDir()

err := WriteJWTBundleSet(jwtBundleSet, tempDir, jwtBundleFilename)
err := WriteJWTBundleSet(jwtBundleSet, tempDir, jwtBundleFilename, jwtBundleFileMode)
require.NoError(t, err)

actualJWTBundle, err := jwtbundle.Load(td, path.Join(tempDir, jwtBundleFilename))
Expand Down Expand Up @@ -64,7 +67,7 @@ func TestWriteJWTSVID(t *testing.T) {

// Write to disk
tempDir := t.TempDir()
err = WriteJWTSVID(jwtSVID, tempDir, jwtSVIDFilename)
err = WriteJWTSVID(jwtSVID, tempDir, jwtSVIDFilename, jwtSVIDFileMode)
require.NoError(t, err)

// Read back and check its the same
Expand Down
20 changes: 8 additions & 12 deletions pkg/disk/x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,18 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"io/fs"
"os"
"path"

"github.com/spiffe/go-spiffe/v2/workloadapi"
)

const (
certsFileMode = os.FileMode(0644)
keyFileMode = os.FileMode(0600)
)

// WriteX509Context takes a X509Context, representing a svid message from
// the Workload API, and calls writeCerts and writeKey to write to disk
// the svid, key and bundle of certificates.
// It is possible to change output setting `addIntermediatesToBundle` as true.
func WriteX509Context(x509Context *workloadapi.X509Context, addIntermediatesToBundle, includeFederatedDomains bool, certDir, svidFilename, svidKeyFilename, svidBundleFilename string) error {
func WriteX509Context(x509Context *workloadapi.X509Context, addIntermediatesToBundle, includeFederatedDomains bool, certDir, svidFilename, svidKeyFilename, svidBundleFilename string, certFileMode, keyFileMode fs.FileMode) error {
svidFile := path.Join(certDir, svidFilename)
svidKeyFile := path.Join(certDir, svidKeyFilename)
svidBundleFile := path.Join(certDir, svidBundleFilename)
Expand Down Expand Up @@ -58,20 +54,20 @@ func WriteX509Context(x509Context *workloadapi.X509Context, addIntermediatesToBu
}

// Write cert, key, and bundle to disk
if err := writeCerts(svidFile, certs); err != nil {
if err := writeCerts(svidFile, certs, certFileMode); err != nil {
return err
}

if err := writeKey(svidKeyFile, privateKey); err != nil {
if err := writeKey(svidKeyFile, privateKey, keyFileMode); err != nil {
return err
}

return writeCerts(svidBundleFile, bundles)
return writeCerts(svidBundleFile, bundles, certFileMode)
}

// writeCerts takes an array of certificates,
// and encodes them as PEM blocks, writing them to file
func writeCerts(file string, certs []*x509.Certificate) error {
func writeCerts(file string, certs []*x509.Certificate, certFileMode fs.FileMode) error {
var pemData []byte
for _, cert := range certs {
b := &pem.Block{
Expand All @@ -81,12 +77,12 @@ func writeCerts(file string, certs []*x509.Certificate) error {
pemData = append(pemData, pem.EncodeToMemory(b)...)
}

return os.WriteFile(file, pemData, certsFileMode)
return os.WriteFile(file, pemData, certFileMode)
}

// writeKey takes a private key as a slice of bytes,
// formats as PEM, and writes it to file
func writeKey(file string, data []byte) error {
func writeKey(file string, data []byte, keyFileMode fs.FileMode) error {
b := &pem.Block{
Type: "PRIVATE KEY",
Bytes: data,
Expand Down
5 changes: 4 additions & 1 deletion pkg/disk/x509_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package disk

import (
"io/fs"
"path"
"testing"

Expand All @@ -17,6 +18,8 @@ const (
svidFilename = "svid.pem"
svidKeyFilename = "svid_key.pem"
svidBundleFilename = "svid_bundle.pem"
certFileMode = fs.FileMode(0600)
keyFileMode = fs.FileMode(0600)
)

func TestWriteX509Context(t *testing.T) {
Expand Down Expand Up @@ -131,7 +134,7 @@ func TestWriteX509Context(t *testing.T) {
}
}

err = WriteX509Context(x509Context, test.intermediateInBundle, test.includeFederatedDomains, tempDir, svidFilename, svidKeyFilename, svidBundleFilename)
err = WriteX509Context(x509Context, test.intermediateInBundle, test.includeFederatedDomains, tempDir, svidFilename, svidKeyFilename, svidBundleFilename, certFileMode, keyFileMode)
require.NoError(t, err)

// Load certificates from disk and validate it is expected
Expand Down
14 changes: 14 additions & 0 deletions pkg/sidecar/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package sidecar

import (
"io/fs"

"github.com/sirupsen/logrus"
)

Expand All @@ -24,6 +26,18 @@ type Config struct {
// If true, fetche x509 certificate and then exit(0).
ExitWhenReady bool

// Permissions to use when writing x509 SVID to disk
CertFileMode fs.FileMode

// Permissions to use when writing x509 SVID Key to disk
KeyFileMode fs.FileMode

// Permissions to use when writing JWT Bundle to disk
JWTBundleFileMode fs.FileMode

// Permissions to use when writing JWT SVIDs to disk
JWTSVIDFileMode fs.FileMode

// If true, includes trust domains from federated servers in the CA bundle.
IncludeFederatedDomains bool

Expand Down
6 changes: 3 additions & 3 deletions pkg/sidecar/sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func (s *Sidecar) setupClients(ctx context.Context) error {
// updateCertificates Updates the certificates stored in disk and signal the Process to restart
func (s *Sidecar) updateCertificates(svidResponse *workloadapi.X509Context) {
s.config.Log.Debug("Updating X.509 certificates")
if err := disk.WriteX509Context(svidResponse, s.config.AddIntermediatesToBundle, s.config.IncludeFederatedDomains, s.config.CertDir, s.config.SVIDFileName, s.config.SVIDKeyFileName, s.config.SVIDBundleFileName); err != nil {
if err := disk.WriteX509Context(svidResponse, s.config.AddIntermediatesToBundle, s.config.IncludeFederatedDomains, s.config.CertDir, s.config.SVIDFileName, s.config.SVIDKeyFileName, s.config.SVIDBundleFileName, s.config.CertFileMode, s.config.KeyFileMode); err != nil {
s.config.Log.WithError(err).Error("Unable to dump bundle")
return
}
Expand Down Expand Up @@ -275,7 +275,7 @@ func (s *Sidecar) performJWTSVIDUpdate(ctx context.Context, jwtAudience string,
return nil, err
}

if err = disk.WriteJWTSVID(jwtSVID, s.config.CertDir, jwtSVIDFilename); err != nil {
if err = disk.WriteJWTSVID(jwtSVID, s.config.CertDir, jwtSVIDFilename, s.config.JWTSVIDFileMode); err != nil {
s.config.Log.Errorf("Unable to update JWT SVID: %v", err)
return nil, err
}
Expand Down Expand Up @@ -372,7 +372,7 @@ type JWTBundlesWatcher struct {
// OnJWTBundlesUpdate is ran every time a bundle is updated
func (w JWTBundlesWatcher) OnJWTBundlesUpdate(jwkSet *jwtbundle.Set) {
w.sidecar.config.Log.Debug("Updating JWT bundle")
if err := disk.WriteJWTBundleSet(jwkSet, w.sidecar.config.CertDir, w.sidecar.config.JWTBundleFilename); err != nil {
if err := disk.WriteJWTBundleSet(jwkSet, w.sidecar.config.CertDir, w.sidecar.config.JWTBundleFilename, w.sidecar.config.JWTBundleFileMode); err != nil {
w.sidecar.config.Log.Errorf("Error writing JWT Bundle to disk: %v", err)
return
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/sidecar/sidecar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"crypto"
"crypto/x509"
"os"
"path"
"testing"
"time"
Expand Down Expand Up @@ -82,6 +83,10 @@ func TestSidecar_RunDaemon(t *testing.T) {
SVIDKeyFileName: "svid_key.pem",
SVIDBundleFileName: "svid_bundle.pem",
Log: log,
CertFileMode: os.FileMode(0644),
KeyFileMode: os.FileMode(0600),
JWTBundleFileMode: os.FileMode(0600),
JWTSVIDFileMode: os.FileMode(0600),
}

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
Expand Down
6 changes: 3 additions & 3 deletions pkg/sidecar/workloadapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (s *Sidecar) fetchAndWriteX509Context(ctx context.Context) error {
return err
}

return disk.WriteX509Context(x509Context, s.config.AddIntermediatesToBundle, s.config.IncludeFederatedDomains, s.config.CertDir, s.config.SVIDFileName, s.config.SVIDKeyFileName, s.config.SVIDBundleFileName)
return disk.WriteX509Context(x509Context, s.config.AddIntermediatesToBundle, s.config.IncludeFederatedDomains, s.config.CertDir, s.config.SVIDFileName, s.config.SVIDKeyFileName, s.config.SVIDBundleFileName, s.config.CertFileMode, s.config.KeyFileMode)
}

func (s *Sidecar) fetchAndWriteJWTBundle(ctx context.Context) error {
Expand All @@ -56,7 +56,7 @@ func (s *Sidecar) fetchAndWriteJWTBundle(ctx context.Context) error {
return err
}

return disk.WriteJWTBundleSet(jwtBundleSet, s.config.CertDir, s.config.JWTBundleFilename)
return disk.WriteJWTBundleSet(jwtBundleSet, s.config.CertDir, s.config.JWTBundleFilename, s.config.JWTBundleFileMode)
}

func (s *Sidecar) fetchAndWriteJWTSVIDs(ctx context.Context) error {
Expand Down Expand Up @@ -84,5 +84,5 @@ func (s *Sidecar) fetchAndWriteJWTSVID(ctx context.Context, audience, jwtSVIDFil
return err
}

return disk.WriteJWTSVID(jwtSVID, s.config.CertDir, jwtSVIDFilename)
return disk.WriteJWTSVID(jwtSVID, s.config.CertDir, jwtSVIDFilename, s.config.JWTSVIDFileMode)
}
Loading