Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions config/crds/v1/all-crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6576,6 +6576,23 @@ spec:
description: TLS defines options for configuring TLS on the transport
layer.
properties:
caSecretKeys:
description: |-
CASecretKeys optionally overrides which keys ECK reads from the Secret referenced by Certificate.
Useful when the Secret was produced by cert-manager, which emits tls.crt/tls.key alongside ca.crt.
When either field is set, the legacy tls.* vs modern ca.* auto-detection (and its "both exist"
conflict check) is skipped entirely.
properties:
caCertKey:
description: CACertKey is the Secret data key holding
the CA certificate (PEM). Defaults to "ca.crt" when
empty.
type: string
caKeyKey:
description: CAKeyKey is the Secret data key holding the
CA private key (PEM). Defaults to "ca.key" when empty.
type: string
type: object
certificate:
description: |-
Certificate is a reference to a Kubernetes secret that contains the CA certificate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10845,6 +10845,23 @@ spec:
description: TLS defines options for configuring TLS on the transport
layer.
properties:
caSecretKeys:
description: |-
CASecretKeys optionally overrides which keys ECK reads from the Secret referenced by Certificate.
Useful when the Secret was produced by cert-manager, which emits tls.crt/tls.key alongside ca.crt.
When either field is set, the legacy tls.* vs modern ca.* auto-detection (and its "both exist"
conflict check) is skipped entirely.
properties:
caCertKey:
description: CACertKey is the Secret data key holding
the CA certificate (PEM). Defaults to "ca.crt" when
empty.
type: string
caKeyKey:
description: CAKeyKey is the Secret data key holding the
CA private key (PEM). Defaults to "ca.key" when empty.
type: string
type: object
certificate:
description: |-
Certificate is a reference to a Kubernetes secret that contains the CA certificate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6625,6 +6625,23 @@ spec:
description: TLS defines options for configuring TLS on the transport
layer.
properties:
caSecretKeys:
description: |-
CASecretKeys optionally overrides which keys ECK reads from the Secret referenced by Certificate.
Useful when the Secret was produced by cert-manager, which emits tls.crt/tls.key alongside ca.crt.
When either field is set, the legacy tls.* vs modern ca.* auto-detection (and its "both exist"
conflict check) is skipped entirely.
properties:
caCertKey:
description: CACertKey is the Secret data key holding
the CA certificate (PEM). Defaults to "ca.crt" when
empty.
type: string
caKeyKey:
description: CAKeyKey is the Secret data key holding the
CA private key (PEM). Defaults to "ca.key" when empty.
type: string
type: object
certificate:
description: |-
Certificate is a reference to a Kubernetes secret that contains the CA certificate
Expand Down
16 changes: 16 additions & 0 deletions pkg/apis/elasticsearch/v1/elasticsearch_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,29 @@ type TransportTLSOptions struct {
// - `ca.crt`: The CA certificate in PEM format.
// - `ca.key`: The private key for the CA certificate in PEM format.
Certificate commonv1.SecretRef `json:"certificate,omitempty"`
// CASecretKeys optionally overrides which keys ECK reads from the Secret referenced by Certificate.
// Useful when the Secret was produced by cert-manager, which emits tls.crt/tls.key alongside ca.crt.
// When either field is set, the legacy tls.* vs modern ca.* auto-detection (and its "both exist"
// conflict check) is skipped entirely.
// +kubebuilder:validation:Optional
CASecretKeys *CASecretKeys `json:"caSecretKeys,omitempty"`
// CertificateAuthorities is a reference to a config map that contains one or more x509 certificates for
// trusted authorities in PEM format. The certificates need to be in a file called `ca.crt`.
CertificateAuthorities commonv1.ConfigMapRef `json:"certificateAuthorities,omitempty"`
// SelfSignedCertificates allows configuring the self-signed certificate generated by the operator.
SelfSignedCertificates *SelfSignedTransportCertificates `json:"selfSignedCertificates,omitempty"`
}

// CASecretKeys overrides the Secret data keys ECK reads when consuming a user-provided transport CA.
type CASecretKeys struct {
// CACertKey is the Secret data key holding the CA certificate (PEM). Defaults to "ca.crt" when empty.
// +kubebuilder:validation:Optional
CACertKey string `json:"caCertKey,omitempty"`
// CAKeyKey is the Secret data key holding the CA private key (PEM). Defaults to "ca.key" when empty.
// +kubebuilder:validation:Optional
CAKeyKey string `json:"caKeyKey,omitempty"`
}

