From 7530f180139c39b95b1f3a873a1e9fa89f96bc4e Mon Sep 17 00:00:00 2001 From: Mikhail Volkov Date: Fri, 8 May 2026 08:50:42 +0300 Subject: [PATCH 1/6] nodepublishsecretref - first commit --- go.mod | 10 +++-- go.sum | 26 ++++++------- pkg/apis/v1alpha1/types.go | 5 +++ pkg/apis/validation/validation.go | 6 +-- pkg/apis/validation/validation_test.go | 14 ++++--- pkg/filestore/writer.go | 2 +- pkg/keystore/pkcs12/pkcs12.go | 18 ++++++++- pkg/keystore/pkcs12/pkcs12_test.go | 53 ++++++++++++++++++++++++-- 8 files changed, 102 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index e1f79300..aa787574 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kubernetes-csi/csi-lib-utils v0.23.0 // indirect + github.com/kubernetes-csi/csi-lib-utils v0.23.2 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.9.1 // indirect github.com/moby/spdystream v0.5.1 // indirect @@ -82,7 +82,7 @@ require ( golang.org/x/crypto v0.50.0 // indirect golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.53.0 // indirect - golang.org/x/oauth2 v0.35.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/term v0.42.0 // indirect golang.org/x/text v0.36.0 // indirect @@ -90,14 +90,14 @@ require ( golang.org/x/tools v0.44.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect - google.golang.org/grpc v1.79.3 // indirect + google.golang.org/grpc v1.81.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.36.0 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect - k8s.io/mount-utils v0.34.3 // indirect + k8s.io/mount-utils v0.36.0 // indirect k8s.io/streaming v0.36.0 // indirect sigs.k8s.io/gateway-api v1.5.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect @@ -107,3 +107,5 @@ require ( sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) + +replace github.com/cert-manager/csi-lib => ../csi-lib diff --git a/go.sum b/go.sum index 78c86a71..0804d7b4 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,6 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cert-manager/cert-manager v1.20.2 h1:CimnY00nLqB2lmxhoSuEC4GDMFDK7JCXqyjwMM9ndIQ= github.com/cert-manager/cert-manager v1.20.2/go.mod h1:1g/+a/WK5zWH/dXPZa3dMD3aJQJNRXQu+PN17C6WrOw= -github.com/cert-manager/csi-lib v0.10.0 h1:sSS+xBaRAT8QPkdCv6MI/6DxJI8fLQ0s1JTyF3ZWfZg= -github.com/cert-manager/csi-lib v0.10.0/go.mod h1:IULeJ/WH0dbO8248Rv95WASV9gPbuWddBJDz3Yc0sks= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/container-storage-interface/spec v1.12.0 h1:zrFOEqpR5AghNaaDG4qyedwPBqU2fU0dWjLQMP/azK0= @@ -114,8 +112,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kubernetes-csi/csi-lib-utils v0.23.0 h1:070SC4ubEvJpQak0ibxgv7l5dUoDVdqKyktam6zkm4s= -github.com/kubernetes-csi/csi-lib-utils v0.23.0/go.mod h1:H5+JRXAvb7lpC4nrddI7sfQfaXA1O8Tek3uNrTIx1/g= +github.com/kubernetes-csi/csi-lib-utils v0.23.2 h1:+x9W4RRyuRnJcTiSHKEFpl0cYUeLUqshM5ioPeYPRXw= +github.com/kubernetes-csi/csi-lib-utils v0.23.2/go.mod h1:aIcqnC6EyesZpe7kX5PxHUZePw1tKrYFKwg7RaqlPh8= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -198,8 +196,8 @@ go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWv go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -218,8 +216,8 @@ golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= -golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= -golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -235,12 +233,12 @@ golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w= google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= +google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -271,8 +269,8 @@ k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hk k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= k8s.io/kubectl v0.36.0 h1:hEGr8NvIm2Wjqs2Xy48Uzmvo6lpHdGKlLyMvau2gTms= k8s.io/kubectl v0.36.0/go.mod h1:iDe8aV5BEi45W8k+5n71I2pJ/nwE0PHDu+/2cejzYoo= -k8s.io/mount-utils v0.34.3 h1:+sk7PVMQhGoNkGnxmxhyjEXpFcTaD6s3a6NXZNhqERc= -k8s.io/mount-utils v0.34.3/go.mod h1:MIjjYlqJ0ziYQg0MO09kc9S96GIcMkhF/ay9MncF0GA= +k8s.io/mount-utils v0.36.0 h1:ufsqGyCoPDh7p+6OIa1wv6oH9GqkQQ8XIfEOVfCV3g0= +k8s.io/mount-utils v0.36.0/go.mod h1:+I47UOG6FiUGVSy7VanjU/mQXLShMo3M7xBpGLzCub8= k8s.io/streaming v0.36.0 h1:agnTxU+NFulUrtYzXUGKO3ndEa8jKwht1Kwn9nu9x+4= k8s.io/streaming v0.36.0/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= diff --git a/pkg/apis/v1alpha1/types.go b/pkg/apis/v1alpha1/types.go index 6277e45b..6fb0131b 100644 --- a/pkg/apis/v1alpha1/types.go +++ b/pkg/apis/v1alpha1/types.go @@ -53,6 +53,11 @@ const ( KeyStorePKCS12EnableKey = "csi.cert-manager.io/pkcs12-enable" KeyStorePKCS12FileKey = "csi.cert-manager.io/pkcs12-filename" KeyStorePKCS12PasswordKey = "csi.cert-manager.io/pkcs12-password" // #nosec G101: False positive, gosec thinks this is a credential. + + // KeyStorePKCS12PasswordSecretKey is the key within the secret referenced by + // nodePublishSecretRef that holds the PKCS12 keystore password. Takes + // precedence over KeyStorePKCS12PasswordKey when both are provided. + KeyStorePKCS12PasswordSecretKey = "pkcs12-password" // #nosec G101 ) const ( diff --git a/pkg/apis/validation/validation.go b/pkg/apis/validation/validation.go index 84fba743..ece196a6 100644 --- a/pkg/apis/validation/validation.go +++ b/pkg/apis/validation/validation.go @@ -239,9 +239,9 @@ func pkcs12Values(path *field.Path, attr map[string]string) field.ErrorList { if file := attr[csiapi.KeyStorePKCS12FileKey]; len(file) == 0 { el = append(el, field.Required(path.Child(csiapi.KeyStorePKCS12FileKey), "required attribute when PKCS12 KeyStore is enabled")) } - if password := attr[csiapi.KeyStorePKCS12PasswordKey]; len(password) == 0 { - el = append(el, field.Required(path.Child(csiapi.KeyStorePKCS12PasswordKey), "required attribute when PKCS12 KeyStore is enabled")) - } + // Password is not validated here — it may be provided via nodePublishSecretRef + // instead of as a plaintext attribute. Handle() will error at write time if + // neither source provides a password. switch enable { case "false", "true": diff --git a/pkg/apis/validation/validation_test.go b/pkg/apis/validation/validation_test.go index 1efee7b8..f257b877 100644 --- a/pkg/apis/validation/validation_test.go +++ b/pkg/apis/validation/validation_test.go @@ -516,26 +516,30 @@ func Test_PKCS12Values(t *testing.T) { field.NotSupported(basePath.Child("csi.cert-manager.io/pkcs12-enable"), "foo", []string{"true", "false"}), }, }, - "if key and password is not defined, and enabled is defined as true, expect error": { + "if filename is not defined and enabled is true, expect error": { attr: map[string]string{ "csi.cert-manager.io/pkcs12-enable": "true", }, expErr: field.ErrorList{ field.Required(basePath.Child("csi.cert-manager.io/pkcs12-filename"), "required attribute when PKCS12 KeyStore is enabled"), - field.Required(basePath.Child("csi.cert-manager.io/pkcs12-password"), "required attribute when PKCS12 KeyStore is enabled"), }, }, - "if key and password is defined as empty string, and enabled is defined as true, expect error": { + "if filename is empty string and enabled is true, expect error": { attr: map[string]string{ "csi.cert-manager.io/pkcs12-enable": "true", "csi.cert-manager.io/pkcs12-filename": "", - "csi.cert-manager.io/pkcs12-password": "", }, expErr: field.ErrorList{ field.Required(basePath.Child("csi.cert-manager.io/pkcs12-filename"), "required attribute when PKCS12 KeyStore is enabled"), - field.Required(basePath.Child("csi.cert-manager.io/pkcs12-password"), "required attribute when PKCS12 KeyStore is enabled"), }, }, + "if filename is defined but password is absent, expect no error (password may come from nodePublishSecretRef)": { + attr: map[string]string{ + "csi.cert-manager.io/pkcs12-enable": "true", + "csi.cert-manager.io/pkcs12-filename": "my-file", + }, + expErr: nil, + }, "if key and password is defined, and enabled is defined as true, expect no error": { attr: map[string]string{ "csi.cert-manager.io/pkcs12-enable": "true", diff --git a/pkg/filestore/writer.go b/pkg/filestore/writer.go index 69f7e474..3b57c923 100644 --- a/pkg/filestore/writer.go +++ b/pkg/filestore/writer.go @@ -85,7 +85,7 @@ func (w *Writer) WriteKeypair(meta metadata.Metadata, key crypto.PrivateKey, cha } // Handle PKCS12 keystore attributes. - if err := pkcs12.Handle(attrs, files, key, chain); err != nil { + if err := pkcs12.Handle(meta, attrs, files, key, chain); err != nil { return err } diff --git a/pkg/keystore/pkcs12/pkcs12.go b/pkg/keystore/pkcs12/pkcs12.go index c0ae1087..47977031 100644 --- a/pkg/keystore/pkcs12/pkcs12.go +++ b/pkg/keystore/pkcs12/pkcs12.go @@ -22,6 +22,7 @@ import ( "fmt" "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/csi-lib/metadata" "software.sslmate.com/src/go-pkcs12" csiapi "github.com/cert-manager/csi-driver/pkg/apis/v1alpha1" @@ -30,13 +31,26 @@ import ( // Handle will handle PKCS12 keystore options in the given Volume attributes. // If enabled, A PKCS12 keystore file will be encoded and written to the given // file store. -func Handle(attributes map[string]string, files map[string][]byte, pk crypto.PrivateKey, chainPEM []byte) error { +// +// The PKCS12 password is resolved in order: +// 1. meta.Secrets[KeyStorePKCS12PasswordSecretKey] (from nodePublishSecretRef) +// 2. attributes[KeyStorePKCS12PasswordKey] (plaintext attribute, kept for +// backward compatibility) +func Handle(meta metadata.Metadata, attributes map[string]string, files map[string][]byte, pk crypto.PrivateKey, chainPEM []byte) error { // If PKCS12 support is not enabled, return early. if attributes[csiapi.KeyStorePKCS12EnableKey] != "true" { return nil } - pfx, err := create(attributes[csiapi.KeyStorePKCS12PasswordKey], pk, chainPEM) + password, ok := meta.Secrets[csiapi.KeyStorePKCS12PasswordSecretKey] + if !ok { + password = attributes[csiapi.KeyStorePKCS12PasswordKey] + } + if password == "" { + return errors.New("pkcs12 password must be provided via nodePublishSecretRef or the pkcs12-password attribute") + } + + pfx, err := create(password, pk, chainPEM) if err != nil { return fmt.Errorf("failed to create pkcs12 file: %w", err) } diff --git a/pkg/keystore/pkcs12/pkcs12_test.go b/pkg/keystore/pkcs12/pkcs12_test.go index 06024301..eb287964 100644 --- a/pkg/keystore/pkcs12/pkcs12_test.go +++ b/pkg/keystore/pkcs12/pkcs12_test.go @@ -22,10 +22,12 @@ import ( "crypto/x509" "testing" + "github.com/cert-manager/csi-lib/metadata" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "software.sslmate.com/src/go-pkcs12" + csiapi "github.com/cert-manager/csi-driver/pkg/apis/v1alpha1" "github.com/cert-manager/csi-driver/test/unit" ) @@ -33,6 +35,7 @@ func Test_Handle(t *testing.T) { root := unit.MustCreateBundle(t, nil, "root") tests := map[string]struct { + meta metadata.Metadata attributes map[string]string pk crypto.PrivateKey chainPEM []byte @@ -40,13 +43,15 @@ func Test_Handle(t *testing.T) { expErr bool }{ "if no PKCS12 attributes provided, expect no files written": { + meta: metadata.Metadata{}, attributes: map[string]string{}, pk: root.PK, chainPEM: root.PEM, expFiles: []string{}, expErr: false, }, - "if PKCS12 enabled with options, expect file written": { + "if PKCS12 enabled with password in attribute, expect file written": { + meta: metadata.Metadata{}, attributes: map[string]string{ "csi.cert-manager.io/pkcs12-enable": "true", "csi.cert-manager.io/pkcs12-password": "my-password", @@ -57,13 +62,55 @@ func Test_Handle(t *testing.T) { expFiles: []string{"crt.p12"}, expErr: false, }, + "if PKCS12 enabled with password in secret, expect file written": { + meta: metadata.Metadata{ + Secrets: map[string]string{ + csiapi.KeyStorePKCS12PasswordSecretKey: "secret-password", + }, + }, + attributes: map[string]string{ + "csi.cert-manager.io/pkcs12-enable": "true", + "csi.cert-manager.io/pkcs12-filename": "crt.p12", + }, + pk: root.PK, + chainPEM: root.PEM, + expFiles: []string{"crt.p12"}, + expErr: false, + }, + "if PKCS12 enabled with password in both, secret takes precedence": { + meta: metadata.Metadata{ + Secrets: map[string]string{ + csiapi.KeyStorePKCS12PasswordSecretKey: "secret-password", + }, + }, + attributes: map[string]string{ + "csi.cert-manager.io/pkcs12-enable": "true", + "csi.cert-manager.io/pkcs12-password": "attr-password", + "csi.cert-manager.io/pkcs12-filename": "crt.p12", + }, + pk: root.PK, + chainPEM: root.PEM, + expFiles: []string{"crt.p12"}, + expErr: false, + }, + "if PKCS12 enabled with no password in attribute or secret, expect error": { + meta: metadata.Metadata{}, + attributes: map[string]string{ + "csi.cert-manager.io/pkcs12-enable": "true", + "csi.cert-manager.io/pkcs12-filename": "crt.p12", + }, + pk: root.PK, + chainPEM: root.PEM, + expFiles: []string{}, + expErr: true, + }, } for name, test := range tests { t.Run(name, func(t *testing.T) { files := make(map[string][]byte) - err := Handle(test.attributes, files, test.pk, test.chainPEM) - assert.NoError(t, err) + err := Handle(test.meta, test.attributes, files, test.pk, test.chainPEM) + assert.Equal(t, test.expErr, err != nil, "unexpected error: %v", err) var gotFiles []string for k := range files { From b0d52129fb79313e4955727dd7d52a0e3c5cc6fb Mon Sep 17 00:00:00 2001 From: Mikhail Volkov Date: Fri, 8 May 2026 09:14:42 +0300 Subject: [PATCH 2/6] check condition --- pkg/keystore/pkcs12/pkcs12.go | 12 +++++++++--- pkg/keystore/pkcs12/pkcs12_test.go | 10 +++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/pkg/keystore/pkcs12/pkcs12.go b/pkg/keystore/pkcs12/pkcs12.go index 47977031..f40d5e72 100644 --- a/pkg/keystore/pkcs12/pkcs12.go +++ b/pkg/keystore/pkcs12/pkcs12.go @@ -42,8 +42,14 @@ func Handle(meta metadata.Metadata, attributes map[string]string, files map[stri return nil } - password, ok := meta.Secrets[csiapi.KeyStorePKCS12PasswordSecretKey] - if !ok { + var password string + if meta.Secrets != nil { + var ok bool + password, ok = meta.Secrets[csiapi.KeyStorePKCS12PasswordSecretKey] + if !ok { + return fmt.Errorf("pkcs12 password key %q not found in nodePublishSecretRef", csiapi.KeyStorePKCS12PasswordSecretKey) + } + } else { password = attributes[csiapi.KeyStorePKCS12PasswordKey] } if password == "" { @@ -75,7 +81,7 @@ func create(password string, pk crypto.PrivateKey, chainPEM []byte) ([]byte, err pfx, err := pkcs12.LegacyRC2.Encode(pk, chain[0], chain[1:], password) if err != nil { - return nil, fmt.Errorf("failed to encode the PKCS12 certificate chain file: %v", err) + return nil, fmt.Errorf("failed to encode the PKCS12 certificate chain file: %w", err) } return pfx, nil diff --git a/pkg/keystore/pkcs12/pkcs12_test.go b/pkg/keystore/pkcs12/pkcs12_test.go index eb287964..81114e15 100644 --- a/pkg/keystore/pkcs12/pkcs12_test.go +++ b/pkg/keystore/pkcs12/pkcs12_test.go @@ -77,10 +77,10 @@ func Test_Handle(t *testing.T) { expFiles: []string{"crt.p12"}, expErr: false, }, - "if PKCS12 enabled with password in both, secret takes precedence": { + "if PKCS12 enabled with nodePublishSecretRef but missing password key, expect error": { meta: metadata.Metadata{ Secrets: map[string]string{ - csiapi.KeyStorePKCS12PasswordSecretKey: "secret-password", + "some-other-key": "value", }, }, attributes: map[string]string{ @@ -90,10 +90,10 @@ func Test_Handle(t *testing.T) { }, pk: root.PK, chainPEM: root.PEM, - expFiles: []string{"crt.p12"}, - expErr: false, + expFiles: []string{}, + expErr: true, }, - "if PKCS12 enabled with no password in attribute or secret, expect error": { + "if PKCS12 enabled with no nodePublishSecretRef and no attribute password, expect error": { meta: metadata.Metadata{}, attributes: map[string]string{ "csi.cert-manager.io/pkcs12-enable": "true", From e1e92c9b48406035968351f38a942cec8d7ba569 Mon Sep 17 00:00:00 2001 From: Mikhail Volkov Date: Wed, 13 May 2026 11:11:12 +0300 Subject: [PATCH 3/6] go mod --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index aa787574..d22acaf6 100644 --- a/go.mod +++ b/go.mod @@ -108,4 +108,4 @@ require ( sigs.k8s.io/yaml v1.6.0 // indirect ) -replace github.com/cert-manager/csi-lib => ../csi-lib +replace github.com/cert-manager/csi-lib => github.com/mkvolkov/csi-lib v0.0.0-20260508062254-696a943d355d diff --git a/go.sum b/go.sum index 0804d7b4..ddfb3ccc 100644 --- a/go.sum +++ b/go.sum @@ -124,6 +124,8 @@ github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= +github.com/mkvolkov/csi-lib v0.0.0-20260508062254-696a943d355d h1:3UNnUHXkE9eRjSNDAoeoKEHGCzMjYPVOQYD3lJVX9BM= +github.com/mkvolkov/csi-lib v0.0.0-20260508062254-696a943d355d/go.mod h1:h+f22xjpwtAlXUGKijOwUwN9idclf6rBo+r3dsFX1gI= github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= From c73a35f6a8aaef97d96e74ab1be30845db4212d0 Mon Sep 17 00:00:00 2001 From: Mikhail Volkov Date: Thu, 14 May 2026 15:23:13 +0300 Subject: [PATCH 4/6] correct image, open password priority --- make/00_mod.mk | 7 +++++- pkg/keystore/pkcs12/pkcs12.go | 23 ++++++++------------ pkg/keystore/pkcs12/pkcs12_test.go | 35 ++++++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/make/00_mod.mk b/make/00_mod.mk index 42b26d4f..0c347da1 100644 --- a/make/00_mod.mk +++ b/make/00_mod.mk @@ -22,7 +22,12 @@ build_names := manager go_manager_main_dir := ./cmd go_manager_mod_dir := . go_manager_ldflags := -X $(repo_name)/internal/version.AppVersion=$(VERSION) -X $(repo_name)/internal/version.GitCommit=$(GITCOMMIT) -oci_manager_base_image_flavor := csi-static +# Pin the csi-static base image locally to avoid bot-driven SHA bumps in +# make/_shared/oci-build/00_mod.mk re-introducing a libeconf/util-linux 2.41 +# combo that fails at startup on read-only /etc (mkdir /etc/systemd/system.conf.d). +# Last known-good SHA — same as released in v0.14.0. +oci_manager_base_image_flavor := custom +oci_manager_base_image := quay.io/jetstack/base-static-csi@sha256:e8c56285c3bd5bb98f8c0b3d30c5b28d81c087e333b6f9e3296c2eb51faca47e oci_manager_image_name := quay.io/jetstack/cert-manager-csi-driver oci_manager_image_tag := $(VERSION) oci_manager_image_name_development := cert-manager.local/cert-manager-csi-driver diff --git a/pkg/keystore/pkcs12/pkcs12.go b/pkg/keystore/pkcs12/pkcs12.go index f40d5e72..903c9653 100644 --- a/pkg/keystore/pkcs12/pkcs12.go +++ b/pkg/keystore/pkcs12/pkcs12.go @@ -32,28 +32,23 @@ import ( // If enabled, A PKCS12 keystore file will be encoded and written to the given // file store. // -// The PKCS12 password is resolved in order: -// 1. meta.Secrets[KeyStorePKCS12PasswordSecretKey] (from nodePublishSecretRef) -// 2. attributes[KeyStorePKCS12PasswordKey] (plaintext attribute, kept for -// backward compatibility) +// The PKCS12 password is resolved in order, with later sources used as a +// fallback when an earlier one is absent or empty: +// 1. attributes[KeyStorePKCS12PasswordKey] (plaintext attribute — explicit +// overrides win) +// 2. meta.Secrets[KeyStorePKCS12PasswordSecretKey] (from nodePublishSecretRef) func Handle(meta metadata.Metadata, attributes map[string]string, files map[string][]byte, pk crypto.PrivateKey, chainPEM []byte) error { // If PKCS12 support is not enabled, return early. if attributes[csiapi.KeyStorePKCS12EnableKey] != "true" { return nil } - var password string - if meta.Secrets != nil { - var ok bool - password, ok = meta.Secrets[csiapi.KeyStorePKCS12PasswordSecretKey] - if !ok { - return fmt.Errorf("pkcs12 password key %q not found in nodePublishSecretRef", csiapi.KeyStorePKCS12PasswordSecretKey) - } - } else { - password = attributes[csiapi.KeyStorePKCS12PasswordKey] + password := attributes[csiapi.KeyStorePKCS12PasswordKey] + if password == "" { + password = meta.Secrets[csiapi.KeyStorePKCS12PasswordSecretKey] } if password == "" { - return errors.New("pkcs12 password must be provided via nodePublishSecretRef or the pkcs12-password attribute") + return errors.New("pkcs12 password must be provided via the pkcs12-password attribute or nodePublishSecretRef") } pfx, err := create(password, pk, chainPEM) diff --git a/pkg/keystore/pkcs12/pkcs12_test.go b/pkg/keystore/pkcs12/pkcs12_test.go index 81114e15..0c92c1c5 100644 --- a/pkg/keystore/pkcs12/pkcs12_test.go +++ b/pkg/keystore/pkcs12/pkcs12_test.go @@ -77,10 +77,10 @@ func Test_Handle(t *testing.T) { expFiles: []string{"crt.p12"}, expErr: false, }, - "if PKCS12 enabled with nodePublishSecretRef but missing password key, expect error": { + "if both attribute and secret have a password, the attribute wins": { meta: metadata.Metadata{ Secrets: map[string]string{ - "some-other-key": "value", + csiapi.KeyStorePKCS12PasswordSecretKey: "secret-password", }, }, attributes: map[string]string{ @@ -90,6 +90,37 @@ func Test_Handle(t *testing.T) { }, pk: root.PK, chainPEM: root.PEM, + expFiles: []string{"crt.p12"}, + expErr: false, + }, + "if attribute password is empty, fall back to the secret password": { + meta: metadata.Metadata{ + Secrets: map[string]string{ + csiapi.KeyStorePKCS12PasswordSecretKey: "secret-password", + }, + }, + attributes: map[string]string{ + "csi.cert-manager.io/pkcs12-enable": "true", + "csi.cert-manager.io/pkcs12-password": "", + "csi.cert-manager.io/pkcs12-filename": "crt.p12", + }, + pk: root.PK, + chainPEM: root.PEM, + expFiles: []string{"crt.p12"}, + expErr: false, + }, + "if nodePublishSecretRef lacks the password key and no attribute password is set, expect error": { + meta: metadata.Metadata{ + Secrets: map[string]string{ + "some-other-key": "value", + }, + }, + attributes: map[string]string{ + "csi.cert-manager.io/pkcs12-enable": "true", + "csi.cert-manager.io/pkcs12-filename": "crt.p12", + }, + pk: root.PK, + chainPEM: root.PEM, expFiles: []string{}, expErr: true, }, From 9e822e44d82ed15f462fd2a096bbbe80d72115be Mon Sep 17 00:00:00 2001 From: Mikhail Volkov Date: Fri, 15 May 2026 11:20:42 +0300 Subject: [PATCH 5/6] new image --- make/00_mod.mk | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/make/00_mod.mk b/make/00_mod.mk index 0c347da1..9a45bc61 100644 --- a/make/00_mod.mk +++ b/make/00_mod.mk @@ -22,12 +22,14 @@ build_names := manager go_manager_main_dir := ./cmd go_manager_mod_dir := . go_manager_ldflags := -X $(repo_name)/internal/version.AppVersion=$(VERSION) -X $(repo_name)/internal/version.GitCommit=$(GITCOMMIT) -# Pin the csi-static base image locally to avoid bot-driven SHA bumps in -# make/_shared/oci-build/00_mod.mk re-introducing a libeconf/util-linux 2.41 -# combo that fails at startup on read-only /etc (mkdir /etc/systemd/system.conf.d). -# Last known-good SHA — same as released in v0.14.0. +# Pin the csi-static base image locally. Newer base-static-csi releases +# (Alpine 3.23, libeconf-0.8.3, util-linux-2.41) crash at startup because +# libmount loads libeconf which calls mkdir on /etc/systemd/system.conf.d/ +# and the container's /etc is read-only. This SHA is the base image used +# by upstream v0.13.0 (Alpine 3.21, libeconf-0.6.3, util-linux-2.40.4) — +# verified not to exhibit the bug. oci_manager_base_image_flavor := custom -oci_manager_base_image := quay.io/jetstack/base-static-csi@sha256:e8c56285c3bd5bb98f8c0b3d30c5b28d81c087e333b6f9e3296c2eb51faca47e +oci_manager_base_image := quay.io/jetstack/base-static-csi@sha256:05ec9b9d5798fdd80680a54eab9eb69134d3cdaae948935bb1af07dadeb6e9be oci_manager_image_name := quay.io/jetstack/cert-manager-csi-driver oci_manager_image_tag := $(VERSION) oci_manager_image_name_development := cert-manager.local/cert-manager-csi-driver From 3498c6b2ee753b2b64325bcffb705038b00f83c8 Mon Sep 17 00:00:00 2001 From: Mikhail Volkov Date: Tue, 19 May 2026 16:25:11 +0300 Subject: [PATCH 6/6] parametrized --- pkg/apis/v1alpha1/types.go | 14 +++++++---- pkg/apis/validation/validation.go | 5 ++++ pkg/keystore/pkcs12/pkcs12.go | 10 ++++++-- pkg/keystore/pkcs12/pkcs12_test.go | 38 +++++++++++++++++++++++++++--- 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/pkg/apis/v1alpha1/types.go b/pkg/apis/v1alpha1/types.go index 6fb0131b..40c1cfa4 100644 --- a/pkg/apis/v1alpha1/types.go +++ b/pkg/apis/v1alpha1/types.go @@ -54,10 +54,16 @@ const ( KeyStorePKCS12FileKey = "csi.cert-manager.io/pkcs12-filename" KeyStorePKCS12PasswordKey = "csi.cert-manager.io/pkcs12-password" // #nosec G101: False positive, gosec thinks this is a credential. - // KeyStorePKCS12PasswordSecretKey is the key within the secret referenced by - // nodePublishSecretRef that holds the PKCS12 keystore password. Takes - // precedence over KeyStorePKCS12PasswordKey when both are provided. - KeyStorePKCS12PasswordSecretKey = "pkcs12-password" // #nosec G101 + // KeyStorePKCS12PasswordSecretKeyKey is the volume attribute that selects + // which key within the secret referenced by nodePublishSecretRef holds the + // PKCS12 keystore password. Defaults to DefaultKeyStorePKCS12PasswordSecretKey + // when unset. + KeyStorePKCS12PasswordSecretKeyKey = "csi.cert-manager.io/pkcs12-password-secret-key" // #nosec G101 + + // DefaultKeyStorePKCS12PasswordSecretKey is the default key within the + // secret referenced by nodePublishSecretRef that holds the PKCS12 keystore + // password when KeyStorePKCS12PasswordSecretKeyKey is not set. + DefaultKeyStorePKCS12PasswordSecretKey = "pkcs12-password" // #nosec G101 ) const ( diff --git a/pkg/apis/validation/validation.go b/pkg/apis/validation/validation.go index ece196a6..c0987657 100644 --- a/pkg/apis/validation/validation.go +++ b/pkg/apis/validation/validation.go @@ -261,6 +261,11 @@ func pkcs12Values(path *field.Path, attr map[string]string) field.ErrorList { el = append(el, field.Invalid(path.Child(csiapi.KeyStorePKCS12PasswordKey), password, fmt.Sprintf("cannot use attribute without %q set to %q or %q", csiapi.KeyStorePKCS12EnableKey, "true", "false"))) } + + if secretKey, ok := attr[csiapi.KeyStorePKCS12PasswordSecretKeyKey]; ok { + el = append(el, field.Invalid(path.Child(csiapi.KeyStorePKCS12PasswordSecretKeyKey), secretKey, + fmt.Sprintf("cannot use attribute without %q set to %q or %q", csiapi.KeyStorePKCS12EnableKey, "true", "false"))) + } } if len(el) > 0 { diff --git a/pkg/keystore/pkcs12/pkcs12.go b/pkg/keystore/pkcs12/pkcs12.go index 903c9653..ac116116 100644 --- a/pkg/keystore/pkcs12/pkcs12.go +++ b/pkg/keystore/pkcs12/pkcs12.go @@ -36,7 +36,9 @@ import ( // fallback when an earlier one is absent or empty: // 1. attributes[KeyStorePKCS12PasswordKey] (plaintext attribute — explicit // overrides win) -// 2. meta.Secrets[KeyStorePKCS12PasswordSecretKey] (from nodePublishSecretRef) +// 2. meta.Secrets[] (from nodePublishSecretRef). The key name is taken +// from attributes[KeyStorePKCS12PasswordSecretKeyKey], defaulting to +// DefaultKeyStorePKCS12PasswordSecretKey when unset. func Handle(meta metadata.Metadata, attributes map[string]string, files map[string][]byte, pk crypto.PrivateKey, chainPEM []byte) error { // If PKCS12 support is not enabled, return early. if attributes[csiapi.KeyStorePKCS12EnableKey] != "true" { @@ -45,7 +47,11 @@ func Handle(meta metadata.Metadata, attributes map[string]string, files map[stri password := attributes[csiapi.KeyStorePKCS12PasswordKey] if password == "" { - password = meta.Secrets[csiapi.KeyStorePKCS12PasswordSecretKey] + secretKey := attributes[csiapi.KeyStorePKCS12PasswordSecretKeyKey] + if secretKey == "" { + secretKey = csiapi.DefaultKeyStorePKCS12PasswordSecretKey + } + password = meta.Secrets[secretKey] } if password == "" { return errors.New("pkcs12 password must be provided via the pkcs12-password attribute or nodePublishSecretRef") diff --git a/pkg/keystore/pkcs12/pkcs12_test.go b/pkg/keystore/pkcs12/pkcs12_test.go index 0c92c1c5..532d7b64 100644 --- a/pkg/keystore/pkcs12/pkcs12_test.go +++ b/pkg/keystore/pkcs12/pkcs12_test.go @@ -65,7 +65,7 @@ func Test_Handle(t *testing.T) { "if PKCS12 enabled with password in secret, expect file written": { meta: metadata.Metadata{ Secrets: map[string]string{ - csiapi.KeyStorePKCS12PasswordSecretKey: "secret-password", + csiapi.DefaultKeyStorePKCS12PasswordSecretKey: "secret-password", }, }, attributes: map[string]string{ @@ -80,7 +80,7 @@ func Test_Handle(t *testing.T) { "if both attribute and secret have a password, the attribute wins": { meta: metadata.Metadata{ Secrets: map[string]string{ - csiapi.KeyStorePKCS12PasswordSecretKey: "secret-password", + csiapi.DefaultKeyStorePKCS12PasswordSecretKey: "secret-password", }, }, attributes: map[string]string{ @@ -96,7 +96,7 @@ func Test_Handle(t *testing.T) { "if attribute password is empty, fall back to the secret password": { meta: metadata.Metadata{ Secrets: map[string]string{ - csiapi.KeyStorePKCS12PasswordSecretKey: "secret-password", + csiapi.DefaultKeyStorePKCS12PasswordSecretKey: "secret-password", }, }, attributes: map[string]string{ @@ -124,6 +124,38 @@ func Test_Handle(t *testing.T) { expFiles: []string{}, expErr: true, }, + "if PKCS12 enabled with custom secret key, expect password read from that key": { + meta: metadata.Metadata{ + Secrets: map[string]string{ + "my-custom-key": "secret-password", + }, + }, + attributes: map[string]string{ + "csi.cert-manager.io/pkcs12-enable": "true", + "csi.cert-manager.io/pkcs12-password-secret-key": "my-custom-key", + "csi.cert-manager.io/pkcs12-filename": "crt.p12", + }, + pk: root.PK, + chainPEM: root.PEM, + expFiles: []string{"crt.p12"}, + expErr: false, + }, + "if custom secret key is set but missing from secret, expect error": { + meta: metadata.Metadata{ + Secrets: map[string]string{ + "pkcs12-password": "default-password", + }, + }, + attributes: map[string]string{ + "csi.cert-manager.io/pkcs12-enable": "true", + "csi.cert-manager.io/pkcs12-password-secret-key": "my-custom-key", + "csi.cert-manager.io/pkcs12-filename": "crt.p12", + }, + pk: root.PK, + chainPEM: root.PEM, + expFiles: []string{}, + expErr: true, + }, "if PKCS12 enabled with no nodePublishSecretRef and no attribute password, expect error": { meta: metadata.Metadata{}, attributes: map[string]string{