Skip to content

Commit

Permalink
Add support for transport TLS certificate other/common name suffix (e…
Browse files Browse the repository at this point in the history
…lastic#5189)

Add a new option called otherNameSuffix to allow users to influence the name construction in the OtherName SAN extension of Elasticsearch node certificates for the transport layer. 

Co-authored-by: Sebastien Guilloux <contact.sebgl@gmail.com>
  • Loading branch information
2 people authored and fantapsody committed Jan 3, 2023
1 parent 73cb5d7 commit 49d2d20
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 13 deletions.
9 changes: 9 additions & 0 deletions config/crds/v1/all-crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
"<pod_name>.node.cluster.local".'
type: string
subjectAltNames:
description: SubjectAlternativeNames is a list of SANs to
include in the generated node transport TLS certificates.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
"<pod_name>.node.cluster.local".'
type: string
subjectAltNames:
description: SubjectAlternativeNames is a list of SANs to
include in the generated node transport TLS certificates.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
"<pod_name>.node.cluster.local".'
type: string
subjectAltNames:
description: SubjectAlternativeNames is a list of SANs to
include in the generated node transport TLS certificates.
Expand Down
1 change: 1 addition & 0 deletions docs/reference/api-docs.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<pod_name>.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.
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/elasticsearch/v1/elasticsearch_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<pod_name>.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
Expand Down
16 changes: 11 additions & 5 deletions pkg/controller/elasticsearch/certificates/transport/csr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
},

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 (<pod_name>.<user-suffix).
// Otherwise, it defaults to <pod_name>.node.<es_name>.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)
}
66 changes: 60 additions & 6 deletions pkg/controller/elasticsearch/certificates/transport/csr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit 49d2d20

Please sign in to comment.