diff --git a/README.md b/README.md index c368c5f..44d5fe5 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ metadata: cert-manager.io/alt-names: "mycooldomain.com,mysecondarydomain.com" # Optional, no default cert-manager.io/ip-sans: "10.20.30.40,192.168.192.168" # Optional, no default cert-manager.io/uri-sans: "spiffe://trustdomain/workload" # Optional, no default + cert-manager.io/private-key-algorithm: "ECDSA" # Optional, defaults to RSA spec: host: app.service.clustername.domain.com # will be added to the Subject Alternative Names of the CertificateRequest port: diff --git a/internal/controller/sync.go b/internal/controller/sync.go index aae6373..b1e90c5 100644 --- a/internal/controller/sync.go +++ b/internal/controller/sync.go @@ -193,15 +193,29 @@ func (r *Route) hasNextPrivateKey(route *routev1.Route) bool { } func (r *Route) generateNextPrivateKey(ctx context.Context, route *routev1.Route) error { - // TODO: different kinds of key - key, err := utilpki.GenerateRSAPrivateKey(utilpki.MinRSAKeySize) - if err != nil { - return fmt.Errorf("could not generate RSA Key: %w", err) + privateKeyAlgorithm, found := route.Annotations[cmapi.PrivateKeyAlgorithmAnnotationKey] + if !found { + privateKeyAlgorithm = string(cmapi.RSAKeyAlgorithm) } - - encodedKey, err := utilpki.EncodePrivateKey(key, cmapi.PKCS1) + var privateKey crypto.PrivateKey + var err error + switch privateKeyAlgorithm { + case string(cmapi.ECDSAKeyAlgorithm): + privateKey, err = utilpki.GenerateECPrivateKey(utilpki.ECCurve256) + if err != nil { + return fmt.Errorf("could not generate ECDSA key: %w", err) + } + case string(cmapi.RSAKeyAlgorithm): + privateKey, err = utilpki.GenerateRSAPrivateKey(utilpki.MinRSAKeySize) + if err != nil { + return fmt.Errorf("could not generate RSA Key: %w", err) + } + default: + return fmt.Errorf("invalid private key algorithm: %s", privateKeyAlgorithm) + } + encodedKey, err := utilpki.EncodePrivateKey(privateKey, cmapi.PrivateKeyEncoding(cmapi.PKCS1)) if err != nil { - return fmt.Errorf("could not encode RSA Key: %w", err) + return fmt.Errorf("could not encode %s key: %w", privateKeyAlgorithm, err) } route.Annotations[cmapi.IsNextPrivateKeySecretLabelKey] = string(encodedKey) _, err = r.routeClient.RouteV1().Routes(route.Namespace).Update(ctx, route, metav1.UpdateOptions{}) @@ -326,12 +340,32 @@ func (r *Route) createNextCR(ctx context.Context, route *routev1.Route, revision } } + privateKeyAlgorithm, found := route.Annotations[cmapi.PrivateKeyAlgorithmAnnotationKey] + if !found { + privateKeyAlgorithm = string(cmapi.RSAKeyAlgorithm) + } + + var signatureAlgorithm x509.SignatureAlgorithm + var publicKeyAlgorithm x509.PublicKeyAlgorithm + switch privateKeyAlgorithm { + case string(cmapi.ECDSAKeyAlgorithm): + signatureAlgorithm = x509.ECDSAWithSHA256 + publicKeyAlgorithm = x509.ECDSA + + case string(cmapi.RSAKeyAlgorithm): + signatureAlgorithm = x509.SHA256WithRSA + publicKeyAlgorithm = x509.RSA + + default: + return fmt.Errorf("invalid private key algorithm, %s", privateKeyAlgorithm) + } + csr, err := x509.CreateCertificateRequest( rand.Reader, &x509.CertificateRequest{ Version: 0, - SignatureAlgorithm: x509.SHA256WithRSA, - PublicKeyAlgorithm: x509.RSA, + SignatureAlgorithm: signatureAlgorithm, + PublicKeyAlgorithm: publicKeyAlgorithm, Subject: pkix.Name{ CommonName: route.Annotations[cmapi.CommonNameAnnotationKey], }, diff --git a/internal/controller/sync_test.go b/internal/controller/sync_test.go index b9d8d04..c3c7662 100644 --- a/internal/controller/sync_test.go +++ b/internal/controller/sync_test.go @@ -442,6 +442,44 @@ func TestRoute_generateNextPrivateKey(t *testing.T) { want: nil, wantedEvents: []string{"Normal Issuing Generated Private Key for route"}, }, + { + name: "rsa annotated route has no private key", + route: &routev1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-route", + Namespace: "some-namespace", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-time.Hour * 24 * 30)}, + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "some-issuer", + cmapi.PrivateKeyAlgorithmAnnotationKey: "RSA", + }, + }, + Spec: routev1.RouteSpec{ + Host: "some-host.some-domain.tld", + }, + }, + want: nil, + wantedEvents: []string{"Normal Issuing Generated Private Key for route"}, + }, + { + name: "ecdsa annotated route has no private key", + route: &routev1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-route", + Namespace: "some-namespace", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-time.Hour * 24 * 30)}, + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "some-issuer", + cmapi.PrivateKeyAlgorithmAnnotationKey: "ECDSA", + }, + }, + Spec: routev1.RouteSpec{ + Host: "some-host.some-domain.tld", + }, + }, + want: nil, + wantedEvents: []string{"Normal Issuing Generated Private Key for route"}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -467,6 +505,14 @@ func TestRoute_generateNextPrivateKey(t *testing.T) { assert.NoError(t, err) _, err = utilpki.DecodePrivateKeyBytes([]byte(actualRoute.Annotations[cmapi.IsNextPrivateKeySecretLabelKey])) assert.NoError(t, err) + switch tt.route.Annotations[cmapi.PrivateKeyAlgorithmAnnotationKey] { + case string(cmapi.RSAKeyAlgorithm), "": + assert.Contains(t, actualRoute.Annotations[cmapi.IsNextPrivateKeySecretLabelKey], "BEGIN RSA PRIVATE KEY") + case string(cmapi.ECDSAKeyAlgorithm): + assert.Contains(t, actualRoute.Annotations[cmapi.IsNextPrivateKeySecretLabelKey], "BEGIN EC PRIVATE KEY") + default: + t.Errorf("Failing %v", tt.route.Annotations[cmapi.PrivateKeyAlgorithmAnnotationKey]) + } }) } }