diff --git a/README.md b/README.md index 6c675f3e..ac5f6e76 100644 --- a/README.md +++ b/README.md @@ -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 ``` @@ -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 diff --git a/cmd/spiffe-helper/config/config.go b/cmd/spiffe-helper/config/config.go index fbc3c3bd..f3f1b5cf 100644 --- a/cmd/spiffe-helper/config/config.go +++ b/cmd/spiffe-helper/config/config.go @@ -3,6 +3,7 @@ package config import ( "errors" "flag" + "io/fs" "os" "github.com/hashicorp/hcl" @@ -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 { @@ -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"` @@ -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 } @@ -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, diff --git a/cmd/spiffe-helper/config/config_test.go b/cmd/spiffe-helper/config/config_test.go index 73950a8f..38f25e4c 100644 --- a/cmd/spiffe-helper/config/config_test.go +++ b/cmd/spiffe-helper/config/config_test.go @@ -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) { diff --git a/cmd/spiffe-helper/config/testdata/helper.conf b/cmd/spiffe-helper/config/testdata/helper.conf index ccd57742..0219e304 100644 --- a/cmd/spiffe-helper/config/testdata/helper.conf +++ b/cmd/spiffe-helper/config/testdata/helper.conf @@ -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" diff --git a/pkg/disk/json.go b/pkg/disk/json.go index d882c7d6..1997144e 100644 --- a/pkg/disk/json.go +++ b/pkg/disk/json.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "io/fs" "os" "path" @@ -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() { @@ -25,7 +26,7 @@ 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)) } @@ -33,13 +34,13 @@ func WriteJWTBundleSet(jwkSet *jwtbundle.Set, dir string, jwtBundleFilename stri } // 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 @@ -47,5 +48,5 @@ func writeJSON(certs map[string]any, dir, filename string) error { filePath := path.Join(dir, filename) - return os.WriteFile(filePath, file, 0600) + return os.WriteFile(filePath, file, fileMode) } diff --git a/pkg/disk/json_test.go b/pkg/disk/json_test.go index 9f58b6a3..beac0c13 100644 --- a/pkg/disk/json_test.go +++ b/pkg/disk/json_test.go @@ -7,6 +7,7 @@ import ( "crypto/rand" "crypto/rsa" "fmt" + "io/fs" "os" "path" "testing" @@ -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) { @@ -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)) @@ -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 diff --git a/pkg/disk/x509.go b/pkg/disk/x509.go index 013fb810..f0f04286 100644 --- a/pkg/disk/x509.go +++ b/pkg/disk/x509.go @@ -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) @@ -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{ @@ -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, diff --git a/pkg/disk/x509_test.go b/pkg/disk/x509_test.go index ae7bf697..efbaa4e6 100644 --- a/pkg/disk/x509_test.go +++ b/pkg/disk/x509_test.go @@ -1,6 +1,7 @@ package disk import ( + "io/fs" "path" "testing" @@ -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) { @@ -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 diff --git a/pkg/sidecar/config.go b/pkg/sidecar/config.go index 9d08b3b7..c2694862 100644 --- a/pkg/sidecar/config.go +++ b/pkg/sidecar/config.go @@ -1,6 +1,8 @@ package sidecar import ( + "io/fs" + "github.com/sirupsen/logrus" ) @@ -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 diff --git a/pkg/sidecar/sidecar.go b/pkg/sidecar/sidecar.go index c1799ec3..45deefa0 100644 --- a/pkg/sidecar/sidecar.go +++ b/pkg/sidecar/sidecar.go @@ -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 } @@ -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 } @@ -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 } diff --git a/pkg/sidecar/sidecar_test.go b/pkg/sidecar/sidecar_test.go index 736ab450..21eb39fa 100644 --- a/pkg/sidecar/sidecar_test.go +++ b/pkg/sidecar/sidecar_test.go @@ -4,6 +4,7 @@ import ( "context" "crypto" "crypto/x509" + "os" "path" "testing" "time" @@ -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) diff --git a/pkg/sidecar/workloadapi.go b/pkg/sidecar/workloadapi.go index bef682ef..82383513 100644 --- a/pkg/sidecar/workloadapi.go +++ b/pkg/sidecar/workloadapi.go @@ -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 { @@ -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 { @@ -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) }