func (tto TransportTLSOptions) SelfSignedEnabled() bool {
return tto.SelfSignedCertificates == nil || !tto.SelfSignedCertificates.Disabled
}
Expand Down
20 changes: 20 additions & 0 deletions pkg/apis/elasticsearch/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions pkg/controller/common/certificates/ca_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,26 @@ import (
// It does not check that the public key matches the private key.
// Legacy tls.* keys are still supported while the expected default keys are ca.crt and ca.key.
func ParseCustomCASecret(s corev1.Secret) (*CA, error) {
return ParseCustomCASecretWithKeys(s, "", "")
}

// ParseCustomCASecretWithKeys behaves like ParseCustomCASecret but lets the caller specify which
// Secret data keys hold the CA certificate and private key. When both overrides are empty, the
// legacy tls.* vs modern ca.* auto-detection runs unchanged. When either override is non-empty,
// only the named keys are read and the "both exist" conflict check is bypassed — required for
// consuming Secrets produced by cert-manager (which always emits tls.crt, tls.key, and ca.crt).
func ParseCustomCASecretWithKeys(s corev1.Secret, certKeyOverride, keyKeyOverride string) (*CA, error) {
if certKeyOverride != "" || keyKeyOverride != "" {
crtFileName := certKeyOverride
if crtFileName == "" {
crtFileName = CAFileName
}
keyFileName := keyKeyOverride
if keyFileName == "" {
keyFileName = CAKeyFileName
}
return parseCAFromSecret(s, keyFileName, crtFileName)
}
keyFileName := CAKeyFileName
crtFileName := CAFileName
// For backwards compatibility we support both tls.* and the newer ca.* keys in the secret
Expand Down
93 changes: 93 additions & 0 deletions pkg/controller/common/certificates/ca_secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,99 @@ func TestParseCustomCASecret(t *testing.T) {
}
}

func TestParseCustomCASecretWithKeys(t *testing.T) {
ca := loadFileBytes("ca.crt")
key := loadFileBytes("tls.key")

tests := []struct {
name string
s corev1.Secret
certKeyOverride string
keyKeyOverride string
wantErr bool
}{
{
name: "Empty overrides → identical to ParseCustomCASecret (ca.* path)",
s: corev1.Secret{
Data: map[string][]byte{
"ca.crt": ca,
"ca.key": key,
},
},
wantErr: false,
},
{
name: "Both overrides set → reads tls.* even when ca.crt also present (cert-manager shape)",
s: corev1.Secret{
Data: map[string][]byte{
"tls.crt": ca,
"tls.key": key,
"ca.crt": ca, // cert-manager emits this as the issuing CA bundle
},
},
certKeyOverride: "tls.crt",
keyKeyOverride: "tls.key",
wantErr: false,
},
{
name: "Only cert override set → key falls back to ca.key default",
s: corev1.Secret{
Data: map[string][]byte{
"tls.crt": ca,
"ca.key": key,
},
},
certKeyOverride: "tls.crt",
wantErr: false,
},
{
name: "Only key override set → cert falls back to ca.crt default",
s: corev1.Secret{
Data: map[string][]byte{
"ca.crt": ca,
"tls.key": key,
},
},
keyKeyOverride: "tls.key",
wantErr: false,
},
{
name: "Override pointing at missing key returns error",
s: corev1.Secret{
Data: map[string][]byte{
"ca.crt": ca,
"ca.key": key,
},
},
certKeyOverride: "does-not-exist.crt",
keyKeyOverride: "does-not-exist.key",
wantErr: true,
},
{
name: "Override skips the both-exist conflict check",
s: corev1.Secret{
Data: map[string][]byte{
"tls.crt": ca,
"tls.key": key,
"ca.crt": ca,
"ca.key": key,
},
},
certKeyOverride: "tls.crt",
keyKeyOverride: "tls.key",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := ParseCustomCASecretWithKeys(tt.s, tt.certKeyOverride, tt.keyKeyOverride)
if (err != nil) != tt.wantErr {
t.Errorf("ParseCustomCASecretWithKeys() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestValidateCustomCA(t *testing.T) {
tests := []struct {
name string
Expand Down
6 changes: 5 additions & 1 deletion pkg/controller/elasticsearch/certificates/transport/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ func ReconcileOrRetrieveCA(
// 2. Assuming from here on the user wants to use custom certs and has configured a secret with them.

// Try to parse the provided secret to get to the CA and to report any validation errors to the user.
ca, err := certificates.ParseCustomCASecret(*customCASecret)
var certKeyOverride, keyKeyOverride string
if ks := es.Spec.Transport.TLS.CASecretKeys; ks != nil {
certKeyOverride, keyKeyOverride = ks.CACertKey, ks.CAKeyKey
}
ca, err := certificates.ParseCustomCASecretWithKeys(*customCASecret, certKeyOverride, keyKeyOverride)
if err != nil {
// Surface validation/parsing errors to the user via an event otherwise they might be hard to spot
// validation at admission would also be an alternative but seems quite costly and secret contents might change
Expand Down