Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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
Original file line number Diff line number Diff line change
Expand Up @@ -25413,6 +25413,12 @@ spec:
properties:
allowInvalidCertificates:
type: boolean
certManagementPolicy:
default: auto
enum:
- auto
- userProvidedOnly
type: string
certValidityDuration:
type: string
issuerConf:
Expand Down
6 changes: 6 additions & 0 deletions deploy/bundle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26300,6 +26300,12 @@ spec:
properties:
allowInvalidCertificates:
type: boolean
certManagementPolicy:
default: auto
enum:
- auto
- userProvidedOnly
type: string
certValidityDuration:
type: string
issuerConf:
Expand Down
1 change: 1 addition & 0 deletions deploy/cr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ spec:
# # 90 days in hours
# certValidityDuration: 2160h
# allowInvalidCertificates: true
# certManagementPolicy: auto
# issuerConf:
# name: special-selfsigned-issuer
# kind: ClusterIssuer
Expand Down
6 changes: 6 additions & 0 deletions deploy/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26300,6 +26300,12 @@ spec:
properties:
allowInvalidCertificates:
type: boolean
certManagementPolicy:
default: auto
enum:
- auto
- userProvidedOnly
type: string
certValidityDuration:
type: string
issuerConf:
Expand Down
6 changes: 6 additions & 0 deletions deploy/cw-bundle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26300,6 +26300,12 @@ spec:
properties:
allowInvalidCertificates:
type: boolean
certManagementPolicy:
default: auto
enum:
- auto
- userProvidedOnly
type: string
certValidityDuration:
type: string
issuerConf:
Expand Down
34 changes: 34 additions & 0 deletions e2e-tests/cert-management-policy/conf/some-name-auto.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apiVersion: psmdb.percona.com/v1
kind: PerconaServerMongoDB
metadata:
name: some-name
spec:
image:
imagePullPolicy: Always
updateStrategy: SmartUpdate
tls:
certManagementPolicy: auto
backup:
enabled: false
replsets:
- name: rs0
affinity:
antiAffinityTopologyKey: none
resources:
limits:
cpu: 500m
memory: 1G
requests:
cpu: 100m
memory: 0.1G
volumeSpec:
persistentVolumeClaim:
resources:
requests:
storage: 1Gi
expose:
enabled: false
type: ClusterIP
size: 3
secrets:
users: some-users
34 changes: 34 additions & 0 deletions e2e-tests/cert-management-policy/conf/some-name.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apiVersion: psmdb.percona.com/v1
kind: PerconaServerMongoDB
metadata:
name: some-name
spec:
image:
imagePullPolicy: Always
updateStrategy: SmartUpdate
tls:
certManagementPolicy: userProvidedOnly
backup:
enabled: false
replsets:
- name: rs0
affinity:
antiAffinityTopologyKey: none
resources:
limits:
cpu: 500m
memory: 1G
requests:
cpu: 100m
memory: 0.1G
volumeSpec:
persistentVolumeClaim:
resources:
requests:
storage: 1Gi
expose:
enabled: false
type: ClusterIP
size: 3
secrets:
users: some-users
117 changes: 117 additions & 0 deletions e2e-tests/cert-management-policy/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/bin/bash

set -o errexit

test_dir="$(realpath "$(dirname "$0")")"
. "${test_dir}/../functions"
set_debug

cluster="some-name"

