TLS Certificate
Guide for renewing the DigiCert TLS certificate for lcmd-app.epfl.ch
The TLS certificate for lcmd-app.epfl.ch is issued by DigiCert (Basic OV) and must be renewed manually. The certificate and private key are stored as a SOPS-encrypted Kubernetes secret (lcmd-app-tls) and terminated by Traefik at the ingress.
Certificate details
| Field | Value |
|---|---|
| Common Name | lcmd-app.epfl.ch |
| Issuer | DigiCert Inc |
| Account ID | 1611318 |
| Organization | EPFL |
| Contact | lcmd-app@groupes.epfl.ch |
Renewal process
Request the certificate
Ask the EPFL IT security team (currently Florent Rauth — rauth@epfl.ch) to renew using the same CSR as before.
Once approved, you'll receive a DigiCert zip containing:
lcmd-app_epfl_ch.crt— server certificateDigiCertCA.crt— intermediate CATrustedRoot.crt— root CA
Run the renewal script
just renew_tls_cert ~/Downloads/lcmd-app_epfl_ch_<order_number>.zipThe script extracts the chain, fetches the existing private key from the cluster, verifies the cert matches the key, encrypts the new secret with SOPS → tls-secret.enc.yaml, and applies it to the cluster.
Commit and push
git add infrastructure/kubernetes/app/overlays/prod/tls-secret.enc.yaml
git commit -m "chore(infra): renew TLS certificate"
git push origin mainArgoCD auto-syncs.
Verify
echo | openssl s_client -connect lcmd-app.epfl.ch:443 -servername lcmd-app.epfl.ch 2>/dev/null \
| openssl x509 -noout -subject -dates -serialRun the EPFL security scan: secure-it.epfl.ch/testssl2/testssl.php.
Prerequisites
sops,kubectl,openssl,unzip- SOPS age private key in
~/.config/sops/age/keys.txt(get from 1Password) kubectlaccess to the prod namespace (see Kubernetes Setup)- Connected to EPFL network (or VPN)
The SOPS_AGE_KEY_FILE environment variable must point to ~/.config/sops/age/keys.txt. Add export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt to your shell profile if SOPS can't find the key automatically.
Architecture
DigiCert zip
├── lcmd-app_epfl_ch.crt ──┐
└── DigiCertCA.crt ──┴── fullchain.pem
│
┌──────────┘
▼
tls-secret.enc.yaml (SOPS + age)
│
ArgoCD decrypt
│
▼
K8s Secret: lcmd-app-tls
(tls.crt + tls.key)
│
▼
Traefik Ingress
(TLS termination)Cipher suite
The Traefik TLSOption (strict-tls) restricts connections to PFS + AEAD ciphers only:
| Cipher | Protocol |
|---|---|
TLS_AES_128_GCM_SHA256 | TLS 1.3 |
TLS_AES_256_GCM_SHA384 | TLS 1.3 |
TLS_CHACHA20_POLY1305_SHA256 | TLS 1.3 |
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 | TLS 1.2 |
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 | TLS 1.2 |
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 | TLS 1.2 |
Troubleshooting
Certificate/key mismatch: If the script reports a mismatch, the CSR used for the renewal differs from the deployed key. You'll need to generate a new key + CSR and request a new certificate.
SOPS MAC error in ArgoCD: If ArgoCD fails with a MAC integrity error, re-encrypt the affected file:
cd infrastructure/kubernetes/app/overlays/prod
SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt sops --decrypt --ignore-mac <file>.enc.yaml > <file>.yaml
SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt sops --encrypt <file>.yaml > <file>.enc.yaml
rm <file>.yamlArgoCD not syncing: Force a refresh:
kubectl patch application lcmd-app -n argocd \
--type merge -p '{"metadata":{"annotations":{"argocd.argoproj.io/refresh":"hard"}}}'