fix: publish service DNS name instead of ClusterIP in connectionString - #447
fix: publish service DNS name instead of ClusterIP in connectionString#447WentingWu666666 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Updates status.connectionString to publish stable Service DNS names for ClusterIP services while preserving LoadBalancer endpoints.
Changes:
- Renames endpoint resolution to
EnsureServiceEndpoint. - Adds ClusterIP DNS and LoadBalancer endpoint test coverage.
- Updates reconciliation to use the resolved endpoint.
- Critical finding: certificate SANs do not account for 63-character Service-name truncation, so strict TLS can still fail for long names.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Summary |
|---|---|
operator/src/internal/utils/util.go |
Resolves ClusterIP services to DNS endpoints. |
operator/src/internal/utils/util_test.go |
Tests DNS, IP, hostname, and error cases. |
operator/src/internal/controller/documentdb_controller.go |
Publishes the resolved endpoint in connection strings. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
🤖 Auto-triaged by documentdb-triage-tool. Applied: Reasoningcomponent from path globs (controllers, test); effort from diff stats (71+21 LOC, 3 files); LLM: Fixes a broken connection string under strict TLS when ClusterIP is published instead of a DNS SAN — an active functional blocker for TLS-enabled in-cluster consumers, with a focused rename + logic change in one component and extended tests. If a label is wrong, remove it manually and ping |
When spec.exposeViaService.serviceType is ClusterIP, status.connectionString embedded the raw ClusterIP (e.g. 10.96.x.x) as the host. That address is routable in-cluster, but it breaks TLS hostname verification: the gateway certificate issued by the certificate controller carries only DNS SANs (documentdb-service-<name>, .<ns>, and .<ns>.svc) and no IP SAN. Once status.tls.Ready is true the connection string omits tlsAllowInvalidCertificates, so a client following the published string performs strict verification against an IP with no matching SAN and fails. Return the in-cluster DNS name <service>.<namespace>.svc for ClusterIP services instead. It matches an existing certificate SAN, resolves from any pod in the cluster, and stays valid when the Service is recreated with a different ClusterIP. The name is read from the Service object so the 63-char truncation applied when the Service is built is preserved. LoadBalancer behavior is unchanged: the external ingress IP/hostname is still returned, since cluster-internal DNS is not resolvable by outside clients. EnsureServiceIP is renamed to EnsureServiceEndpoint since it no longer returns an IP in every case. Fixes documentdb#201 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Wenting Wu <wentingwu@microsoft.com>
43e7cac to
3816ae7
Compare
GetDocumentDBServiceDefinition truncates the Service name to the 63-character RFC 1123 limit, but certificate_controller.go built the gateway cert SANs from the untruncated DOCUMENTDB_SERVICE_PREFIX + ddb.Name. For DocumentDB names longer than 44 characters the two diverged, so the DNS name now published in status.connectionString had no matching SAN and failed strict TLS verification. Introduce util.GetDocumentDBServiceName as the single source of truth for that name and use it in the Service definition, both cert SAN call sites, and the e2e port-forward helper that mirrors it. Also trim a trailing separator left behind by truncation, which would otherwise produce an invalid RFC 1123 label the API server rejects. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Wenting Wu <wentingwu@microsoft.com>
|
Pushed e482849 addressing the review thread on the 63-character truncation: the Service name and the gateway cert SANs now both derive from a single |
Summary
When
spec.exposeViaService.serviceTypeisClusterIP,status.connectionStringpublished the raw ClusterIP (e.g.10.96.x.x) as the host. This PR publishes the in-cluster DNS name (<service>.<namespace>.svc) instead.Fixes #201.
Why not
localhost?The original issue proposed substituting
localhost. While investigating I found that would be wrong for in-cluster consumers (I raised this as an open question on the issue back in June). More importantly, the investigation surfaced that the raw ClusterIP is not merely cosmetic — it is broken under strict TLS, which makes the DNS name the correct fix for both audiences and resolves the open question.Root cause
The gateway certificate created by
certificate_controller.gocarries DNS SANs only — no IP SAN:Meanwhile
GenerateConnectionStringalways setstls=true, and omitstlsAllowInvalidCertificates=trueexactly whenstatus.tls.Readyis true:So once TLS is trusted, a client following the published string performs strict hostname verification against an IP address that has no matching SAN, and the connection fails. The failure was masked on the untrusted path because
tlsAllowInvalidCertificates=trueskips verification entirely.The fix
One-line behavioral change in
EnsureServiceIP: forClusterIPservices it now returns<service>.<namespace>.svc. That address:The name is read from the Service object rather than recomputed from the CR name, so the 63-character truncation applied in
GetDocumentDBServiceDefinitionis preserved.LoadBalancer behavior is unchanged — the external ingress IP/hostname is still returned, since cluster-internal DNS is not resolvable by outside clients.
No call-site changes are needed:
GenerateConnectionStringalready accepts an arbitrary host (it has existing hostname and IPv6 test cases), so the diff is confined tointernal/utils.Tests
TestEnsureServiceIPextended: asserts the exact DNS name forClusterIP. The previous success case only asserted non-empty, which is precisely why this regression was invisible. Added LoadBalancer IP and hostname cases alongside the existing error cases.go build ./...,go vet ./...,gofmtclean.api/preview,internal/cnpg,internal/controller,internal/otel,internal/utils,internal/webhook.go-patch-covergate CI uses: 100% (4/4 changed statements), against the 90% threshold.e2e compatibility
test/e2e/tests/status/connection_string_test.gois unaffected: it asserts only that the host is non-empty and that the port matches the gateway port, and it dials via port-forward rather than the published host. ItssplitHostPortscans from the right for:, so a dotted DNS name parses correctly.Notes for reviewers
Two adjacent items I deliberately left out of scope:
EnsureServiceIPno longer always returns an IP. I initially renamed it toEnsureServiceEndpoint, but that pulleddocumentdb_controller.gointo the diff — adding churn on behaviorally-unchanged lines, overlapping with in-flight feat: fail-fast preflight for schema upgrade path (multi-minor jumps) #444, and dragging patch coverage down to 43% onReconcilelines that unit tests do not reach. I reverted the rename and documented the behavior on the function instead. Happy to do the rename as a standalone follow-up once feat: fail-fast preflight for schema upgrade path (multi-minor jumps) #444 lands.Pre-existing: the cert SANs are built from
DOCUMENTDB_SERVICE_PREFIX + ddb.Namewithout the 63-char truncation thatGetDocumentDBServiceDefinitionapplies to the real Service name. For a sufficiently long CR name the SAN and the actual service name would diverge. Not touched here — can file a follow-up if useful.