test_user_provided_only() {
desc '=== Test: certManagementPolicy: userProvidedOnly ==='

desc 'create secrets (users + TLS)'
kubectl_bin apply -f "$conf_dir/secrets_with_tls.yml"

desc "create PSMDB cluster $cluster with certManagementPolicy: userProvidedOnly"
apply_cluster "$test_dir/conf/$cluster.yml"

desc 'check if all Pods started'
wait_for_running $cluster-rs0 3

desc 'save SSL secrets for later restore'
kubectl_bin get secret ${cluster}-ssl -o yaml >"$tmp_dir/ssl_backup.yaml"
kubectl_bin get secret ${cluster}-ssl-internal -o yaml >"$tmp_dir/ssl_internal_backup.yaml"

desc 'delete SSL secrets to simulate secret loss'
kubectl_bin delete secret ${cluster}-ssl ${cluster}-ssl-internal

desc 'wait for a few reconcile loops'
sleep 30

desc 'verify operator did NOT recreate SSL secrets'
if kubectl_bin get secret ${cluster}-ssl 2>/dev/null; then
echo "FAIL: operator recreated SSL secret when certManagementPolicy is userProvidedOnly"
exit 1
fi
if kubectl_bin get secret ${cluster}-ssl-internal 2>/dev/null; then
echo "FAIL: operator recreated SSL internal secret when certManagementPolicy is userProvidedOnly"
exit 1
fi
echo "PASS: operator did not recreate SSL secrets"

desc 'verify TLSSecretMissing status condition is set'
local condition
condition=$(kubectl_bin get psmdb ${cluster} -o jsonpath='{.status.conditions[?(@.type=="TLSSecretMissing")].status}')
if [[ "$condition" != "True" ]]; then
echo "FAIL: TLSSecretMissing condition is not set (got: '$condition')"
exit 1
fi
echo "PASS: TLSSecretMissing condition is set"

desc 'verify pods are still running (no restart, skip cluster readiness check)'
wait_for_running $cluster-rs0 3 false

desc 'restore SSL secrets'
kubectl_bin apply -f "$tmp_dir/ssl_backup.yaml"
kubectl_bin apply -f "$tmp_dir/ssl_internal_backup.yaml"

desc 'verify cluster is still healthy after secret restore'
sleep 10
wait_for_running $cluster-rs0 3

desc 'verify TLSSecretMissing condition is removed after secret restore'
local condition_after
condition_after=$(kubectl_bin get psmdb ${cluster} -o jsonpath='{.status.conditions[?(@.type=="TLSSecretMissing")].status}')
if [[ -n "$condition_after" ]]; then
echo "FAIL: TLSSecretMissing condition should be removed after secret restore (got: '$condition_after')"
exit 1
fi
echo "PASS: TLSSecretMissing condition is removed after secret restore"

desc 'cleanup cluster'
kubectl_bin delete psmdb $cluster
wait_for_delete psmdb/$cluster 180
}

test_auto() {
desc '=== Test: certManagementPolicy: auto ==='

desc 'create only user secrets (no TLS secrets)'
kubectl_bin apply -f "$conf_dir/secrets.yml"

desc "create PSMDB cluster $cluster with certManagementPolicy: auto"
apply_cluster "$test_dir/conf/$cluster-auto.yml"

desc 'wait for operator to auto-create SSL secrets'
sleep 30

desc 'verify operator created SSL secrets automatically'
if ! kubectl_bin get secret ${cluster}-ssl 2>/dev/null; then
echo "FAIL: operator did not create SSL secret when certManagementPolicy is auto"
exit 1
fi
if ! kubectl_bin get secret ${cluster}-ssl-internal 2>/dev/null; then
echo "FAIL: operator did not create SSL internal secret when certManagementPolicy is auto"
exit 1
fi
echo "PASS: operator created SSL secrets automatically"

desc 'check if all Pods started'
wait_for_running $cluster-rs0 3
}

main() {
create_infra "$namespace"
destroy_cert_manager || true

test_user_provided_only
test_auto

destroy "$namespace"

desc 'test passed'
}

main
1 change: 1 addition & 0 deletions e2e-tests/run-pr.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
arbiter
balancer
cert-management-policy
cross-site-sharded
custom-replset-name
custom-tls
Expand Down
1 change: 1 addition & 0 deletions e2e-tests/run-release.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
arbiter
balancer
cert-management-policy
cross-site-sharded
custom-replset-name
custom-tls
Expand Down
6 changes: 6 additions & 0 deletions e2e-tests/version-service/conf/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26300,6 +26300,12 @@ spec:
properties:
allowInvalidCertificates:
type: boolean
certManagementPolicy:
default: auto
enum:
- auto
- userProvidedOnly
type: string
certValidityDuration:
type: string
issuerConf:
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/psmdb/v1/psmdb_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,21 @@ const (
TLSModeRequire TLSMode = "requireTLS"
)

type CertManagementPolicy string

const (
CertManagementAuto CertManagementPolicy = "auto"
CertManagementUserProvidedOnly CertManagementPolicy = "userProvidedOnly"
)

type TLSSpec struct {
Mode TLSMode `json:"mode,omitempty"`
AllowInvalidCertificates *bool `json:"allowInvalidCertificates,omitempty"`
CertValidityDuration metav1.Duration `json:"certValidityDuration,omitempty"`
IssuerConf *cmmeta.ObjectReference `json:"issuerConf,omitempty"`
// +kubebuilder:default=auto
// +kubebuilder:validation:Enum={auto,userProvidedOnly}
CertManagementPolicy CertManagementPolicy `json:"certManagementPolicy,omitempty"`
}

