diff --git a/config/crds/v1/all-crds.yaml b/config/crds/v1/all-crds.yaml index 83cb42b6910..f1da4e69f29 100644 --- a/config/crds/v1/all-crds.yaml +++ b/config/crds/v1/all-crds.yaml @@ -4399,6 +4399,15 @@ spec: description: SecretName is the name of the secret. type: string type: object + otherNameSuffix: + description: 'OtherNameSuffix when defined will be prefixed + with the Pod name and used as the common name, and the first + DNSName, as well as an OtherName required by Elasticsearch + in the Subject Alternative Name extension of each Elasticsearch + node''s transport TLS certificate. Example: if set to "node.cluster.local", + the generated certificate will have its otherName set to + ".node.cluster.local".' + type: string subjectAltNames: description: SubjectAlternativeNames is a list of SANs to include in the generated node transport TLS certificates. diff --git a/config/crds/v1/bases/elasticsearch.k8s.elastic.co_elasticsearches.yaml b/config/crds/v1/bases/elasticsearch.k8s.elastic.co_elasticsearches.yaml index e2d50bcc721..a2664c5be7f 100644 --- a/config/crds/v1/bases/elasticsearch.k8s.elastic.co_elasticsearches.yaml +++ b/config/crds/v1/bases/elasticsearch.k8s.elastic.co_elasticsearches.yaml @@ -8808,6 +8808,15 @@ spec: description: SecretName is the name of the secret. type: string type: object + otherNameSuffix: + description: 'OtherNameSuffix when defined will be prefixed + with the Pod name and used as the common name, and the first + DNSName, as well as an OtherName required by Elasticsearch + in the Subject Alternative Name extension of each Elasticsearch + node''s transport TLS certificate. Example: if set to "node.cluster.local", + the generated certificate will have its otherName set to + ".node.cluster.local".' + type: string subjectAltNames: description: SubjectAlternativeNames is a list of SANs to include in the generated node transport TLS certificates. diff --git a/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml b/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml index 676f4b4c29d..d0ae622f629 100644 --- a/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml +++ b/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml @@ -4429,6 +4429,15 @@ spec: description: SecretName is the name of the secret. type: string type: object + otherNameSuffix: + description: 'OtherNameSuffix when defined will be prefixed + with the Pod name and used as the common name, and the first + DNSName, as well as an OtherName required by Elasticsearch + in the Subject Alternative Name extension of each Elasticsearch + node''s transport TLS certificate. Example: if set to "node.cluster.local", + the generated certificate will have its otherName set to + ".node.cluster.local".' + type: string subjectAltNames: description: SubjectAlternativeNames is a list of SANs to include in the generated node transport TLS certificates. diff --git a/docs/reference/api-docs.asciidoc b/docs/reference/api-docs.asciidoc index c8cc5e0a5e5..03c62761d6b 100644 --- a/docs/reference/api-docs.asciidoc +++ b/docs/reference/api-docs.asciidoc @@ -1092,6 +1092,7 @@ TransportConfig holds the transport layer settings for Elasticsearch. [cols="25a,75a", options="header"] |=== | Field | Description +| *`otherNameSuffix`* __string__ | OtherNameSuffix when defined will be prefixed with the Pod name and used as the common name, and the first DNSName, as well as an OtherName required by Elasticsearch in the Subject Alternative Name extension of each Elasticsearch node's transport TLS certificate. Example: if set to "node.cluster.local", the generated certificate will have its otherName set to ".node.cluster.local". | *`subjectAltNames`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-subjectalternativename[$$SubjectAlternativeName$$]__ | SubjectAlternativeNames is a list of SANs to include in the generated node transport TLS certificates. | *`certificate`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-secretref[$$SecretRef$$]__ | Certificate is a reference to a Kubernetes secret that contains the CA certificate and private key for generating node certificates. The referenced secret should contain the following: - `ca.crt`: The CA certificate in PEM format. - `ca.key`: The private key for the CA certificate in PEM format. diff --git a/pkg/apis/elasticsearch/v1/elasticsearch_types.go b/pkg/apis/elasticsearch/v1/elasticsearch_types.go index be112cf30b9..3e5b69f7f52 100644 --- a/pkg/apis/elasticsearch/v1/elasticsearch_types.go +++ b/pkg/apis/elasticsearch/v1/elasticsearch_types.go @@ -148,6 +148,11 @@ type TransportConfig struct { } type TransportTLSOptions struct { + // OtherNameSuffix when defined will be prefixed with the Pod name and used as the common name, + // and the first DNSName, as well as an OtherName required by Elasticsearch in the Subject Alternative Name + // extension of each Elasticsearch node's transport TLS certificate. + // Example: if set to "node.cluster.local", the generated certificate will have its otherName set to ".node.cluster.local". + OtherNameSuffix string `json:"otherNameSuffix,omitempty"` // SubjectAlternativeNames is a list of SANs to include in the generated node transport TLS certificates. SubjectAlternativeNames []commonv1.SubjectAlternativeName `json:"subjectAltNames,omitempty"` // Certificate is a reference to a Kubernetes secret that contains the CA certificate diff --git a/pkg/controller/elasticsearch/certificates/transport/csr.go b/pkg/controller/elasticsearch/certificates/transport/csr.go index ea9e01a2699..83fbc4184db 100644 --- a/pkg/controller/elasticsearch/certificates/transport/csr.go +++ b/pkg/controller/elasticsearch/certificates/transport/csr.go @@ -41,7 +41,7 @@ func createValidatedCertificateTemplate( // TODO: csr signature is not checked certificateTemplate := certificates.ValidatedCertificateTemplate(x509.Certificate{ Subject: pkix.Name{ - CommonName: buildCertificateCommonName(pod, cluster.Name, cluster.Namespace), + CommonName: buildCertificateCommonName(pod, cluster), OrganizationalUnit: []string{cluster.Name}, }, @@ -76,7 +76,7 @@ func buildGeneralNames( ssetName := pod.Labels[label.StatefulSetNameLabelName] svcName := nodespec.HeadlessServiceName(ssetName) - commonName := buildCertificateCommonName(pod, cluster.Name, cluster.Namespace) + commonName := buildCertificateCommonName(pod, cluster) commonNameUTF8OtherName := &certificates.UTF8StringValuedOtherName{ OID: certificates.CommonNameObjectIdentifier, @@ -112,7 +112,13 @@ func buildGeneralNames( return generalNames, nil } -// buildCertificateCommonName returns the CN (and ES othername) entry for a given pod within a stack -func buildCertificateCommonName(pod corev1.Pod, clusterName, namespace string) string { - return fmt.Sprintf("%s.node.%s.%s.es.local", pod.Name, clusterName, namespace) +// buildCertificateCommonName returns the CN (and ES otherName) entry for a given Elasticsearch Pod. +// If the user provided an otherName suffix in the spec, it prepends the pod name to it (..node..es.local. +func buildCertificateCommonName(pod corev1.Pod, es esv1.Elasticsearch) string { + userConfiguredSuffix := es.Spec.Transport.TLS.OtherNameSuffix + if userConfiguredSuffix == "" { + return fmt.Sprintf("%s.node.%s.%s.es.local", pod.Name, es.Name, es.Namespace) + } + return fmt.Sprintf("%s.%s", pod.Name, userConfiguredSuffix) } diff --git a/pkg/controller/elasticsearch/certificates/transport/csr_test.go b/pkg/controller/elasticsearch/certificates/transport/csr_test.go index 47da0447e89..023cec8677f 100644 --- a/pkg/controller/elasticsearch/certificates/transport/csr_test.go +++ b/pkg/controller/elasticsearch/certificates/transport/csr_test.go @@ -67,18 +67,22 @@ func Test_createValidatedCertificateTemplate(t *testing.T) { func Test_buildGeneralNames(t *testing.T) { expectedCommonName := "test-pod-name.node.test-es-name.test-namespace.es.local" expectedTransportSvcName := "test-es-name-es-transport.test-namespace.svc" - otherName, err := (&certificates.UTF8StringValuedOtherName{ - OID: certificates.CommonNameObjectIdentifier, - Value: expectedCommonName, - }).ToOtherName() - require.NoError(t, err) + + mkOtherName := func(name string) certificates.OtherName { + otherName, err := (&certificates.UTF8StringValuedOtherName{ + OID: certificates.CommonNameObjectIdentifier, + Value: name, + }).ToOtherName() + require.NoError(t, err) + return *otherName + } type args struct { cluster esv1.Elasticsearch pod corev1.Pod } expectedGeneralNames := []certificates.GeneralName{ - {OtherName: *otherName}, + {OtherName: mkOtherName(expectedCommonName)}, {DNSName: expectedCommonName}, {DNSName: expectedTransportSvcName}, {DNSName: "test-pod-name.test-sset"}, @@ -176,6 +180,56 @@ func Test_buildGeneralNames(t *testing.T) { {DNSName: "my-custom-domain"}, }...), }, + { + name: "custom name suffix", + args: args{ + cluster: func() esv1.Elasticsearch { + es := testES + es.Spec.Transport.TLS.OtherNameSuffix = "user.provided.suffix" + return es + }(), + pod: testPod, + }, + want: func() []certificates.GeneralName { + expectedCommonName := "test-pod-name.user.provided.suffix" + return []certificates.GeneralName{ + {OtherName: mkOtherName(expectedCommonName)}, + {DNSName: expectedCommonName}, + {DNSName: expectedTransportSvcName}, + {DNSName: "test-pod-name.test-sset"}, + {IPAddress: net.ParseIP(testIP).To4()}, + {IPAddress: net.ParseIP("127.0.0.1").To4()}, + } + }(), + }, + { + name: "custom name suffix with additional SANs", + args: args{ + cluster: func() esv1.Elasticsearch { + es := testES + es.Spec.Transport.TLS.OtherNameSuffix = "user.provided.suffix" + es.Spec.Transport.TLS.SubjectAlternativeNames = []commonv1.SubjectAlternativeName{ + { + DNS: "my-custom-domain", + }, + } + return es + }(), + pod: testPod, + }, + want: func() []certificates.GeneralName { + expectedCommonName := "test-pod-name.user.provided.suffix" + return []certificates.GeneralName{ + {OtherName: mkOtherName(expectedCommonName)}, + {DNSName: expectedCommonName}, + {DNSName: expectedTransportSvcName}, + {DNSName: "test-pod-name.test-sset"}, + {IPAddress: net.ParseIP(testIP).To4()}, + {IPAddress: net.ParseIP("127.0.0.1").To4()}, + {DNSName: "my-custom-domain"}, + } + }(), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/controller/elasticsearch/certificates/transport/pod_secret.go b/pkg/controller/elasticsearch/certificates/transport/pod_secret.go index d5c0114d0c0..55495794b27 100644 --- a/pkg/controller/elasticsearch/certificates/transport/pod_secret.go +++ b/pkg/controller/elasticsearch/certificates/transport/pod_secret.go @@ -108,7 +108,7 @@ func shouldIssueNewCertificate( ca *certificates.CA, certReconcileBefore time.Duration, ) bool { - certCommonName := buildCertificateCommonName(pod, es.Name, es.Namespace) + certCommonName := buildCertificateCommonName(pod, es) generalNames, err := buildGeneralNames(es, pod) if err != nil { diff --git a/pkg/controller/elasticsearch/certificates/transport/reconcile.go b/pkg/controller/elasticsearch/certificates/transport/reconcile.go index 4f7a0c809a6..867b3d2a73e 100644 --- a/pkg/controller/elasticsearch/certificates/transport/reconcile.go +++ b/pkg/controller/elasticsearch/certificates/transport/reconcile.go @@ -122,7 +122,7 @@ func reconcileNodeSetTransportCertificatesSecrets( ); err != nil { return err } - certCommonName := buildCertificateCommonName(pod, es.Name, es.Namespace) + certCommonName := buildCertificateCommonName(pod, es) cert := extractTransportCert(*secret, pod, certCommonName) if cert == nil { return errors.New("no certificate found for pod")