diff --git a/go.mod b/go.mod index 5d28dea..a1a1854 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/onsi/gomega v1.19.0 github.com/spf13/cobra v1.4.0 github.com/spf13/pflag v1.0.5 + github.com/spiffe/go-spiffe/v2 v2.1.0 github.com/stretchr/testify v1.7.1 gopkg.in/square/go-jose.v2 v2.6.0 k8s.io/api v0.23.6 @@ -128,6 +129,7 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect + github.com/zeebo/errs v1.2.2 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect @@ -141,8 +143,8 @@ require ( gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 // indirect - google.golang.org/grpc v1.43.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/grpc v1.46.0 // indirect + google.golang.org/protobuf v1.28.0 // indirect gopkg.in/gorp.v1 v1.7.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index e78cea0..6fa882a 100644 --- a/go.sum +++ b/go.sum @@ -120,8 +120,9 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= @@ -416,6 +417,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= @@ -1055,6 +1057,8 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/spiffe/go-spiffe/v2 v2.1.0 h1:IZRlWhyFpPbJOiK8K+MwEFPU/QCdaW4Zf5bmIKBd3XM= +github.com/spiffe/go-spiffe/v2 v2.1.0/go.mod h1:5qg6rpqlwIub0JAiF1UK9IMD6BpPTmvG6yfSgDBs5lg= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1116,6 +1120,8 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMzt github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g= +github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -1618,6 +1624,7 @@ google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1690,9 +1697,11 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc/examples v0.0.0-20201130180447-c456688b1860/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1705,8 +1714,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1731,6 +1741,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= diff --git a/internal/csi/driver/camanager.go b/internal/csi/driver/camanager.go index c79d47b..3446312 100644 --- a/internal/csi/driver/camanager.go +++ b/internal/csi/driver/camanager.go @@ -21,111 +21,155 @@ import ( "context" "fmt" "time" + + "github.com/cert-manager/csi-driver-spiffe/internal/csi/rootca" + "github.com/cert-manager/csi-lib/storage" + "github.com/go-logr/logr" ) -// manageCAFiles subscribes to events from the Root CAs provider, and updates -// all managed volumes CA files accordingly. Exits early if rootCAs is not -// configured. -func (d *Driver) manageCAFiles(ctx context.Context, updateRetryPeriod time.Duration) { - log := d.log.WithName("ca-manager") +// camanager is a process responsible for distributing trust bundles to +// mounting pods. +type camanager struct { + // log is the logger for camanager. + log logr.Logger + + // store is the csi-lib file system storage implementation. Must by file + // system in order to read volumes back from mounted pods. + store *storage.Filesystem + + // rootCAs exposes the current trust bundle to be propagated, and signals + // when a new trust bundle is available. + rootCAs rootca.Interface + + // certFileName, keyFileName, caFileName are the names used when writing file + // to volumes. + certFileName, keyFileName, caFileName string + + // updateRootCAFiles is a func to update all managed volumes with the current + // root CA certificates PEM. Used for testing. + updateRootCAFilesFn func() error +} + +// newCAManager constructs a new camanager which distributes new trust bundles +// to mounted pods, as they are changed. +func newCAManager(log logr.Logger, + store *storage.Filesystem, + rootCAs rootca.Interface, + certFileName, keyFileName, caFileName string, +) *camanager { + c := &camanager{ + log: log.WithName("ca-manager"), + store: store, + rootCAs: rootCAs, + certFileName: certFileName, + keyFileName: keyFileName, + caFileName: caFileName, + } + c.updateRootCAFilesFn = c.updateRootCAFiles + return c +} +// run subscribes to events from the Root CAs provider, and updates all managed +// volumes CA files accordingly. Exits early if rootCAs is not configured. +// Blocking function. +func (c *camanager) run(ctx context.Context, updateRetryPeriod time.Duration) { // Exit straight away if root CAs haven't been configured. - if d.rootCAs == nil { - log.Info("not running CA file manager, root CA certificates not configured") + if c.rootCAs == nil { + c.log.Info("not running CA file manager, root CA certificates not configured") return } - watcher := d.rootCAs.Subscribe() + watcher := c.rootCAs.Subscribe() - log.Info("starting root CA file manager") + c.log.Info("starting root CA file manager") // updateChan is used to trigger an update of CA certificates of file of all // managed volumes. Trigged by both RootCAs events, as well as retrying updates on errors. updateChan := make(chan struct{}, 1) - go func() { - for { - select { - case <-ctx.Done(): - log.Info("closing root CA file manager") - return - - case <-watcher: - updateChan <- struct{}{} - - case <-updateChan: - log.Info("root CA file event received, updating managed volumes") - - if err := d.updateRootCAFilesFn(); err != nil { - log.Error(err, "failed to update root CA files on managed volumes") - - // Retry updating the root CA files. - go func() { - select { - // Wait for 5 seconds before retrying. - case <-time.After(updateRetryPeriod): - case <-ctx.Done(): - return - } - log.Error(err, "retrying CA file update...") - updateChan <- struct{}{} - }() - - continue - } - - log.Info("updated root CA files on managed volumes") + for { + select { + case <-ctx.Done(): + c.log.Info("closing root CA file manager") + return + + case <-watcher: + updateChan <- struct{}{} + + case <-updateChan: + c.log.Info("root CA file event received, updating managed volumes") + + if err := c.updateRootCAFilesFn(); err != nil { + c.log.Error(err, "failed to update root CA files on managed volumes") + + // Retry updating the root CA files. + go func() { + select { + // Wait for 5 seconds before retrying. + case <-time.After(updateRetryPeriod): + case <-ctx.Done(): + return + } + c.log.Error(err, "retrying CA file update...") + updateChan <- struct{}{} + }() + + continue } + + c.log.Info("updated root CA files on managed volumes") } - }() + } } // updateRootCAFiles will update all managed volumes with the CA certificates // data returned from rootCAs. -func (d *Driver) updateRootCAFiles() error { - if d.rootCAs == nil { +func (c *camanager) updateRootCAFiles() error { + if c.rootCAs == nil { // Exit early if rootCAs is not configured. return nil } - volumeIDs, err := d.store.ListVolumes() + log := c.log.WithName("ca-updater") + + volumeIDs, err := c.store.ListVolumes() if err != nil { return fmt.Errorf("failed to list managed volumes: %w", err) } for _, volumeID := range volumeIDs { - meta, err := d.store.ReadMetadata(volumeID) + meta, err := c.store.ReadMetadata(volumeID) if err != nil { return fmt.Errorf("%q: failed to read metadata from volume: %w", volumeID, err) } - certData, err := d.store.ReadFile(volumeID, d.certFileName) + certData, err := c.store.ReadFile(volumeID, c.certFileName) if err != nil { return fmt.Errorf("%q: failed to read certificate file from volume to perform write: %w", volumeID, err) } - keyData, err := d.store.ReadFile(volumeID, d.keyFileName) + keyData, err := c.store.ReadFile(volumeID, c.keyFileName) if err != nil { return fmt.Errorf("%q: failed to read key file from volume to perform write: %w", volumeID, err) } // No need to re-write CA data again if it hasn't changed on file. - caData, err := d.store.ReadFile(volumeID, d.caFileName) - if err == nil && bytes.Equal(caData, d.rootCAs.CertificatesPEM()) { + caData, err := c.store.ReadFile(volumeID, c.caFileName) + if err == nil && bytes.Equal(caData, c.rootCAs.CertificatesPEM()) { continue } - if err := d.store.WriteFiles(meta, map[string][]byte{ - d.certFileName: certData, - d.keyFileName: keyData, - d.caFileName: d.rootCAs.CertificatesPEM(), + if err := c.store.WriteFiles(meta, map[string][]byte{ + c.certFileName: certData, + c.keyFileName: keyData, + c.caFileName: c.rootCAs.CertificatesPEM(), }); err != nil { return fmt.Errorf("%q: failed to write new ca data to volume: %w", volumeID, err) } - d.log.WithName("ca-updater").Info("updated CA file on volume", "volume", volumeID) + log.Info("updated CA file on volume", "volume", volumeID) } return nil diff --git a/internal/csi/driver/camanager_test.go b/internal/csi/driver/camanager_test.go index 918c0df..6aaaf13 100644 --- a/internal/csi/driver/camanager_test.go +++ b/internal/csi/driver/camanager_test.go @@ -36,15 +36,17 @@ func Test_manageCAFiles(t *testing.T) { t.Log("starting manageCAFiles()") rootCAsChan := make(chan []byte) - d := &Driver{ + c := &camanager{ log: klogr.New(), rootCAs: rootca.NewMemory(ctx, rootCAsChan), } - d.manageCAFiles(ctx, time.Millisecond*5) + go func() { + c.run(ctx, time.Millisecond*5) + }() t.Log("if root CAs update happens, expect updateRootCAFilesFn() to be called") calledCtx, calledCancel := context.WithCancel(context.TODO()) - d.updateRootCAFilesFn = func() error { + c.updateRootCAFilesFn = func() error { t.Log("updateRootCAFilesFn() called") calledCancel() return nil @@ -63,7 +65,7 @@ func Test_manageCAFiles(t *testing.T) { t.Log("should call updateRootCAFilesFn() again if it fails") var i int calledTwiceChan := make(chan struct{}) - d.updateRootCAFilesFn = func() error { + c.updateRootCAFilesFn = func() error { if i == 0 { i++ t.Log("returning error from updateRootCAFilesFn()") diff --git a/internal/csi/driver/driver.go b/internal/csi/driver/driver.go index 79d37bf..4afd1d5 100644 --- a/internal/csi/driver/driver.go +++ b/internal/csi/driver/driver.go @@ -19,11 +19,11 @@ package driver import ( "context" "crypto" - "crypto/ecdsa" "crypto/x509" "encoding/pem" "fmt" "net/url" + "sync" "time" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -123,11 +123,11 @@ type Driver struct { driver *driver.Driver // store is the csi-lib implementation of a cert-manager CSI storage manager. - store *storage.Filesystem + store storage.Interface - // updateRootCAFiles is a func to update all managed volumes with the current - // root CA certificates PEM. Used for testing. - updateRootCAFilesFn func() error + // camanager is used to update all managed volumes with the current root CA + // certificates PEM. + camanager *camanager } // New constructs a new Driver instance. @@ -156,16 +156,19 @@ func New(log logr.Logger, opts Options) (*Driver, error) { if d.certificateRequestDuration == 0 { d.certificateRequestDuration = time.Hour } - d.updateRootCAFilesFn = d.updateRootCAFiles var err error - d.store, err = storage.NewFilesystem(d.log, opts.DataRoot) + store, err := storage.NewFilesystem(d.log, opts.DataRoot) if err != nil { return nil, fmt.Errorf("failed to setup filesystem: %w", err) } // Used by clients to set the stored file's file-system group before // mounting. - d.store.FSGroupVolumeAttributeKey = "spiffe.csi.cert-manager.io/fs-group" + store.FSGroupVolumeAttributeKey = "spiffe.csi.cert-manager.io/fs-group" + + d.store = store + d.camanager = newCAManager(log, store, opts.RootCAs, + opts.CertificateFileName, opts.KeyFileName, opts.CAFileName) cmclient, err := cmclient.NewForConfig(opts.RestConfig) if err != nil { @@ -202,13 +205,28 @@ func New(log logr.Logger, opts Options) (*Driver, error) { // Run is a blocking func that run the CSI driver. func (d *Driver) Run(ctx context.Context) error { + var wg sync.WaitGroup + go func() { <-ctx.Done() d.driver.Stop() }() - d.manageCAFiles(ctx, time.Second*5) - return d.driver.Run() + wg.Add(1) + go func() { + defer wg.Done() + d.camanager.run(ctx, time.Second*5) + }() + + wg.Add(1) + var err error + go func() { + defer wg.Done() + err = d.driver.Run() + }() + + wg.Wait() + return err } // generateRequest will generate a SPIFFE manager.CertificateRequestBundle @@ -274,14 +292,14 @@ func (d *Driver) generateRequest(meta metadata.Metadata) (*manager.CertificateRe // writeKeypair writes the private key and certificate chain to file that will // be mounted into the pod. func (d *Driver) writeKeypair(meta metadata.Metadata, key crypto.PrivateKey, chain []byte, _ []byte) error { - pemBytes, err := x509.MarshalECPrivateKey(key.(*ecdsa.PrivateKey)) + pemBytes, err := x509.MarshalPKCS8PrivateKey(key) if err != nil { return fmt.Errorf("failed to marshal ECDSA private key for PEM encoding: %w", err) } keyPEM := pem.EncodeToMemory( &pem.Block{ - Type: "EC PRIVATE KEY", + Type: "PRIVATE KEY", Bytes: pemBytes, }, ) diff --git a/internal/csi/driver/driver_test.go b/internal/csi/driver/driver_test.go new file mode 100644 index 0000000..21cb7b7 --- /dev/null +++ b/internal/csi/driver/driver_test.go @@ -0,0 +1,85 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package driver + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "testing" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + utilpki "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/csi-lib/metadata" + "github.com/cert-manager/csi-lib/storage" + "github.com/spiffe/go-spiffe/v2/svid/x509svid" + "github.com/stretchr/testify/require" + + "github.com/cert-manager/csi-driver-spiffe/internal/csi/rootca" +) + +// Ensure writeKeyPair is compatible with go-spiffe/v2 x509svid.Parse. +func Test_writeKeyPair(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(func() { + cancel() + }) + + capk, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + require.NoError(t, err) + caTmpl, err := utilpki.GenerateTemplate(&cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "my-ca"}}) + require.NoError(t, err) + caPEM, ca, err := utilpki.SignCertificate(caTmpl, caTmpl, capk.Public(), capk) + require.NoError(t, err) + + leafpk, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + require.NoError(t, err) + leafTmpl, err := utilpki.GenerateTemplate( + &cmapi.Certificate{ + Spec: cmapi.CertificateSpec{URIs: []string{"spiffe://cert-manager.io/ns/sandbox/sa/default"}}, + }, + ) + + require.NoError(t, err) + leafPEM, _, err := utilpki.SignCertificate(leafTmpl, ca, leafpk.Public(), capk) + require.NoError(t, err) + + ch := make(chan []byte) + rootCAs := rootca.NewMemory(ctx, ch) + ch <- caPEM + + store := storage.NewMemoryFS() + d := &Driver{ + certFileName: "crt.pem", + keyFileName: "key.pem", + caFileName: "ca.pem", + rootCAs: rootCAs, + store: store, + } + + meta := metadata.Metadata{VolumeID: "vol-id"} + _, err = store.RegisterMetadata(meta) + require.NoError(t, err) + require.NoError(t, d.writeKeypair(meta, leafpk, leafPEM, nil)) + + files, err := store.ReadFiles("vol-id") + require.NoError(t, err) + + _, err = x509svid.Parse(files["crt.pem"], files["key.pem"]) + require.NoError(t, err) +}