func (spec *PerconaServerMongoDBSpec) Replset(name string) *ReplsetSpec {
Expand Down Expand Up @@ -374,6 +384,8 @@ const (
ConditionTypePendingSmartUpdate AppState = "pendingSmartUpdate"

ConditionTypePBMReady AppState = "PBMReady"

ConditionTypeTLSSecretMissing AppState = "TLSSecretMissing"
)

type ClusterCondition struct {
Expand Down
57 changes: 57 additions & 0 deletions pkg/controller/perconaservermongodb/psmdb_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -1564,6 +1564,39 @@ func ensurePVCs(

var errTLSNotReady = errors.New("waiting for TLS secret")

// currentSSLAnnotation reads the current SSL annotations from existing StatefulSets
// to preserve them when the TLS secret is missing.
func (r *ReconcilePerconaServerMongoDB) currentSSLAnnotation(ctx context.Context, cr *api.PerconaServerMongoDB) map[string]string {
annotation := map[string]string{
"percona.com/ssl-hash": "",
"percona.com/ssl-internal-hash": "",
}

sfsList := appsv1.StatefulSetList{}
if err := r.client.List(ctx, &sfsList,
&client.ListOptions{
Namespace: cr.Namespace,
LabelSelector: labels.SelectorFromSet(map[string]string{
naming.LabelKubernetesInstance: cr.Name,
}),
},
); err != nil {
return annotation
}

for _, sts := range sfsList.Items {
if v, ok := sts.Spec.Template.Annotations["percona.com/ssl-hash"]; ok {
annotation["percona.com/ssl-hash"] = v
}
if v, ok := sts.Spec.Template.Annotations["percona.com/ssl-internal-hash"]; ok {
annotation["percona.com/ssl-internal-hash"] = v
}
break
}

return annotation
}

func (r *ReconcilePerconaServerMongoDB) sslAnnotation(ctx context.Context, cr *api.PerconaServerMongoDB) (map[string]string, error) {
annotation := make(map[string]string)

Expand Down Expand Up @@ -1593,12 +1626,24 @@ func (r *ReconcilePerconaServerMongoDB) sslAnnotation(ctx context.Context, cr *a
return &secretObj, nil
}

isUserProvidedOnly := cr.Spec.TLS != nil && cr.Spec.TLS.CertManagementPolicy == api.CertManagementUserProvidedOnly

sslSecret, err := getSecret(api.SSLSecretName(cr))
if err != nil {
if k8serrors.IsNotFound(err) {
if cr.UnsafeTLSDisabled() {
return annotation, nil
}
if isUserProvidedOnly {
logf.FromContext(ctx).Error(nil, "TLS secret not found, skipping annotation update since certManagementPolicy is userProvidedOnly", "secret", api.SSLSecretName(cr))
cr.Status.AddCondition(api.ClusterCondition{
Status: api.ConditionTrue,
Type: api.ConditionTypeTLSSecretMissing,
Reason: "TLSSecretNotFound",
Message: fmt.Sprintf("TLS secret %s is missing, certManagementPolicy is userProvidedOnly", api.SSLSecretName(cr)),
})
Comment on lines +1639 to +1644
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@myJamong I think rather than having a negative condition type and a positive status, we should have a positive type and negative status. for example:

				cr.Status.AddCondition(api.ClusterCondition{
					Status:  api.ConditionFalse,
					Type:    api.ConditionTypeTLSSecretsReady,
					Reason:  "TLSSecretNotFound",
					Message: fmt.Sprintf("TLS secret %s is missing, certManagementPolicy is userProvidedOnly", api.SSLSecretName(cr)),
				})

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed - 4968ab6

Renamed TLSSecretMissing to TLSSecretsReady with positive type and negative status and updated all references including unit tests and e2e tests.

return r.currentSSLAnnotation(ctx, cr), nil
}
return nil, errTLSNotReady
}
return nil, errors.Wrapf(err, "get secret/%s", api.SSLSecretName(cr))
Expand All @@ -1615,12 +1660,24 @@ func (r *ReconcilePerconaServerMongoDB) sslAnnotation(ctx context.Context, cr *a
if cr.UnsafeTLSDisabled() || isCustomSecret {
return annotation, nil
}
if isUserProvidedOnly {
logf.FromContext(ctx).Error(nil, "TLS secret not found, skipping annotation update since certManagementPolicy is userProvidedOnly", "secret", api.SSLInternalSecretName(cr))
cr.Status.AddCondition(api.ClusterCondition{
Status: api.ConditionTrue,
Type: api.ConditionTypeTLSSecretMissing,
Reason: "TLSSecretNotFound",
Message: fmt.Sprintf("TLS secret %s is missing, certManagementPolicy is userProvidedOnly", api.SSLInternalSecretName(cr)),
})
return r.currentSSLAnnotation(ctx, cr), nil
}
return nil, errTLSNotReady
}
return nil, errors.Wrapf(err, "get secret/%s", api.SSLInternalSecretName(cr))
}
annotation["percona.com/ssl-internal-hash"] = getHash(sslInternalSecret)

cr.Status.RemoveCondition(api.ConditionTypeTLSSecretMissing)

return annotation, nil
}

Expand Down
Loading