SSL Certificate Management
Overview
Implement automated SSL/TLS certificate management across infrastructure, including provisioning, renewal, monitoring, and secure distribution to services.
When to Use
- HTTPS/TLS enablement
- Certificate renewal automation
- Multi-domain certificate management
- Wildcard certificate handling
- Certificate monitoring and alerts
- Zero-downtime certificate rotation
- Internal PKI management
Implementation Examples
1. Let's Encrypt with Cert-Manager
# cert-manager-setup.yaml apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: [email protected] privateKeySecretRef: name: letsencrypt-prod solvers: # HTTP-01 solver for standard domains - http01: ingress: class: nginx selector: dnsNames: - "myapp.com" - "www.myapp.com" # DNS-01 solver for wildcard domains - dns01: route53: region: us-east-1 hostedZoneID: Z1234567890ABC accessKeyID: AKIAIOSFODNN7EXAMPLE secretAccessKeySecretRef: name: route53-credentials key: secret-access-key selector: dnsNames: - "*.myapp.com" --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: myapp-tls namespace: production spec: secretName: myapp-tls-secret issuerRef: name: letsencrypt-prod kind: ClusterIssuer commonName: myapp.com dnsNames: - myapp.com - www.myapp.com - api.myapp.com - "*.myapp.com" duration: 2160h # 90 days renewBefore: 720h # 30 days before expiry --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp namespace: production annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" nginx.ingress.kubernetes.io/ssl-redirect: "true" spec: ingressClassName: nginx tls: - hosts: - myapp.com - www.myapp.com secretName: myapp-tls-secret rules: - host: myapp.com http: paths: - path: / pathType: Prefix backend: service: name: myapp port: number: 80
2. AWS ACM Certificate Management
# acm-certificates.yaml resource "aws_acm_certificate" "main" { domain_name = "myapp.com" validation_method = "DNS" subject_alternative_names = [ "www.myapp.com", "api.myapp.com", "*.myapp.com" ] tags = { Name = "myapp-certificate" } lifecycle { create_before_destroy = true } } # Create Route53 validation records resource "aws_route53_record" "cert_validation" { for_each = { for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name record = dvo.resource_record_value type = dvo.resource_record_type } } allow_overwrite = true name = each.value.name records = [each.value.record] ttl = 60 type = each.value.type zone_id = aws_route53_zone.main.zone_id } # Validate certificate resource "aws_acm_certificate_validation" "main" { certificate_arn = aws_acm_certificate.main.arn timeouts { create = "5m" } depends_on = [aws_route53_record.cert_validation] } # Use in ALB resource "aws_lb_listener" "https" { load_balancer_arn = aws_lb.main.arn port = "443" protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01" certificate_arn = aws_acm_certificate_validation.main.certificate_arn default_action { type = "forward" target_group_arn = aws_lb_target_group.main.arn } }
3. Certificate Monitoring and Renewal
#!/bin/bash # certificate-monitor.sh - Monitor and alert on certificate expiration set -euo pipefail ALERT_DAYS=30 ALERT_EMAIL="[email protected]" # Check certificate expiration in Kubernetes check_k8s_certificates() { echo "Checking Kubernetes certificate expiration..." kubectl get secrets -A -o json | jq -r '.items[] | select(.type=="kubernetes.io/tls") | "\(.metadata.name) \(.metadata.namespace)"' | \ while read secret namespace; do cert=$(kubectl get secret "$secret" -n "$namespace" -o jsonpath='{.data.tls\.crt}' | base64 -d) expiry=$(echo "$cert" | openssl x509 -noout -enddate | cut -d= -f2) expiry_epoch=$(date -d "$expiry" +%s) now_epoch=$(date +%s) days_until=$((($expiry_epoch - $now_epoch) / 86400)) if [ $days_until -lt $ALERT_DAYS ]; then echo "WARNING: Certificate $secret in namespace $namespace expires in $days_until days" echo "Certificate $secret expires on $expiry" | \ mail -s "Certificate Expiration Alert" "$ALERT_EMAIL" fi done } # Check AWS ACM certificates check_acm_certificates() { echo "Checking AWS ACM certificate expiration..." aws acm list-certificates \ --query 'CertificateSummaryList[*].CertificateArn' \ --output text | tr '\t' '\n' | \ while read arn; do expiry=$(aws acm describe-certificate --certificate-arn "$arn" \ --query 'Certificate.NotAfter' --output text) expiry_epoch=$(date -d "$expiry" +%s) now_epoch=$(date +%s) days_until=$((($expiry_epoch - $now_epoch) / 86400)) if [ $days_until -lt $ALERT_DAYS ]; then domain=$(aws acm describe-certificate --certificate-arn "$arn" \ --query 'Certificate.DomainName' --output text) echo "WARNING: Certificate for $domain expires in $days_until days" echo "ACM Certificate $domain expires on $expiry" | \ mail -s "Certificate Expiration Alert" "$ALERT_EMAIL" fi done } # Main execution check_k8s_certificates check_acm_certificates echo "Certificate check complete"
4. Automated Certificate Renewal
# certificate-renewal-cronjob.yaml apiVersion: batch/v1 kind: CronJob metadata: name: certificate-renewal namespace: operations spec: schedule: "0 2 * * 0" # Weekly at 2 AM Sunday jobTemplate: spec: template: spec: serviceAccountName: cert-renewal-sa containers: - name: renewer image: alpine:latest command: - sh - -c - | apk add --no-cache kubectl curl jq openssl echo "Checking certificate renewal status..." # Get all certificates kubectl get certificates -A -o json | jq -r '.items[] | "\(.metadata.name) \(.metadata.namespace)"' | \ while read cert namespace; do status=$(kubectl get certificate "$cert" -n "$namespace" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') if [ "$status" != "True" ]; then echo "Renewing certificate: $cert in namespace $namespace" kubectl annotate certificate "$cert" -n "$namespace" \ cert-manager.io/issue-temporary-certificate="true" \ --overwrite || true fi done echo "Certificate renewal check complete" restartPolicy: OnFailure --- apiVersion: v1 kind: ServiceAccount metadata: name: cert-renewal-sa namespace: operations --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: cert-renewal rules: - apiGroups: ["cert-manager.io"] resources: ["certificates"] verbs: ["get", "list", "patch", "annotate"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: cert-renewal roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cert-renewal subjects: - kind: ServiceAccount name: cert-renewal-sa namespace: operations
5. Certificate Pinning
# nginx-certificate-pinning.conf server { listen 443 ssl http2; server_name api.myapp.com; ssl_certificate /etc/nginx/certs/server.crt; ssl_certificate_key /etc/nginx/certs/server.key; ssl_protocols TLSv1.2 TLSv1.3; # Certificate pinning for API clients add_header Public-Key-Pins 'pin-sha256="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; pin-sha256="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="; max-age=2592000; includeSubDomains' always; location / { proxy_pass http://backend; } }
Best Practices
β DO
- Automate certificate renewal
- Use Let's Encrypt for public certs
- Monitor certificate expiration
- Use wildcard certs strategically
- Implement certificate pinning
- Rotate certificates regularly
- Store keys securely
- Use strong key sizes (2048+ RSA, 256+ ECDSA)
β DON'T
- Manual certificate management
- Self-signed certs in production
- Share private keys
- Ignore expiration warnings
- Use weak key sizes
- Mix dev and prod certs
- Commit certs to git
- Disable certificate validation
Certificate Types
- Self-signed: Development only
- Domain Validated (DV): Single domain
- Wildcard: All subdomains
- Multi-SAN: Multiple domains
- Extended Validation (EV): High trust
Common Commands
# Generate CSR openssl req -new -key server.key -out server.csr # Check certificate openssl x509 -in cert.pem -text -noout # Get certificate fingerprint openssl x509 -in cert.pem -noout -fingerprint -sha256 # Renew certificate certbot renew --force-renewal