diff --git a/internal/controller/sync.go b/internal/controller/sync.go index 9fd8fd4..8ade3e2 100644 --- a/internal/controller/sync.go +++ b/internal/controller/sync.go @@ -477,14 +477,14 @@ func (r *RouteController) buildNextCert(ctx context.Context, route *routev1.Rout revisionHistoryLimit = &typedLimit } - secretName := route.Name + "-tls-cert" + secretName := safeKubernetesNameAppend(route.Name, "tls") // Build the Certificate resource with the collected information // https://cert-manager.io/docs/usage/certificate/ cert := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: route.Name + "-", - Namespace: route.Namespace, + Name: safeKubernetesNameAppend(route.Name, "cert"), + Namespace: route.Namespace, OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef( route, diff --git a/internal/controller/sync_test.go b/internal/controller/sync_test.go index 723de44..2591455 100644 --- a/internal/controller/sync_test.go +++ b/internal/controller/sync_test.go @@ -402,6 +402,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { domain := "some-host.some-domain.tld" domainSlice := []string{domain} + routeName := "some-route" + certName := routeName + "-cert" + secretName := routeName + "-tls" + + // see util_test.go for details + reallyLongRouteName := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + reallyLongCertName := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-03aaf5-cert" + reallyLongSecretName := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-03aaf5-tls" + tests := []struct { name string route *routev1.Route @@ -413,7 +422,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "Basic test with duration and hostname", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -440,14 +449,59 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: 42 * time.Minute}, - DNSNames: domainSlice, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: 42 * time.Minute}, + DNSNames: domainSlice, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, + }, + }, + wantErr: nil, + }, + + { + name: "Basic test with long route name", + route: generateRouteStatus(&routev1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: reallyLongRouteName, + Namespace: "some-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "self-signed-issuer", + }, + }, + Spec: routev1.RouteSpec{ + Host: domain, + }, + Status: routev1.RouteStatus{ + Ingress: []routev1.RouteIngress{ + { + Host: domain, + Conditions: []routev1.RouteIngressCondition{ + { + Type: "Admitted", + Status: "True", + }, + }, + }, + }, + }, + }, + true), + want: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: reallyLongCertName, + Namespace: "some-namespace", + }, + Spec: cmapi.CertificateSpec{ + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + DNSNames: domainSlice, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: reallyLongSecretName, }, }, wantErr: nil, @@ -457,7 +511,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "Basic test with issuer name + kind", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -484,14 +538,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - DNSNames: domainSlice, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + DNSNames: domainSlice, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, IssuerRef: cmmeta.ObjectReference{ Name: "self-signed-issuer", @@ -506,7 +561,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "Basic test with issuer name, kind + group", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -534,14 +589,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - DNSNames: domainSlice, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + DNSNames: domainSlice, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, IssuerRef: cmmeta.ObjectReference{ Name: "self-signed-issuer", @@ -557,7 +613,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "Basic test with alternate ingress issuer name annotation", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IngressIssuerNameAnnotationKey: "self-signed-issuer", @@ -585,14 +641,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - DNSNames: domainSlice, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + DNSNames: domainSlice, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, IssuerRef: cmmeta.ObjectReference{ Name: "self-signed-issuer", @@ -608,7 +665,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With subdomain and multiple ICs", route: &routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route-with-subdomain", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -651,13 +708,14 @@ func TestRoute_buildNextCertificate(t *testing.T) { }, want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-with-subdomain-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, - IsCA: false, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + IsCA: false, + SecretName: secretName, DNSNames: []string{ "some-sub-domain.some-domain.tld", @@ -672,7 +730,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With ECDSA private key algorithm annotation", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -699,14 +757,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - IsCA: false, - DNSNames: domainSlice, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + IsCA: false, + DNSNames: domainSlice, + SecretName: secretName, PrivateKey: &cmapi.CertificatePrivateKey{ Algorithm: cmapi.ECDSAKeyAlgorithm, @@ -720,7 +779,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With ECDSA P-384 private key algorithm and size annotation", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -748,14 +807,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - IsCA: false, - DNSNames: domainSlice, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + IsCA: false, + DNSNames: domainSlice, + SecretName: secretName, PrivateKey: &cmapi.CertificatePrivateKey{ Algorithm: cmapi.ECDSAKeyAlgorithm, @@ -770,7 +830,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With ECDSA P-521 private key algorithm and size annotation", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -798,14 +858,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - IsCA: false, - DNSNames: domainSlice, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + IsCA: false, + DNSNames: domainSlice, + SecretName: secretName, PrivateKey: &cmapi.CertificatePrivateKey{ Algorithm: cmapi.ECDSAKeyAlgorithm, @@ -820,7 +881,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With RSA private key algorithm annotation", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -847,14 +908,16 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - IsCA: false, - DNSNames: domainSlice, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + IsCA: false, + DNSNames: domainSlice, + SecretName: secretName, + PrivateKey: &cmapi.CertificatePrivateKey{ Algorithm: cmapi.RSAKeyAlgorithm, }, @@ -867,7 +930,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With RSA 3072 private key algorithm and size annotation", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -895,14 +958,16 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - IsCA: false, - DNSNames: domainSlice, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + IsCA: false, + DNSNames: domainSlice, + SecretName: secretName, + PrivateKey: &cmapi.CertificatePrivateKey{ Algorithm: cmapi.RSAKeyAlgorithm, Size: 3072, @@ -916,7 +981,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With Ed25519 private key algorithm and size annotation", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -943,14 +1008,16 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - IsCA: false, - DNSNames: domainSlice, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + IsCA: false, + DNSNames: domainSlice, + SecretName: secretName, + PrivateKey: &cmapi.CertificatePrivateKey{ Algorithm: cmapi.Ed25519KeyAlgorithm, }, @@ -963,7 +1030,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With subject annotations", route: &routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route-with-subject-annotations", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -997,14 +1064,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { }, want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-with-subject-annotations-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, - IsCA: false, - DNSNames: domainSlice, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + IsCA: false, + DNSNames: domainSlice, + SecretName: secretName, Subject: &cmapi.X509Subject{ Organizations: []string{"Company 1", "Company 2"}, @@ -1025,7 +1093,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With custom URI SAN", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1052,14 +1120,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - DNSNames: domainSlice, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + DNSNames: domainSlice, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, URIs: []string{"spiffe://example.com/myuri"}, }, @@ -1071,7 +1140,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With extra DNS names", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1098,13 +1167,14 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, DNSNames: []string{domain, "example.com", "another.example.com"}, }, @@ -1116,7 +1186,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With custom IPv4 address", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1143,14 +1213,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - DNSNames: domainSlice, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + DNSNames: domainSlice, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, IPAddresses: []string{"169.50.50.50"}, }, @@ -1162,7 +1233,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With custom IPv6 address", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1189,14 +1260,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - DNSNames: domainSlice, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + DNSNames: domainSlice, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, IPAddresses: []string{"2a02:ec80:300:ed1a::1"}, }, @@ -1208,7 +1280,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With custom mixed IP addresses", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1235,14 +1307,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - DNSNames: domainSlice, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + DNSNames: domainSlice, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, IPAddresses: []string{"169.50.50.50", "2a02:ec80:300:ed1a::1", "192.168.0.1"}, }, @@ -1254,7 +1327,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With custom emails", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1281,14 +1354,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - DNSNames: domainSlice, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + DNSNames: domainSlice, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, EmailAddresses: []string{"test@example.com", "hello@example.com"}, }, @@ -1300,7 +1374,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With all SAN fields", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1331,13 +1405,14 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, DNSNames: []string{domain, "example.com", "another.example.com"}, URIs: []string{"spiffe://example.com/myuri"}, @@ -1352,7 +1427,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "With custom renewBefore", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1379,14 +1454,15 @@ func TestRoute_buildNextCertificate(t *testing.T) { true), want: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "some-route-", - Namespace: "some-namespace", + Name: certName, + Namespace: "some-namespace", }, Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, - DNSNames: domainSlice, - IsCA: false, - Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + Duration: &metav1.Duration{Duration: DefaultCertificateDuration}, + DNSNames: domainSlice, + IsCA: false, + Usages: []cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment}, + SecretName: secretName, RenewBefore: &metav1.Duration{Duration: 30 * time.Minute}, }, @@ -1398,7 +1474,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "missing issuer-name is an error", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.RenewBeforeAnnotationKey: "30m", @@ -1430,7 +1506,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "invalid duration is an error", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1463,7 +1539,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "invalid renew-before is an error", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1496,7 +1572,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "invalid private key size is an error", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1529,7 +1605,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { name: "invalid revision history limit is an error", route: generateRouteStatus(&routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "some-route", + Name: routeName, Namespace: "some-namespace", Annotations: map[string]string{ cmapi.IssuerNameAnnotationKey: "self-signed-issuer", @@ -1595,6 +1671,7 @@ func TestRoute_buildNextCertificate(t *testing.T) { assert.Equal(t, tt.want.Spec.EmailAddresses, cert.Spec.EmailAddresses) assert.Equal(t, tt.want.Spec.IPAddresses, cert.Spec.IPAddresses) assert.Equal(t, tt.want.Spec.URIs, cert.Spec.URIs) + assert.Equal(t, tt.want.Spec.SecretName, cert.Spec.SecretName) if tt.want.Spec.PrivateKey != nil { assert.Equal(t, tt.want.Spec.PrivateKey, cert.Spec.PrivateKey) diff --git a/internal/controller/util.go b/internal/controller/util.go new file mode 100644 index 0000000..3b2957e --- /dev/null +++ b/internal/controller/util.go @@ -0,0 +1,50 @@ +/* +Copyright 2024 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 controller + +import ( + "crypto/sha256" + "encoding/hex" + "strings" +) + +const ( + maxKubernetesResourceNameLength = 253 +) + +func safeKubernetesNameAppend(name string, suffix string) string { + dumbAppend := strings.Join([]string{name, suffix}, "-") + if len(dumbAppend) < maxKubernetesResourceNameLength { + // if simply appending the suffix isn't too long, just do that + return dumbAppend + } + + // We're going to need to remove some of the end of `name` to be able to append `suffix` + // Take a hash of the full name and add it between `name` and `suffix` so that we don't + // risk collisions for long names that only differ in the final few characters + h := sha256.Sum256([]byte(name)) + + hashStr := hex.EncodeToString(h[:])[:6] + + // We'll have the form -- + // Hash is 6 chars long (because we take the last 6 for hashStr below) + // Suffix is len(suffix) charts long + // There are two chars for "-" joining characters + name = name[:len(name)-2-6-len(suffix)] + + return strings.Join([]string{name, hashStr, suffix}, "-") +} diff --git a/internal/controller/util_test.go b/internal/controller/util_test.go new file mode 100644 index 0000000..84050b6 --- /dev/null +++ b/internal/controller/util_test.go @@ -0,0 +1,58 @@ +/* +Copyright 2024 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 controller + +import "testing" + +// For reference below: +// $ echo -n "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" | sha256sum +// 03aaf5773717feae6f704bf2637ae0a9af8b1b26c3493ef29553818378773a04 - + +// $ echo -n "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" | sha256sum +// 9124f8b01de4e3e64e86f1f98309adf6a4cb474aacd78e5f9b7247bbb08a5c20 - + +func Test_safeKubernetesNameAppend(t *testing.T) { + tests := map[string]struct { + name string + expected string + }{ + "short name uses dumb approach": { + name: "short", + expected: "short-test", + }, + "long name has a unique hash and suffix appended": { + // 252 "a" characters + name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + expected: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-03aaf5-test", + }, + "long name with different end has a unique hash and suffix appended": { + // 251 "a" characters followed by a "b" + name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", + expected: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-9124f8-test", + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + out := safeKubernetesNameAppend(test.name, "test") + + if test.expected != out { + t.Errorf("wanted %s\n got %s", test.expected, out) + } + }) + } +}