Phase 6 — TLS with cert-manager (Bootstrap CA)¶
Configure cert-manager to act as its own CA using the self-signed bootstrap approach. No external PKI or Vault dependency for TLS certificate issuance.
Approach Decision¶
| Option | Chosen | Reason |
|---|---|---|
| Enterprise CA (external PKI) | ❌ | No access to ca.key — enterprise PKI team controls it |
| Vault PKI | ❌ | Vault outage = cert renewal blocked → service TLS breaks |
| cert-manager Bootstrap (self-signed CA) | ✅ | No external dependency, cert issuance always available |
Architecture¶
┌─────────────────────────────────────────────────────────────────────┐
│ cert-manager (bootstrap) │
│ │
│ ClusterIssuer/selfsigned-bootstrapper (self-signed — one-time) │
│ │ │
│ │ issues CA cert │
│ ▼ │
│ Secret/internal-ca-secret (cert-manager ns) │
│ tls.crt = CA cert │ tls.key = CA private key │
│ │ │
│ ▼ │
│ ClusterIssuer/internal-ca-issuer │
│ │ │
│ ┌────────────┴──────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ Secret/wildcard-tls-secret Per-app certs (optional) │
│ (cert-manager ns) cert-manager auto-issues on demand │
└──────────┬──────────────────────────────────────────────────────────┘
│
│ One cert — two consumers
│
┌──────┴──────────────────────────────────────┐
│ │
▼ ▼
┌───────────────────────────┐ ┌────────────────────────────────┐
│ HAProxy (external) │ │ K8s Nginx Ingress Controller │
│ Ceph MON nodes │ │ (in-cluster) │
│ 100.115.1.10 (VIP) │ │ │
│ │ │ *.mod.auh1.dev.dir │
│ s3.cl1.sq4.aegis.int │ │ *.cl1.sq4.aegis.internal │
│ ────────────────────── │ │ ┌─────────────────────────┐ │
│ TLS termination for RGW │ │ │ ask.mod.auh1.dev.dir │ │
│ S3 object storage │ │ │ api.mod.auh1.dev.dir │ │
│ │ │ │ argocd.mod.auh1.dev.dir │ │
│ ⚠ Manual push once/year │ │ │ gitlab.mod.auh1.dev.dir │ │
│ Step 6.6 — scp haproxy │ │ │ vault.mod.auh1.dev.dir │ │
│ .pem to all 3 MON nodes │ │ │ harbor.mod.auh1.dev.dir │ │
│ │ │ │ argocd.cl1.sq4.aegis.int │ │
│ │ │ │ gitlab.cl1.sq4.aegis.int │ │
│ │ │ │ vault.cl1.sq4.aegis.int │ │
│ │ │ │ harbor.cl1.sq4.aegis.int │ │
│ │ │ └─────────────────────────┘ │
└───────────────────────────┘ │ │
│ ✅ Auto-renewed by cert-manager│
│ Zero manual steps │
└────────────────────────────────┘
Certificate Distribution Summary¶
| Consumer | Cert Delivery | Renewal | Manual Step |
|---|---|---|---|
| HAProxy (RGW S3) | scp haproxy.pem → all 3 MON nodes |
cert-manager auto-renews K8s secret | Re-push PEM once/year |
| Nginx Ingress (K8s apps) | secretName: wildcard-tls-secret in Ingress spec |
✅ Fully automatic | None |
| Per-app cert (optional) | cert-manager.io/cluster-issuer annotation |
✅ Fully automatic | None |
Known Risks & Mitigations¶
Known Risks — Read Before Proceeding
| Risk | Severity | Mitigation |
|---|---|---|
| CA private key in etcd | Medium | K8s Secret in cert-manager namespace — protected by RBAC. Not internet-facing. Restrict get secret in this namespace to cluster-admins only. |
| No certificate revocation | Low | Cannot revoke a compromised leaf cert — must wait for expiry. Set short TTL (90d) for sensitive services if needed. |
| No audit trail | Low | No log of which certs were issued. Mitigate with K8s API server audit logging. |
| CA cert expiry | High | CA set to 10 years. If it expires, ALL certs become untrusted immediately. Monitor CA expiry — set a calendar reminder. |
| Wildcard cert on HAProxy — manual renewal | Low | Cert expires in 1 year. cert-manager auto-renews in K8s but the copy on HAProxy must be re-pushed manually. Set a calendar reminder at 11 months. |
| Client trust distribution | Medium | All clients (browsers, nodes, pods) must trust the bootstrap CA cert — distribute modgpt-ca.crt manually. |
Why not Vault PKI?
Vault PKI is more secure (CA key never leaves Vault, full revocation, audit trail). However: if Vault is down, cert-manager cannot renew expiring certs — services lose TLS. For this deployment, cert availability > cert auditability. Vault PKI remains the production-correct long-term target once Vault HA is proven stable.
Future migration path
When enterprise CA access is granted or Vault PKI is adopted:
- Create new ClusterIssuer pointing to enterprise CA or Vault
- cert-manager re-issues all K8s certs on next renewal cycle — automatic
- Re-push wildcard cert to HAProxy once
- Remove bootstrap CA secret
No downtime required.
Prerequisites¶
| Item | Required state |
|---|---|
| cert-manager | Installed, pods Running in cert-manager namespace |
| kubectl | Working from bastion |
| SSH access | bastion → Ceph MON nodes ([email protected]/12/13) |
| HAProxy | Installed on Ceph MON nodes (see Ceph Infrastructure → RGW HA) |
Step 6.1 — Create Self-Signed Bootstrap Issuer¶
One-time bootstrapper — used only to issue the CA cert, nothing else.
kubectl apply -f - << 'EOF'
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-bootstrapper
spec:
selfSigned: {}
EOF
kubectl get clusterissuer selfsigned-bootstrapper
# Expected: READY=True
Step 6.2 — Issue the Internal CA Certificate¶
kubectl apply -f - << 'EOF'
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: internal-ca
namespace: cert-manager
spec:
isCA: true
secretName: internal-ca-secret
duration: 87600h # 10 years
renewBefore: 720h
subject:
organizations:
- "ai71"
commonName: "modgpt"
issuerRef:
name: selfsigned-bootstrapper
kind: ClusterIssuer
EOF
# Wait for CA cert
kubectl get certificate internal-ca -n cert-manager -w
# Expected: READY=True
# Verify it is a CA cert
kubectl get secret internal-ca-secret -n cert-manager \
-o jsonpath='{.data.tls\.crt}' | base64 -d \
| openssl x509 -noout -subject -dates -ext basicConstraints
# Expected:
# subject=CN=modgpt, O=ai71
# CA:TRUE
# notAfter=<10 years from now>
Step 6.3 — Create the CA ClusterIssuer¶
All applications and services use this issuer to request certificates.
kubectl apply -f - << 'EOF'
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: internal-ca-issuer
spec:
ca:
secretName: internal-ca-secret
EOF
kubectl get clusterissuer internal-ca-issuer
# Expected: READY=True
Step 6.4 — Extract CA Certificate for Trust Distribution¶
Clients need the CA cert to verify any cert issued by this issuer.
# Extract CA cert
kubectl get secret internal-ca-secret -n cert-manager \
-o jsonpath='{.data.tls\.crt}' | base64 -d > /tmp/modgpt-ca.crt
openssl x509 -noout -subject -dates -in /tmp/modgpt-ca.crt
Push CA to all nodes (K8s + Ceph MON)¶
for node in 100.115.1.11 100.115.1.12 100.115.1.13 100.115.1.14 100.115.1.15 100.115.1.16 100.115.1.17; do
scp /tmp/modgpt-ca.crt cloud-user@${node}:/tmp/
ssh cloud-user@${node} "sudo cp /tmp/modgpt-ca.crt /usr/local/share/ca-certificates/modgpt-ca.crt \
&& sudo update-ca-certificates"
echo "✅ ${node} trusts modgpt CA"
done
Trust in browser (Mac)¶
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain /tmp/modgpt-ca.crt
Verify CA is trusted on every node¶
Check the CA file is present and valid on all nodes:
# Check file exists
for node in 100.115.1.11 100.115.1.12 100.115.1.13 100.115.1.14 100.115.1.15 100.115.1.16 100.115.1.17; do
echo -n "Node $node: "
ssh cloud-user@$node "ls /usr/local/share/ca-certificates/modgpt-ca.crt 2>/dev/null \
&& echo '✅ present' || echo '❌ missing'"
done
# Verify CA cert is valid and trusted
for node in 100.115.1.11 100.115.1.12 100.115.1.13 100.115.1.14 100.115.1.15 100.115.1.16 100.115.1.17; do
echo -n "Node $node: "
ssh cloud-user@$node "openssl verify \
-CAfile /usr/local/share/ca-certificates/modgpt-ca.crt \
/usr/local/share/ca-certificates/modgpt-ca.crt 2>/dev/null \
&& echo '✅ CA valid' || echo '❌ CA not trusted'"
done
Expected output:
Node 100.115.1.11: ✅ CA valid
Node 100.115.1.12: ✅ CA valid
Node 100.115.1.13: ✅ CA valid
Node 100.115.1.14: ✅ CA valid
Node 100.115.1.15: ✅ CA valid
Node 100.115.1.16: ✅ CA valid
Node 100.115.1.17: ✅ CA valid
Step 6.4b — Trust on Bastion Node¶
All CLI tools on the bastion (curl, helm, argocd) call https://gitlab.cl1.sq4.aegis.internal
and other internal HTTPS endpoints. Without the CA, every call needs --insecure or --skip-tls-verify.
# On bastion (cloud-user@bastion1)
# Get the CA cert from the K8s secret or copy from where it was extracted
kubectl get secret internal-ca-secret -n cert-manager \
-o jsonpath='{.data.tls\.crt}' | base64 -d > /tmp/modgpt-ca.crt
# Add to system trust store
sudo cp /tmp/modgpt-ca.crt /usr/local/share/ca-certificates/modgpt-ca.crt
sudo update-ca-certificates
# Verify
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt /tmp/modgpt-ca.crt
# Expected: /tmp/modgpt-ca.crt: OK
# Test — curl to GitLab should work WITHOUT --insecure
curl -s https://gitlab.cl1.sq4.aegis.internal/-/readiness | python3 -m json.tool | head -5
# Expected: {"master_check":[{"status":"ok"}],...}
Keep the cert on bastion for reuse:
Step 6.4c — Trust in ArgoCD Cert Store¶
ArgoCD repo-server connects to https://gitlab.cl1.sq4.aegis.internal to pull GitOps repos
and https://gitlab.cl1.sq4.aegis.internal/api/v4/projects/.../packages/helm/... for Helm charts.
Without the CA, every connection fails TLS verification.
# Add modgpt CA to ArgoCD TLS cert store (keyed by hostname)
argocd cert add-tls gitlab.cl1.sq4.aegis.internal \
--from ~/certificates/modgpt-ca.crt \
--grpc-web --upsert
# Cover the Helm registry hostname (same cert, same host)
# No separate entry needed — hostname is the same
# Verify
argocd cert list --grpc-web | grep gitlab
Expected: gitlab.cl1.sq4.aegis.internal https <CN=modgpt>
ArgoCD cert store vs --insecure
argocd cert add-tls stores the CA in the argocd-tls-certs-cm ConfigMap.
ArgoCD repo-server loads it at startup. After adding, repos connecting to that
hostname no longer need --insecure-skip-server-verification.
Check the ConfigMap directly:
Step 6.4d — Trust in GitLab (Outbound HTTPS)¶
GitLab makes outbound HTTPS calls when: - Connecting to Vault for secrets - Webhook calls to other internal services - GitLab Runners connecting to internal registries
Add the modgpt CA to GitLab via its Helm values global.certificates.customCACerts:
# Extract the GitLab Helm release values
helm get values gitlab -n gitlab > /tmp/gitlab-current-values.yaml
# Check if customCACerts is already set
grep -A5 customCACerts /tmp/gitlab-current-values.yaml || echo "Not set yet"
Add the CA via a Kubernetes secret and reference it in GitLab:
# Create a secret with the modgpt CA cert in the gitlab namespace
kubectl create secret generic modgpt-ca \
-n gitlab \
--from-file=modgpt-ca.crt=~/certificates/modgpt-ca.crt \
--dry-run=client -o yaml | kubectl apply -f -
In the GitLab Helm values (in GitOps — update values-mod-auh1-dev-ask.yaml or GitLab's values file):
Requires GitLab Helm upgrade to take effect
The customCACerts value causes GitLab to mount the secret and inject the CA into
its internal containers (webservice, sidekiq, registry). Apply via helm upgrade or
through the ArgoCD Application sync (if GitLab is managed by ArgoCD).
If GitLab is NOT managed by ArgoCD in this deployment, run:
helm upgrade gitlab gitlab/gitlab -n gitlab \
--reuse-values \
--set "global.certificates.customCACerts[0].secret=modgpt-ca"
In practice here: GitLab outbound HTTPS calls to Vault/internal services are not required until Phase 7 (ESO + Vault). Skip this step until then — it's documented here for completeness.
Step 6.5 — Issue Wildcard Certificate¶
One wildcard cert covers all services — K8s Ingress, HAProxy VIP, and any future services.
kubectl apply -f - << 'EOF'
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-tls
namespace: cert-manager
spec:
secretName: wildcard-tls-secret
duration: 8760h # 1 year
renewBefore: 720h # cert-manager auto-renews in K8s — triggers HAProxy re-push reminder
issuerRef:
name: internal-ca-issuer
kind: ClusterIssuer
dnsNames:
- "*.cl1.sq4.aegis.internal" # covers all subdomains
- "cl1.sq4.aegis.internal" # bare domain
- "*.dev.dir" # additional TLD
- "dev.dir" # bare domain
- "*.mod.auh1.dev.dir" # ASK platform domain
- "mod.auh1.dev.dir" # bare domain
- "s3.cl1.sq4.aegis.internal" # RGW S3 endpoint
EOF
kubectl get certificate wildcard-tls -n cert-manager -w
# Expected: READY=True
# Verify SAN
kubectl get secret wildcard-tls-secret -n cert-manager \
-o jsonpath='{.data.tls\.crt}' | base64 -d \
| openssl x509 -noout -text | grep -A6 "Subject Alternative"
# Expected:
# DNS:*.cl1.sq4.aegis.internal
# DNS:cl1.sq4.aegis.internal
# DNS:*.dev.dir
# DNS:dev.dir
# DNS:*.mod.auh1.dev.dir
# DNS:mod.auh1.dev.dir
# DNS:s3.cl1.sq4.aegis.internal
Step 6.6 — Push Wildcard Cert to HAProxy (Manual — Once per Year)¶
Why manual?
HAProxy runs on external Ceph nodes outside the cluster. A wildcard cert with 1-year TTL only needs to be pushed once. cert-manager auto-renews the K8s Secret — when renewal happens, re-run this step. Set a calendar reminder at 11 months.
# Extract cert and key from K8s
kubectl get secret wildcard-tls-secret -n cert-manager \
-o jsonpath='{.data.tls\.crt}' | base64 -d > /tmp/tls.crt
kubectl get secret wildcard-tls-secret -n cert-manager \
-o jsonpath='{.data.tls\.key}' | base64 -d > /tmp/tls.key
# Combine into HAProxy PEM format (cert + key in one file)
cat /tmp/tls.crt /tmp/tls.key > /tmp/haproxy.pem
# Create cert directory and push to all 3 Ceph MON nodes
for node in 100.115.1.11 100.115.1.12 100.115.1.13; do
ssh cloud-user@${node} "sudo mkdir -p /opt/certs/rgw"
scp /tmp/haproxy.pem cloud-user@${node}:/tmp/haproxy.pem
ssh cloud-user@${node} "sudo mv /tmp/haproxy.pem /opt/certs/rgw/haproxy.pem \
&& sudo chmod 600 /opt/certs/rgw/haproxy.pem \
&& sudo systemctl reload haproxy"
echo "✅ ${node} updated"
done
# Clean up sensitive files from bastion
rm /tmp/tls.crt /tmp/tls.key /tmp/haproxy.pem
echo "⏰ Set calendar reminder: re-push wildcard cert in 11 months"
Verify TLS on VIP:
curl -sv --cacert /tmp/modgpt-ca.crt https://100.115.1.10:7480 2>&1 \
| grep -E "subject|issuer|< HTTP"
# Expected: HTTP/1.1 200, subject=*.cl1.sq4.aegis.internal
Step 6.7 — Configure HAProxy for TLS¶
# Run on all 3 Ceph MON nodes
for node in 100.115.1.11 100.115.1.12 100.115.1.13; do
ssh cloud-user@${node} "sudo tee /etc/haproxy/haproxy.cfg > /dev/null << 'HAEOF'
global
log /dev/log local0
maxconn 4096
user haproxy
group haproxy
daemon
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5s
timeout client 30s
timeout server 30s
frontend rgw_frontend
bind *:7480 ssl crt /opt/certs/rgw/haproxy.pem
default_backend rgw_backends
backend rgw_backends
balance roundrobin
option httpchk GET /
http-check expect status 200
server rgw1 100.115.1.11:7481 check inter 2s fall 2 rise 2
server rgw2 100.115.1.12:7481 check inter 2s fall 2 rise 2
server rgw3 100.115.1.13:7481 check inter 2s fall 2 rise 2
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
stats auth admin:admin
HAEOF
sudo systemctl reload haproxy && echo '✅ HAProxy reloaded on ${node}'"
done
Step 6.8 — Enable TLS on K8s App Ingresses¶
For K8s apps, cert-manager issues a dedicated cert per service via the cluster-issuer annotation.
cert-manager handles issuance and renewal automatically — no manual steps.
Production Recommendation — Option B
Use Option B (per-service cert via annotation) for all ingresses. Each service gets an isolated cert — smaller blast radius, auto-renewed, no secret copying.
Option A — Not Recommended for Production
Copying the wildcard secret across namespaces creates secret sprawl and requires manual re-copy on every wildcard renewal. Use only as a temporary workaround.
Option A — Reference wildcard secret directly ⚠️ Non-production only¶
# Copy wildcard secret to target namespace
kubectl get secret wildcard-tls-secret -n cert-manager -o yaml \
| sed 's/namespace: cert-manager/namespace: gitlab/' \
| kubectl apply -f -
Option B — cert-manager annotation (per-service cert) ✅ Production¶
cert-manager automatically issues and renews a dedicated cert for each ingress hostname.
metadata:
annotations:
cert-manager.io/cluster-issuer: "internal-ca-issuer"
spec:
tls:
- hosts:
- gitlab.cl1.sq4.aegis.internal
secretName: gitlab-tls-secret # cert-manager creates this automatically
Patch Commands — All Ingresses¶
Apply TLS to every existing ingress using Option B:
ArgoCD¶
kubectl patch ingress argocd-server -n argocd --type=merge -p '{
"metadata": {
"annotations": {
"cert-manager.io/cluster-issuer": "internal-ca-issuer",
"nginx.ingress.kubernetes.io/ssl-redirect": "true"
}
},
"spec": {
"tls": [{
"hosts": [
"argocd.cl1.sq4.aegis.internal",
"argocd.mod.auh1.dev.dir"
],
"secretName": "argocd-tls-secret"
}]
}
}'
GitLab¶
kubectl patch ingress gitlab-webservice-default -n gitlab --type=merge -p '{
"metadata": {
"annotations": {
"cert-manager.io/cluster-issuer": "internal-ca-issuer",
"nginx.ingress.kubernetes.io/ssl-redirect": "true"
}
},
"spec": {
"tls": [{
"hosts": [
"gitlab.cl1.sq4.aegis.internal",
"gitlab.mod.auh1.dev.dir"
],
"secretName": "gitlab-tls-secret"
}]
}
}'
GitLab Registry¶
kubectl patch ingress gitlab-registry -n gitlab --type=merge -p '{
"metadata": {
"annotations": {
"cert-manager.io/cluster-issuer": "internal-ca-issuer"
}
},
"spec": {
"tls": [{
"hosts": [
"registry.cl1.sq4.aegis.internal"
],
"secretName": "gitlab-registry-tls-secret"
}]
}
}'
GitLab KAS¶
kubectl patch ingress gitlab-kas -n gitlab --type=merge -p '{
"metadata": {
"annotations": {
"cert-manager.io/cluster-issuer": "internal-ca-issuer"
}
},
"spec": {
"tls": [{
"hosts": [
"kas.cl1.sq4.aegis.internal"
],
"secretName": "gitlab-kas-tls-secret"
}]
}
}'
Monitoring (Grafana, Prometheus, Alertmanager)¶
for ingress in rancher-monitoring-grafana rancher-monitoring-prometheus rancher-monitoring-alertmanager; do
host=$(kubectl get ingress $ingress -n cattle-monitoring-system -o jsonpath='{.spec.rules[0].host}')
kubectl patch ingress $ingress -n cattle-monitoring-system --type=merge -p "{
\"metadata\": {
\"annotations\": {
\"cert-manager.io/cluster-issuer\": \"internal-ca-issuer\"
}
},
\"spec\": {
\"tls\": [{
\"hosts\": [\"${host}\"],
\"secretName\": \"${ingress}-tls-secret\"
}]
}
}"
echo "✅ TLS patched: $ingress"
done
Verify Certs Issued¶
# Check all certificates
kubectl get certificate -A
# Expected: all READY=True
# NAME READY SECRET AGE
# argocd-tls-secret True argocd-tls-secret 30s
# gitlab-tls-secret True gitlab-tls-secret 30s
# gitlab-registry-tls-secret True gitlab-registry-tls-secret 30s
# ...
Test in browser:
https://argocd.cl1.sq4.aegis.internal ← should show padlock 🔒
https://argocd.mod.auh1.dev.dir ← should show padlock 🔒
https://gitlab.cl1.sq4.aegis.internal ← should show padlock 🔒
Browser shows untrusted cert?
The CA cert (modgpt-ca.crt) must be trusted in your browser/OS keychain.
See Step 6.4 — Trust on Mac to add it.
Renewal — What Happens When Cert Expires¶
cert-manager auto-renews wildcard cert 30 days before expiry
│
▼
K8s Secret (wildcard-tls-secret) updated automatically
K8s Ingress certs — renewed automatically ✅
│
▼
HAProxy cert on Ceph nodes — still has OLD cert ⚠️
│
▼
Re-run Step 6.6 (takes ~5 minutes) ← only manual step per year
Annual reminder — re-push wildcard cert to HAProxy
cert-manager renews the K8s wildcard cert automatically. HAProxy on the Ceph nodes must be updated manually — re-run Step 6.6. Set a calendar reminder at 11 months from today.
Summary Checklist¶
cert-manager Bootstrap¶
- [ ]
ClusterIssuer/selfsigned-bootstrapper— READY=True - [ ]
Certificate/internal-ca— READY=True,isCA=true, 10yr expiry - [ ]
ClusterIssuer/internal-ca-issuer— READY=True
Trust Distribution¶
- [ ]
modgpt-ca.crtadded to all K8s nodes —update-ca-certificatesdone - [ ]
modgpt-ca.crtadded to all Ceph MON nodes —update-ca-certificatesdone - [ ]
modgpt-ca.crtadded to bastion (/usr/local/share/ca-certificates/) —update-ca-certificatesdone - [ ]
curl https://gitlab.cl1.sq4.aegis.internalworks on bastion without--insecure - [ ]
modgpt-ca.crtadded to ArgoCD cert store (argocd cert add-tls gitlab.cl1.sq4.aegis.internal) - [ ] ArgoCD repo list shows
STATUS: Successfulfor GitLab repo (no TLS errors) - [ ]
modgpt-ca.crttrusted in Mac browser keychain (Keychain Access → System → Always Trust) - [ ] GitLab
modgpt-casecret created ingitlabnamespace (for Phase 7 Vault integration)
Wildcard Certificate¶
- [ ]
Certificate/wildcard-tls— READY=True, SAN includes*.cl1.sq4.aegis.internal,*.dev.dir,*.mod.auh1.dev.dir,s3.cl1.sq4.aegis.internal - [ ]
haproxy.pempushed to all 3 Ceph nodes - [ ]
sudo systemctl reload haproxydone on all 3 Ceph nodes - [ ]
curl --cacert modgpt-ca.crt https://100.115.1.10:7480→ HTTP 200 - [ ] ⏰ Calendar reminder set — re-push cert in 11 months
K8s App TLS¶
- [ ] GitLab Ingress — TLS enabled
- [ ] ArgoCD Ingress — TLS enabled
- [ ]
global.ingress.tls.enabled: truein GitDeployed values → helm upgrade
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
ClusterIssuer READY=False |
internal-ca-secret not in cert-manager namespace |
Check namespace |
Certificate READY=False |
ClusterIssuer not ready | kubectl describe certificate → check events |
curl: SSL certificate verify failed |
CA not trusted on client | Add modgpt-ca.crt to trust store + update-ca-certificates |
| HAProxy TLS handshake fails | haproxy.pem missing or wrong format |
Re-run Step 6.6 — ensure cat tls.crt tls.key > haproxy.pem |
| Wildcard cert not covering a hostname | Hostname outside *.cl1.sq4.aegis.internal and *.dev.dir |
Add explicit SAN to Certificate CR |
| HAProxy cert expired (browser error) | Forgot annual re-push | Re-run Step 6.6 |