diff --git a/internal/controller/sync.go b/internal/controller/sync.go index 092e6f2..431d720 100644 --- a/internal/controller/sync.go +++ b/internal/controller/sync.go @@ -44,6 +44,7 @@ const ( ReasonIssuing = `Issuing` ReasonInvalidKey = `InvalidKey` ReasonInvalidPrivateKeyAlgorithm = `InvalidPrivateKeyAlgorithm` + ReasonInvalidPrivateKeySize = `InvalidPrivateKeySize` ReasonInvalidValue = `InvalidValue` ReasonInternalReconcileError = `InternalReconcileError` ReasonMissingHostname = `MissingHostname` @@ -219,16 +220,35 @@ func (r *Route) generateNextPrivateKey(ctx context.Context, route *routev1.Route if !found { privateKeyAlgorithm = string(cmapi.RSAKeyAlgorithm) } + + var privateKeySize int + privateKeySizeStr, found := route.Annotations[cmapi.PrivateKeySizeAnnotationKey] + if found { + var err error + privateKeySize, err = strconv.Atoi(privateKeySizeStr) + if err != nil { + r.eventRecorder.Event(route, corev1.EventTypeWarning, ReasonInvalidPrivateKeySize, "invalid private key size:"+privateKeySizeStr) + return fmt.Errorf("invalid private key size, %s: %v", privateKeySizeStr, err) + } + } else { + switch privateKeyAlgorithm { + case string(cmapi.ECDSAKeyAlgorithm): + privateKeySize = utilpki.ECCurve256 + case string(cmapi.RSAKeyAlgorithm): + privateKeySize = utilpki.MinRSAKeySize + } + } + var privateKey crypto.PrivateKey var err error switch privateKeyAlgorithm { case string(cmapi.ECDSAKeyAlgorithm): - privateKey, err = utilpki.GenerateECPrivateKey(utilpki.ECCurve256) + privateKey, err = utilpki.GenerateECPrivateKey(privateKeySize) if err != nil { return fmt.Errorf("could not generate ECDSA key: %w", err) } case string(cmapi.RSAKeyAlgorithm): - privateKey, err = utilpki.GenerateRSAPrivateKey(utilpki.MinRSAKeySize) + privateKey, err = utilpki.GenerateRSAPrivateKey(privateKeySize) if err != nil { return fmt.Errorf("could not generate RSA Key: %w", err) } @@ -374,15 +394,42 @@ func (r *Route) buildNextCR(ctx context.Context, route *routev1.Route, revision privateKeyAlgorithm = string(cmapi.RSAKeyAlgorithm) } + var privateKeySize int + privateKeySizeStr, found := route.Annotations[cmapi.PrivateKeySizeAnnotationKey] + if found { + privateKeySize, err = strconv.Atoi(privateKeySizeStr) + if err != nil { + r.eventRecorder.Event(route, corev1.EventTypeWarning, ReasonInvalidPrivateKeySize, "invalid private key size:"+privateKeySizeStr) + return nil, fmt.Errorf("invalid private key size, %s: %v", privateKeySizeStr, err) + } + } + var signatureAlgorithm x509.SignatureAlgorithm var publicKeyAlgorithm x509.PublicKeyAlgorithm switch privateKeyAlgorithm { case string(cmapi.ECDSAKeyAlgorithm): - signatureAlgorithm = x509.ECDSAWithSHA256 + switch privateKeySize { + case 521: + signatureAlgorithm = x509.ECDSAWithSHA512 + case 384: + signatureAlgorithm = x509.ECDSAWithSHA384 + case 256: + signatureAlgorithm = x509.ECDSAWithSHA256 + default: + signatureAlgorithm = x509.ECDSAWithSHA256 + } publicKeyAlgorithm = x509.ECDSA - case string(cmapi.RSAKeyAlgorithm): - signatureAlgorithm = x509.SHA256WithRSA + switch { + case privateKeySize >= 4096: + signatureAlgorithm = x509.SHA512WithRSA + case privateKeySize >= 3072: + signatureAlgorithm = x509.SHA384WithRSA + case privateKeySize >= 2048: + signatureAlgorithm = x509.SHA256WithRSA + default: + signatureAlgorithm = x509.SHA256WithRSA + } publicKeyAlgorithm = x509.RSA default: diff --git a/internal/controller/sync_test.go b/internal/controller/sync_test.go index 5ab9156..6c4b311 100644 --- a/internal/controller/sync_test.go +++ b/internal/controller/sync_test.go @@ -30,6 +30,7 @@ import ( "net" "net/url" "sort" + "strconv" "testing" "time" @@ -856,6 +857,118 @@ func TestRoute_buildNextCR(t *testing.T) { }, wantErr: nil, }, + { + name: "With ECDSA 384 private key algorithm and size annotation", + revision: 1337, + route: generateRouteStatus(&routev1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-route", + Namespace: "some-namespace", + Annotations: map[string]string{ + cmapi.IsNextPrivateKeySecretLabelKey: string(ecdsaPEM), + cmapi.PrivateKeyAlgorithmAnnotationKey: string(cmapi.ECDSAKeyAlgorithm), + cmapi.PrivateKeySizeAnnotationKey: strconv.Itoa(384), + }, + }, + Spec: routev1.RouteSpec{ + Host: "some-host.some-domain.tld", + }, + Status: routev1.RouteStatus{ + Ingress: []routev1.RouteIngress{ + { + Host: "some-host.some-domain.tld", + Conditions: []routev1.RouteIngressCondition{ + { + Type: "Admitted", + Status: "True", + }, + }, + }, + }, + }, + }, + true), + want: &cmapi.CertificateRequest{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "some-route-", + Namespace: "some-namespace", + Annotations: map[string]string{ + cmapi.CertificateRequestRevisionAnnotationKey: "1338", + }, + }, + Spec: cmapi.CertificateRequestSpec{ + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + }, + }, + wantCSR: &x509.CertificateRequest{ + SignatureAlgorithm: x509.ECDSAWithSHA256, + PublicKeyAlgorithm: x509.ECDSA, + Subject: pkix.Name{ + CommonName: "", + }, + DNSNames: []string{"some-host.some-domain.tld"}, + IPAddresses: []net.IP(nil), + URIs: []*url.URL(nil), + }, + wantErr: nil, + }, + { + name: "With ECDSA 521 private key algorithm and size annotation", + revision: 1337, + route: generateRouteStatus(&routev1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-route", + Namespace: "some-namespace", + Annotations: map[string]string{ + cmapi.IsNextPrivateKeySecretLabelKey: string(ecdsaPEM), + cmapi.PrivateKeyAlgorithmAnnotationKey: string(cmapi.ECDSAKeyAlgorithm), + cmapi.PrivateKeySizeAnnotationKey: strconv.Itoa(521), + }, + }, + Spec: routev1.RouteSpec{ + Host: "some-host.some-domain.tld", + }, + Status: routev1.RouteStatus{ + Ingress: []routev1.RouteIngress{ + { + Host: "some-host.some-domain.tld", + Conditions: []routev1.RouteIngressCondition{ + { + Type: "Admitted", + Status: "True", + }, + }, + }, + }, + }, + }, + true), + want: &cmapi.CertificateRequest{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "some-route-", + Namespace: "some-namespace", + Annotations: map[string]string{ + cmapi.CertificateRequestRevisionAnnotationKey: "1338", + }, + }, + Spec: cmapi.CertificateRequestSpec{ + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + }, + }, + wantCSR: &x509.CertificateRequest{ + SignatureAlgorithm: x509.ECDSAWithSHA256, + PublicKeyAlgorithm: x509.ECDSA, + Subject: pkix.Name{ + CommonName: "", + }, + DNSNames: []string{"some-host.some-domain.tld"}, + IPAddresses: []net.IP(nil), + URIs: []*url.URL(nil), + }, + wantErr: nil, + }, { name: "With RSA private key algorithm annotation", revision: 1337, @@ -911,6 +1024,118 @@ func TestRoute_buildNextCR(t *testing.T) { }, wantErr: nil, }, + { + name: "With RSA 3072 private key algorithm and size annotation", + revision: 1337, + route: generateRouteStatus(&routev1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-route", + Namespace: "some-namespace", + Annotations: map[string]string{ + cmapi.IsNextPrivateKeySecretLabelKey: string(rsaPEM), + cmapi.PrivateKeyAlgorithmAnnotationKey: string(cmapi.RSAKeyAlgorithm), + cmapi.PrivateKeySizeAnnotationKey: strconv.Itoa(3072), + }, + }, + Spec: routev1.RouteSpec{ + Host: "some-host.some-domain.tld", + }, + Status: routev1.RouteStatus{ + Ingress: []routev1.RouteIngress{ + { + Host: "some-host.some-domain.tld", + Conditions: []routev1.RouteIngressCondition{ + { + Type: "Admitted", + Status: "True", + }, + }, + }, + }, + }, + }, + true), + want: &cmapi.CertificateRequest{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "some-route-", + Namespace: "some-namespace", + Annotations: map[string]string{ + cmapi.CertificateRequestRevisionAnnotationKey: "1338", + }, + }, + Spec: cmapi.CertificateRequestSpec{ + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + }, + }, + wantCSR: &x509.CertificateRequest{ + SignatureAlgorithm: x509.SHA384WithRSA, + PublicKeyAlgorithm: x509.RSA, + Subject: pkix.Name{ + CommonName: "", + }, + DNSNames: []string{"some-host.some-domain.tld"}, + IPAddresses: []net.IP(nil), + URIs: []*url.URL(nil), + }, + wantErr: nil, + }, + { + name: "With RSA 3072 private key algorithm and size annotation", + revision: 1337, + route: generateRouteStatus(&routev1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-route", + Namespace: "some-namespace", + Annotations: map[string]string{ + cmapi.IsNextPrivateKeySecretLabelKey: string(rsaPEM), + cmapi.PrivateKeyAlgorithmAnnotationKey: string(cmapi.RSAKeyAlgorithm), + cmapi.PrivateKeySizeAnnotationKey: strconv.Itoa(4096), + }, + }, + Spec: routev1.RouteSpec{ + Host: "some-host.some-domain.tld", + }, + Status: routev1.RouteStatus{ + Ingress: []routev1.RouteIngress{ + { + Host: "some-host.some-domain.tld", + Conditions: []routev1.RouteIngressCondition{ + { + Type: "Admitted", + Status: "True", + }, + }, + }, + }, + }, + }, + true), + want: &cmapi.CertificateRequest{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "some-route-", + Namespace: "some-namespace", + Annotations: map[string]string{ + cmapi.CertificateRequestRevisionAnnotationKey: "1338", + }, + }, + Spec: cmapi.CertificateRequestSpec{ + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + }, + }, + wantCSR: &x509.CertificateRequest{ + SignatureAlgorithm: x509.SHA512WithRSA, + PublicKeyAlgorithm: x509.RSA, + Subject: pkix.Name{ + CommonName: "", + }, + DNSNames: []string{"some-host.some-domain.tld"}, + IPAddresses: []net.IP(nil), + URIs: []*url.URL(nil), + }, + wantErr: nil, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {