Skip to content

Advanced VM install: cert-manager DNS-01, drop Caddy/lego#1336

Draft
RAVEENSR wants to merge 7 commits into
wso2:mainfrom
RAVEENSR:vm-advanced-cert-manager
Draft

Advanced VM install: cert-manager DNS-01, drop Caddy/lego#1336
RAVEENSR wants to merge 7 commits into
wso2:mainfrom
RAVEENSR:vm-advanced-cert-manager

Conversation

@RAVEENSR

Copy link
Copy Markdown
Member

Draft: the code is complete, unit-tested, and the routing/cert-manager mechanics are validated on a live cluster, but the full real-DNS-01 end-to-end (a real domain + provider token on a VM) is still pending. It also wants the hook-RBAC regression fix (#1335) merged so a fresh install completes cleanly.

Purpose

Continues #1329. The advanced VM installer used a host Caddy container plus dockerized lego, with a five-mode TLS matrix (letsencrypt | letsencrypt-dns | byoc | selfsigned | upstream). This replaces all of it with a single publicly-trusted path: cert-manager's ACME DNS-01 challenge issues a wildcard certificate in-cluster and kgateway terminates TLS on :443 — no Caddy, no lego. Because DNS-01 validates via a DNS TXT record (egress only), the same flow works on a public or a private/firewalled VM.

The simple path (sslip.io + Caddy + TLS-ALPN-01) is intentionally unchanged; this PR only touches the advanced path.

Related to #1329. Builds on #1331 (simple path + one-command delivery, merged). Depends on #1335 (hook-RBAC fix) for a clean fresh install.

Goals

  • Remove the Caddy dependency and use OpenChoreo's already-present cert-manager for certificate management (the two open [Improvement]: Simplify the Deploy on a VM script #1329 checkboxes).
  • Collapse the five-mode TLS matrix to one publicly-trusted DNS-01 path.
  • Support the same DNS providers lego covered — Cloudflare, Route 53, Google Cloud DNS, Azure DNS.

Approach

  • deployments/vm/lib-certmanager.sh (new): renders the DNS-01 provider Secret, the ACME ClusterIssuer, the wildcard Certificate, and the consolidated :443 Gateway. Four providers via one code path; each emits the matching cert-manager dns01 solver block.
  • Config collapse: the advanced config drops to DNS-01 only — DOMAIN_BASE / ACME_EMAIL / DNS_PROVIDER + that provider's credentials. TLS_MODE and the byoc cert validation are removed.
  • Single :443 gateway: the three plane gateways are consolidated onto one HTTPS gateway with the cert-manager wildcard cert (allowedRoutes: from All). Fixed-service routes are repointed via ocIngress gateway overrides; deployed-agent routes via the ClusterDataPlane external ingress (now parameterized in install.sh); remaining routes are attached post-install.
  • deployments/vm/lib-tls.sh deleted (lego issuance, self-signed CA, systemd renewal) — cert-manager auto-renews in-cluster.
  • k3d: the advanced install publishes host :443 (the simple path stays loopback + Caddy).
  • Advanced docs tab rewritten for the single DNS-01 flow (public/private split collapses).

User stories

As an operator with my own domain, I can install Agent Manager on a public or private VM and get publicly-trusted TLS with no Caddy/lego and no per-host certificate steps — cert-manager issues and renews a wildcard cert via my DNS provider.

Release note

The advanced VM installer now issues publicly-trusted certificates via cert-manager's ACME DNS-01 challenge (Cloudflare, Route 53, Google Cloud DNS, or Azure DNS) and terminates TLS at the in-cluster gateway, replacing the Caddy + lego front door. It works on public and private VMs alike.

Documentation

Updated in this PR: the Advanced tab of documentation/docs/getting-started/on-a-vm.mdx.

Training

N/A — no training-content impact.

Certification

N/A — no impact on certification exams.

Marketing

N/A — install-experience improvement.

Automation tests

  • Unit tests

    deployments/vm/tests/run.sh and tests/preflight.sh pass (config validation, the four provider solvers, the cert/gateway renderers); all changed shell is shellcheck-clean.

  • Integration tests

    --dry-run renders the cert-manager Secret + ClusterIssuer + Certificate + Gateway; the rendered YAML was validated against a live cert-manager admission webhook (server dry-run accepted) for all four providers. The gateway-consolidation design was validated on a live k3d + OpenChoreo cluster: one :443 gateway with a cert-manager wildcard cert fronts the control-plane, observability, and data-plane routes cross-namespace (console 200, observer reached on a real API path, deployed-agent route parity). Pending: the full real Let's Encrypt DNS-01 issuance e2e (Cloudflare, private VM), which needs the Grant the hook Role configmap access for CA publish #1335 chart fix in the published charts.

Security checks

  • Followed secure coding standards: yes
  • Ran FindSecurityBugs plugin and verified report? N/A — shell/Helm/docs change, no Java.
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? yes (provider credentials are read from the operator's config at install time and written only to an in-cluster Secret)

Samples

N/A.

Related PRs

Migrations (if applicable)

N/A — VM installer path; existing installs are unaffected.

Test environment

k3d (Kubernetes v1.32) + OpenChoreo on Ubuntu; cert-manager (jetstack); Helm 3/4. Shell verified on macOS (bash 3.2 compatibility retained) and Linux.

Learning

Confirmed cert-manager cannot replicate Caddy's on-demand per-hostname issuance, so the DNS-01 wildcard (which requires a controlled zone) is what makes dropping Caddy viable — hence the advanced path requires an owned domain while the simple path keeps Caddy + sslip.io.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f58ee73b-7671-436c-9d6b-1e107dc78e10

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

RAVEENSR added 3 commits July 17, 2026 17:40
The advanced installer used a host Caddy container plus dockerized lego,
with a five-mode TLS matrix (letsencrypt | letsencrypt-dns | byoc |
selfsigned | upstream). Replace all of it with a single publicly-trusted
path: cert-manager's ACME DNS-01 challenge issues a wildcard certificate
in-cluster and kgateway terminates TLS on :443. Works on a public or a
private VM, since DNS-01 validates via a DNS TXT record (egress only, no
inbound).

- Add lib-certmanager.sh: renders the DNS-01 provider Secret, the ACME
  ClusterIssuer, the wildcard Certificate, and the single :443 gateway.
  Four providers (cloudflare | route53 | clouddns | azuredns) via one
  code path; each emits the matching cert-manager dns01 solver block.
- Collapse the config to DNS-01 only (DOMAIN_BASE/ACME_EMAIL/DNS_PROVIDER
  + that provider's credentials); drop TLS_MODE and the byoc validate.
- Consolidate the three plane gateways onto one :443 gateway with a
  cert-manager wildcard cert (validated: cross-namespace routes attach
  via allowedRoutes from All). Fixed-service routes are repointed via
  ocIngress overrides; deployed-agent routes via the ClusterDataPlane
  external ingress (now parameterized in install.sh); the rest are
  attached post-install.
- Expose host :443 in the k3d config (advanced only; the simple path
  stays loopback + Caddy).
- Delete lib-tls.sh (lego issuance, self-signed CA, systemd renewal) —
  cert-manager auto-renews in-cluster. Rework the test suites.

Related to wso2#1329
The advanced tab described the removed Caddy + lego + five-TLS-mode flow
with separate Public/Private network sub-tabs. Replace it with the
single DNS-01 path: prerequisites, config keys, the four DNS-provider
credential blocks (cloudflare/route53/clouddns/azuredns), a how-TLS-works
diagram (cert-manager issues a wildcard cert, kgateway terminates :443),
the one-command bootstrap install, and teardown. The public/private
split collapses because DNS-01 issuance is egress-only and identical for
both.

Related to wso2#1329
main wso2#1340 consolidated observability into agent-manager-observer and
renamed the observability chart's top-level key tracesObserver ->
amObserver. Update the advanced installer's consolidated-gateway override
to amObserver.ocIngress.gatewayName/gatewayNamespace so the observer
route still attaches to the single :443 gateway.

Related to wso2#1329
@RAVEENSR
RAVEENSR force-pushed the vm-advanced-cert-manager branch from 9587a38 to f0e252c Compare July 17, 2026 12:12
RAVEENSR added 4 commits July 18, 2026 00:20
The advanced installer used to consolidate every route onto one gateway
in the control plane, reparenting the data-plane and observability routes
onto it cross-namespace. That broke the dynamic tiers: OpenChoreo births
each deployed agent's route on the data-plane gateway-default, and nothing
moved it to the :443 gateway, so agents deployed after install were
unreachable until their route was manually attached.

Invert the model. The consolidated :443 gateway now terminates TLS and
forwards by host to each plane's own gateway-default via three static
front-proxy HTTPRoutes (gateway->gateway), authorized cross-namespace by
ReferenceGrants. Every plane keeps its native routes, so plane separation
is preserved, and the *.agents and *.thunder wildcards cover the dynamic
tiers permanently — agents and per-env Thunder are reachable with no
reparenting. This drops the ocIngress/external-gateway overrides and the
post-install attach step entirely.

Validated on a fresh VM install (DNS-01 staging): all endpoints reachable
on :443, routes born native on their plane gateways, and the *.agents
chain live.
The OTEL export endpoint injected into deployed agents pointed at the
public https gateway (`https://<gateway-host>/otel`). In-cluster that
only works by hairpinning out through the VM's public IP; a private VM
has no such path, and the data-plane->control-plane :443 hop is blocked
by network policy, so trace export failed there.

Export is an in-cluster hop, so target the data-plane gateway over
http :19080 instead, and resolve the gateway host in-cluster to the
data-plane gateway service via a CoreDNS rewrite. External agents still
resolve the host to the public IP via DNS and keep the public endpoint;
only in-cluster resolution is redirected. Applies to both VM flavors
(the override + CoreDNS render are shared).

The eval/monitor LLM-judge and deployed-agent LLM proxy reach the
gateway the same way and have the same latent issue; that fix lives in
agent-manager-service (buildProxyURL's unimplemented internal branch)
and is tracked separately.
buildProxyURL took an isInternal flag but ignored it, always returning
the gateway's public vhost. In-cluster consumers that pass isInternal
(the monitor/eval LLM-judge and deployed-agent LLM proxy) therefore
addressed the gateway by its public https vhost. On a VM that only works
by hairpinning out through the public IP; on a private VM there is no
such path and the data-plane->control-plane :443 hop is blocked, so the
monitor LLM-judge timed out.

Implement the isInternal branch. On an on-prem deployment
(IsOnPremDeployment, the default) rewrite the vhost to plain http on the
kgateway port (DefaultGatewayPort), keeping the host so the gateway's
host-based routing still matches — it resolves in-cluster to the
data-plane gateway via the installer's CoreDNS rewrite. Cloud
deployments (IsOnPremDeployment=false) keep the public vhost, which is
reachable in-cluster there, so their behavior is unchanged.

Companion to routing agent OTEL export at the internal data-plane
gateway; both keep in-cluster gateway traffic off the public endpoint.
The goheader linter requires the repository's exact header (2026, and a
double space after "implied."); the new test file used a slightly
different one, failing the Lint check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant