Let’s Encrypt Test Plan¶
Intent¶
Validate the package’s optional Let’s Encrypt support in a disposable Big Bang environment using staging, not production. This plan covers package-specific issuer, mesh, policy, and evidence checks, not general cluster/bootstrap setup. ACME requires public DNS/reachability or provider credentials and is intentionally excluded from the default package pipeline.
What CI Covers¶
The unit suites check default-disabled and upgrade-only issuer behavior, staging/production/custom rendering, validation, and generated networking. The runtime checks verify admission and self-signed issuance, not public ACME, renewal, or external trust distribution. These checks are not evidence of a successful Let’s Encrypt Order in a particular environment.
What Manual ACME Testing Proves¶
Keep the normal Istio, NetworkPolicy, private-registry, and Kyverno controls
enabled. Prove that the staging issuer and Certificate become Ready, the related
Order and Challenge reach valid, and solver resources have the necessary
network/mesh/provider access without admission rejection. Use a delegated test
zone and least-privilege provider credentials.
Recommended Big Bang Test Path¶
Prefer DNS-01 for repeatable development validation: it does not require a public route into a k3d load balancer. HTTP-01 needs the verified route described below. Neither path creates a fixed cert-manager development subdomain.
Environment prerequisites¶
Use the umbrella’s development environment tooling or an existing disposable Big Bang cluster, and its deployment guidance for Flux, registry credentials, and bootstrap. Select those references at the Big Bang release under test rather than copying bootstrap commands here.
Confirm kubeconfig, namespace, issuer scope, FQDN, ACME directory, and credential retention before mutation. Use a pushed package ref that Flux can read and keep Registry1 credentials in the environment’s protected/encrypted configuration. Retain all package Iron Bank images, including the temporary ACME solver, to exercise the real registry path. Do not commit provider tokens or account keys.
Install Standard Big Bang and cert-manager¶
Use the package’s standard or
ambient umbrella selection. Select a feature
source using the umbrella’s package branch test workflow.
The canonical path is packages.certManager with the v1 discriminator, not
packages.cert-manager. Do not set a branch and tag together; clear the inherited
tag when selecting a branch. In the legacy contract use certManager instead.
Wait for Flux reconciliation and verify the release, deployments, and webhook
admission before the issuer upgrade. The selections enable self-signed management
with createOnInstall: false; follow the staged procedure.
Keep Istio, Kyverno Policies, monitoring, and private-registry handling active.
Use only the narrowly scoped policy exceptions required by the chosen development
environment, not a blanket cert-manager namespace exemption.
Create the DNS Credential Secret¶
Create the credential separately after the namespace exists. The illustrative
Cloudflare configuration below references cloudflare-api-token with key
api-token in cert-manager for a namespaced issuer. Use the
provider’s requirements
and restrict access to the delegated zone. For other providers or cluster-scoped
issuers, verify Secret placement and identity permissions before proceeding.
Enable the Staging DNS-01 Issuer¶
Apply an uncommitted staging override after the webhook is ready. Replace the example email/zone and provider configuration; this file contains only Secret references, not credentials:
packageConfiguration:
version: v1
packages:
certManager:
values:
bb-common:
routes:
outbound:
cloudflare-api:
enabled: true
hosts:
- api.cloudflare.com
issuers:
letsEncrypt:
enabled: true
environment: staging
email: platform-team@example.mil
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
selector:
dnsZones:
- acme-test.example.mil
Use this override last in the environment’s values ordering and reconcile the umbrella upgrade. The chart registers the ACME directory in sidecar mode and adds controller egress when enabled; provider API registration and suitable CIDRs remain environment-specific. Follow network prerequisites. Confirm the intended kubeconfig and wait for the default namespaced staging issuer before the request. These commands inspect/wait for existing resources; they do not install or enable the issuer:
kubectl config current-context
kubectl -n cert-manager wait --for=condition=Ready --timeout=300s \
issuer/cert-manager-letsencrypt-staging
kubectl -n cert-manager get issuer cert-manager-letsencrypt-staging \
-o jsonpath='{.spec.acme.server}{"\n"}'
Expect https://acme-staging-v02.api.letsencrypt.org/directory, not production.
Adjust the name and namespace for the configured release. For an intentionally
cluster-scoped issuer, wait on clusterissuer/<actual-name> instead.
Request and Verify the Certificate¶
From the package root, run the script against a controlled, non-wildcard name in the delegated zone:
RUN_ID=acme-doc-check \
ACME_TEST_FQDN=cert-manager.acme-test.example.mil \
ACME_ISSUER_NAME=cert-manager-letsencrypt-staging \
ACME_ISSUER_KIND=Issuer \
NAMESPACE=cert-manager \
bash chart/tests/scripts/cert_manager.sh
The script also exercises self-signed issuance and the negative missing-issuer
case. For a configured packaged self-signed issuer, set REQUIRE_BUILTIN_ISSUER=true
to require it, and set BUILTIN_ISSUER_NAME/BUILTIN_ISSUER_KIND to its identity.
For custom releases also set INSTANCE_NAME, WEBHOOK_SERVICE, and
WEBHOOK_CA_SECRET as needed; Helm’s test wiring supplies them automatically.
ACME_EXPECTED_SERVER defaults to Let’s Encrypt staging and the script verifies
the selected issuer’s ACME directory. Set it explicitly only for an approved
custom test server, not production. Use a separate DNS-01 test for wildcard names.
The script creates run-scoped resources and deletes its test Certificates,
Secrets, and temporary self-signed issuer on exit, not the packaged issuers or
ACME account Secret. Set RUN_ID for a recognizable run and capture evidence
while it executes; use a separately managed retained Certificate if durable
evidence or renewal testing is needed.
Capture the test Certificate, its CertificateRequest, related Order/Challenge,
events, controller/webhook logs, and Kyverno reports. Use the Certificate’s
cert-manager.io/test-run label and resource ownership to isolate the run;
related resources need not all have the same label. Redact credentials and keys.
Investigate admission, DNS propagation, provider access, image pulls, and mesh
egress before requesting narrow policy changes. Do not disable Kyverno.
For the script invocation above, use another terminal with the same kubeconfig and run ID while the script executes. These read-only commands inspect its ACME Certificate and show owner names to help locate related resources:
RUN_ID=acme-doc-check
kubectl -n cert-manager get certificates -l "cert-manager.io/test-run=$RUN_ID"
kubectl -n cert-manager describe certificate "test-acme-cert-$RUN_ID"
kubectl -n cert-manager get certificaterequests,orders,challenges \
-o custom-columns='KIND:.kind,NAME:.metadata.name,OWNER:.metadata.ownerReferences[*].name'
Follow the Certificate -> CertificateRequest -> Order -> Challenge owner chain; do not assume every resource inherited the test label. The owner listing can include other namespace resources: select only those belonging to this run and do not publish unrelated certificate names or DNS metadata. For a failed run, inspect the exact related objects and recent controller errors:
REQUEST_NAME="replace-with-this-run-certificaterequest-name"
ORDER_NAME="replace-with-this-run-order-name"
CHALLENGE_NAME="replace-with-this-run-challenge-name"
kubectl -n cert-manager describe issuer cert-manager-letsencrypt-staging
kubectl -n cert-manager describe certificaterequest "$REQUEST_NAME"
kubectl -n cert-manager describe order "$ORDER_NAME"
kubectl -n cert-manager describe challenge "$CHALLENGE_NAME"
kubectl -n cert-manager logs deployment/cert-manager -c upstream-controller --since=10m --tail=200
Adjust the Deployment/container for a custom release and inspect admission events and Kyverno reports for the exact failing resource. These commands do not read Secret data; still redact sensitive metadata and logs before sharing them. Use a unique lowercase alphanumeric/dash run ID for each concurrent run and keep it consistent between the script and inspection terminal. The script normalizes and truncates IDs to 20 characters and may clean its resources before inspection.
HTTP-01 With Big Bang Istio¶
Public DNS must resolve the exact requested FQDN to a reachable HTTP listener on
TCP 80. Workstation tunnels, /etc/hosts, private MetalLB addresses, and
sshuttle are not public Let’s Encrypt reachability.
An Istio networking.istio.io/Gateway and a Gateway API Gateway are different
APIs. cert-manager’s automatic solvers create Kubernetes Ingress or Gateway API
HTTPRoute resources, not routes on an arbitrary existing Istio Gateway. Choose
and verify one path:
- An explicitly configured Kubernetes Ingress controller with an appropriate
IngressClass. Do not assume
ingressClassName: istiomakes Istio reconcile it. - A public Gateway API Gateway with a port
80listener andallowedRoutespermitting the challenge namespace, plus cert-manager’s version-specific Gateway support. - Environment-owned automation connecting the temporary solver Service to a verified public route.
Keep /.well-known/acme-challenge/ reachable through load balancers, firewalls,
and gateway routing. Namespace owners must provide solver ingress on TCP 8089
and review mesh enrollment/authorization and Kyverno compatibility. The ingress
solver’s opt-out metadata does not apply automatically to Gateway API solvers.
Use the matching Ingress solver example or
Gateway API solver example, staged upgrade,
and script. Evidence must include the generated route and successful public
challenge response as well as Certificate, Order, and Challenge status.
Production and Ingress Certificates¶
A test certificate in cert-manager does not replace the gateway’s TLS Secret.
Separately design Secret placement/distribution, gateway references, renewal
ownership, and trust before production use. Development ingress certificates are
not the ACME test certificate and do not satisfy its challenge. After staging,
review production DNS names, account-key retention, rate limits, and gateway
rollout separately; do not switch this test to production for browser trust.
Cleanup¶
The script cleans its run-scoped test resources. Remove any separately retained
test Certificate/Secret and provider credential only after confirming exact names
and shared use. Disable the test issuer in desired state before deliberately
discarding its staging account key, or reconciliation can create another account.
Follow account-key retention before namespace
deletion. Use the matching umbrella tooling to remove provisioned infrastructure;
local k3d cluster delete alone may leave cloud resources behind.
Acceptance Criteria¶
- Staging, scoped credentials, and normal registry/mesh/network/Kyverno controls.
- Issuer and Certificate
Ready=True, related Order and Challengevalid. - Evidence of provider/solver reachability without admission or image-pull failures.
- Redacted Certificate/Request/Order/Challenge/event/policy evidence and deliberate cleanup/account retention. Renewal and external trust need separate evidence.
Pipeline Decision¶
Keep external ACME acceptance testing separate from deterministic package CI. Only run it in an explicitly provisioned environment with DNS ownership, credential handling, and cleanup agreed beforehand.