Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

### Breaking Changes
- **Validating webhook added**: A new `ValidatingWebhookConfiguration` enforces that `spec.schemaVersion` never exceeds the binary version and blocks `spec.documentDBVersion` rollbacks below the committed schema version. This requires [cert-manager](https://cert-manager.io/) to be installed in the cluster (it is already a prerequisite for the sidecar injector). Existing clusters upgrading to this release will have the webhook activated automatically via `helm upgrade`.
- **Removed `Disabled` TLS gateway mode**: The `spec.tls.gateway.mode: Disabled` option has been removed to eliminate the security risk of plaintext Mongo wire protocol traffic. Previously, `Disabled` mode served connections in plaintext, contradicting documentation that stated TLS was always active. Empty or unset mode now defaults to `SelfSigned`. Users with `mode: Disabled` should remove this setting or explicitly set `mode: SelfSigned` — the gateway will automatically use a cert-manager generated self-signed certificate. See [issue #356](https://github.com/documentdb/documentdb-kubernetes-operator/issues/356) for details.

## [0.2.0] - 2026-03-25

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ _Appears in:_

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `mode` _string_ | Mode selects the TLS management strategy. | | Enum: [Disabled SelfSigned CertManager Provided] <br /> |
| `mode` _string_ | Mode selects the TLS management strategy. Defaults to SelfSigned if not specified. | SelfSigned | Enum: [SelfSigned CertManager Provided] <br /> |
| `certManager` _[CertManagerTLS](#certmanagertls)_ | CertManager config when Mode=CertManager. | | |
| `provided` _[ProvidedTLS](#providedtls)_ | Provided secret reference when Mode=Provided. | | |

Expand Down
39 changes: 2 additions & 37 deletions docs/operator-public-documentation/preview/configuration/tls.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: TLS Configuration
description: Configure TLS certificate management for DocumentDB gateway connections — Disabled, SelfSigned, CertManager, and Provided modes, private CA guidance, certificate rotation, and troubleshooting.
description: Configure TLS certificate management for DocumentDB gateway connections — SelfSigned, CertManager, and Provided modes, private CA guidance, certificate rotation, and troubleshooting.
tags:
- configuration
- tls
Expand All @@ -23,7 +23,7 @@ metadata:
spec:
tls:
gateway:
mode: SelfSigned # Disabled | SelfSigned | CertManager | Provided
mode: SelfSigned # SelfSigned | CertManager | Provided
```

For the full field reference, see [TLSConfiguration](../api-reference.md#tlsconfiguration) in the API Reference.
Expand All @@ -32,41 +32,6 @@ For the full field reference, see [TLSConfiguration](../api-reference.md#tlsconf

Select your TLS mode below. Each tab shows prerequisites, the complete YAML configuration, and connection instructions.

=== "Disabled"

**Best for:** Development and testing only

!!! danger "Not recommended for production"

**Prerequisites:** None

Disabled mode means the operator does not manage TLS certificates. However, the gateway still encrypts all connections using an internally generated self-signed certificate. Clients must connect with `tls=true&tlsAllowInvalidCertificates=true`.

```yaml title="documentdb-tls-disabled.yaml"
apiVersion: documentdb.io/preview
kind: DocumentDB
metadata:
name: my-documentdb
namespace: default
spec:
nodeCount: 1
instancesPerNode: 1
resource:
storage:
pvcSize: 10Gi
exposeViaService:
serviceType: ClusterIP
tls:
gateway:
mode: Disabled
```

**Connect with mongosh:**

```bash
mongosh "mongodb://<username>:<password>@<host>:10260/?directConnection=true&authMechanism=SCRAM-SHA-256&tls=true&tlsAllowInvalidCertificates=true"
```

=== "SelfSigned"

**Best for:** Development, testing, and environments without external PKI (Public Key Infrastructure)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,6 @@ DocumentDB Gateway serves TLS by default. The TLS mode is configurable via the `
| `SelfSigned` (default) | Operator generates a self-signed certificate | Use `tlsAllowInvalidCertificates=true` |
| `CertManager` | cert-manager issues a trusted certificate | Validate against your CA bundle |
| `Provided` | You supply your own certificate Secret | Validate against your CA |
| `Disabled` | TLS disabled on the gateway | Not recommended |

### Connecting with a trusted certificate

Expand Down
6 changes: 4 additions & 2 deletions operator/documentdb-helm-chart/crds/documentdb.io_dbs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1389,9 +1389,11 @@ spec:
- issuerRef
type: object
mode:
description: Mode selects the TLS management strategy.
default: SelfSigned
description: |-
Mode selects the TLS management strategy.
Defaults to SelfSigned if not specified.
enum:
- Disabled
- SelfSigned
- CertManager
- Provided
Expand Down
6 changes: 4 additions & 2 deletions operator/src/api/preview/documentdb_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ type BootstrapConfiguration struct {
}

// RecoveryConfiguration defines recovery settings for bootstrapping a DocumentDB cluster.
// +kubebuilder:validation:XValidation:rule="!(has(self.backup) && self.backup.name != '' && has(self.persistentVolume) && self.persistentVolume.name != '')",message="cannot specify both backup and persistentVolume recovery at the same time"
// +kubebuilder:validation:XValidation:rule="!(has(self.backup) && self.backup.name != && has(self.persistentVolume) && self.persistentVolume.name != )",message="cannot specify both backup and persistentVolume recovery at the same time"
type RecoveryConfiguration struct {
// Backup specifies the source backup to restore from.
// +optional
Expand Down Expand Up @@ -268,7 +268,9 @@ type TLSConfiguration struct {
// GatewayTLS defines TLS configuration for the gateway sidecar (Phase 1: certificate provisioning only)
type GatewayTLS struct {
// Mode selects the TLS management strategy.
// +kubebuilder:validation:Enum=Disabled;SelfSigned;CertManager;Provided
// Defaults to SelfSigned if not specified.
// +kubebuilder:validation:Enum=SelfSigned;CertManager;Provided
// +kubebuilder:default=SelfSigned
Mode string `json:"mode,omitempty"`

// CertManager config when Mode=CertManager.
Expand Down
24 changes: 12 additions & 12 deletions operator/src/api/preview/zz_generated.deepcopy.go

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

6 changes: 4 additions & 2 deletions operator/src/config/crd/bases/documentdb.io_dbs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1389,9 +1389,11 @@ spec:
- issuerRef
type: object
mode:
description: Mode selects the TLS management strategy.
default: SelfSigned
description: |-
Mode selects the TLS management strategy.
Defaults to SelfSigned if not specified.
enum:
- Disabled
- SelfSigned
- CertManager
- Provided
Expand Down
4 changes: 2 additions & 2 deletions operator/src/internal/cnpg/cnpg_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,8 @@ func SyncCnpgCluster(
// JSON Patch "add" requires the parent path to exist.
if current.Annotations == nil {
patchOps = append(patchOps, JSONPatch{
Op: PatchOpAdd,
Path: "/metadata/annotations",
Op: PatchOpAdd,
Path: "/metadata/annotations",
Value: map[string]string{
"kubectl.kubernetes.io/restartedAt": time.Now().Format(time.RFC3339Nano),
},
Expand Down
17 changes: 6 additions & 11 deletions operator/src/internal/controller/certificate_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,11 @@ func (r *CertificateReconciler) reconcileCertificates(ctx context.Context, ddb *
}

gatewayCfg := ddb.Spec.TLS.Gateway
if gatewayCfg.Mode == "" || gatewayCfg.Mode == "Disabled" {
if ddb.Status.TLS != nil && ddb.Status.TLS.Ready {
if err := r.updateTLSStatus(ctx, ddb, func(status *dbpreview.TLSStatus) {
status.Ready = false
status.Message = "Gateway TLS disabled"
}); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil

// Empty mode defaults to SelfSigned for security - TLS is always enabled
mode := gatewayCfg.Mode
if mode == "" {
mode = "SelfSigned"
}

if ddb.Status.TLS == nil {
Expand All @@ -81,7 +76,7 @@ func (r *CertificateReconciler) reconcileCertificates(ctx context.Context, ddb *
}
}

switch gatewayCfg.Mode {
switch mode {
case "SelfSigned":
return r.ensureSelfSignedCert(ctx, ddb)
case "Provided":
Expand Down
53 changes: 53 additions & 0 deletions operator/src/internal/controller/certificate_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,56 @@ func TestEnsureSelfSignedCert(t *testing.T) {
require.True(t, ddb.Status.TLS.Ready)
require.NotEmpty(t, ddb.Status.TLS.SecretName)
}

// TestEmptyModeDefaultsToSelfSigned verifies that when mode is empty,
// the controller treats it as SelfSigned to ensure TLS is always enabled.
// This is a security fix - see https://github.com/documentdb/documentdb-kubernetes-operator/issues/356
func TestEmptyModeDefaultsToSelfSigned(t *testing.T) {
ctx := context.Background()
ddb := baseDocumentDB("ddb-empty-mode", "default")
// Empty mode should default to SelfSigned behavior
ddb.Spec.TLS = &dbpreview.TLSConfiguration{Gateway: &dbpreview.GatewayTLS{Mode: ""}}
ddb.Status.TLS = &dbpreview.TLSStatus{}
r := buildCertificateReconciler(t, ddb)

// First call should create issuer and certificate (SelfSigned behavior)
res, err := r.reconcileCertificates(ctx, ddb)
require.NoError(t, err)
require.Equal(t, RequeueAfterShort, res.RequeueAfter)

// Certificate should exist, proving SelfSigned was used as default
cert := &cmapi.Certificate{}
require.NoError(t, r.Client.Get(ctx, types.NamespacedName{Name: "ddb-empty-mode-gateway-cert", Namespace: "default"}, cert))

// Simulate ready condition and verify TLS becomes ready
cert.Status.Conditions = append(cert.Status.Conditions, cmapi.CertificateCondition{Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionTrue, LastTransitionTime: &metav1.Time{Time: time.Now()}})
require.NoError(t, r.Client.Update(ctx, cert))
res, err = r.reconcileCertificates(ctx, ddb)
require.NoError(t, err)
require.Zero(t, res.RequeueAfter)
require.True(t, ddb.Status.TLS.Ready, "TLS should be ready with empty mode defaulting to SelfSigned")
require.NotEmpty(t, ddb.Status.TLS.SecretName)
}

// TestDisabledModeNotSupported verifies that "Disabled" is no longer a valid mode.
// This test documents the breaking change per issue #356.
func TestDisabledModeNotSupported(t *testing.T) {
ctx := context.Background()
ddb := baseDocumentDB("ddb-disabled", "default")
// "Disabled" mode should fall through to the default case and do nothing
// In production, the webhook validation prevents this invalid value
ddb.Spec.TLS = &dbpreview.TLSConfiguration{Gateway: &dbpreview.GatewayTLS{Mode: "Disabled"}}
ddb.Status.TLS = &dbpreview.TLSStatus{}
r := buildCertificateReconciler(t, ddb)

// With "Disabled" mode (which is now invalid), the controller should
// fall through the switch default case and return without action
res, err := r.reconcileCertificates(ctx, ddb)
require.NoError(t, err)
require.Zero(t, res.RequeueAfter)

// No certificate should be created since "Disabled" hits the default case
cert := &cmapi.Certificate{}
err = r.Client.Get(ctx, types.NamespacedName{Name: "ddb-disabled-gateway-cert", Namespace: "default"}, cert)
require.True(t, err != nil, "No certificate should be created for invalid 'Disabled' mode")
}
Original file line number Diff line number Diff line change
Expand Up @@ -1019,7 +1019,6 @@ func (r *DocumentDBReconciler) determineSchemaTarget(
}
}


// updateImageStatus reads the current extension and gateway images from the CNPG cluster
// and persists them into the DocumentDB status fields. This is a no-op if both fields
// are already up to date.
Expand Down
4 changes: 2 additions & 2 deletions operator/src/internal/utils/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ const (
MinK8sMinorVersion = 35

// DEFAULT_DOCUMENTDB_IMAGE is the extension image used in ImageVolume mode.
DEFAULT_DOCUMENTDB_IMAGE = DOCUMENTDB_EXTENSION_IMAGE_REPO + ":0.109.0"
DEFAULT_DOCUMENTDB_IMAGE = DOCUMENTDB_EXTENSION_IMAGE_REPO + ":0.109.0"
// NOTE: Keep in sync with operator/cnpg-plugins/sidecar-injector/internal/config/config.go:applyDefaults()
DEFAULT_GATEWAY_IMAGE = GATEWAY_IMAGE_REPO + ":0.109.0"
DEFAULT_DOCUMENTDB_CREDENTIALS_SECRET = "documentdb-credentials"
DEFAULT_OTEL_COLLECTOR_IMAGE = "otel/opentelemetry-collector-contrib:0.149.0"
DEFAULT_OTEL_COLLECTOR_IMAGE = "otel/opentelemetry-collector-contrib:0.149.0"

// TODO: remove these constants once change stream support is included in the official images.
CHANGESTREAM_DOCUMENTDB_IMAGE_REPOSITORY = "ghcr.io/wentingwu666666/documentdb-kubernetes-operator"
Expand Down
6 changes: 3 additions & 3 deletions operator/src/internal/utils/pv_recovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import (
const (
// Label for identifying temporary PVCs created for PV recovery
LabelRecoveryTemp = "documentdb.io/recovery-temp"

// Label for identifying the DocumentDB cluster a PV/PVC belongs to
LabelCluster = "documentdb.io/cluster"
LabelNamespace = "documentdb.io/namespace"
LabelCluster = "documentdb.io/cluster"
LabelNamespace = "documentdb.io/namespace"
)

// TempPVCNameForPVRecovery generates the name for a temporary PVC used during PV recovery.
Expand Down
Loading