Skip to content

Commit

Permalink
Add ability to configure file permissions of written files (closes #183
Browse files Browse the repository at this point in the history
…) (#184)

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

Signed-off-by: Keegan Witt <[email protected]>

* Switch new conf options from string to int

Signed-off-by: Keegan Witt <[email protected]>

* Update pkg/disk/json_test.go

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

* Update cmd/spiffe-helper/config/config.go

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

* Update pkg/disk/x509.go

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

* Update README.md

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

* Fix casing

Signed-off-by: Keegan Witt <[email protected]>

* Fix spacing

Signed-off-by: Keegan Witt <[email protected]>

* Fix spelling

Signed-off-by: Keegan Witt <[email protected]>

* Match casing

Signed-off-by: Keegan Witt <[email protected]>

* Match casing

Signed-off-by: Keegan Witt <[email protected]>

* Change file mode configs to not be a pointer

Signed-off-by: Keegan Witt <[email protected]>

* Update pkg/sidecar/config.go

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

* Update pkg/sidecar/sidecar.go

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

* Update pkg/sidecar/sidecar.go

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

* Update pkg/sidecar/workloadapi.go

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

* Update cmd/spiffe-helper/config/config.go

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

* Update cmd/spiffe-helper/config/config.go

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

* Update pkg/sidecar/workloadapi.go

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

* Fix casing

Signed-off-by: Keegan Witt <[email protected]>

* Fix casing

Signed-off-by: Keegan Witt <[email protected]>

* go fmt

Signed-off-by: Keegan Witt <[email protected]>

* Move defaulting logic to ValidateConfig

Signed-off-by: Keegan Witt <[email protected]>

* Ignore linting errors

Signed-off-by: Keegan Witt <[email protected]>

* Update README.md

Co-authored-by: Faisal Memon <[email protected]>
Signed-off-by: Keegan Witt <[email protected]>

---------

Signed-off-by: Keegan Witt <[email protected]>
Co-authored-by: Faisal Memon <[email protected]>
  • Loading branch information
keeganwitt and faisal-memon authored Sep 18, 2024
1 parent 1df4fdb commit b6b1642
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 29 deletions.
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)
}

0 comments on commit b6b1642

Please sign in to comment.