mirror of
https://github.com/BagelHole/DevOps-Security-Agent-Skills.git
synced 2026-08-22 12:49:53 +02:00
V2
This commit is contained in:
@@ -9,25 +9,157 @@ metadata:
|
||||
|
||||
# SSL/TLS Management
|
||||
|
||||
Manage certificates and secure communications.
|
||||
Manage certificates and secure communications across web servers, Kubernetes clusters, and internal services.
|
||||
|
||||
## Let's Encrypt (Certbot)
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Setting up HTTPS for a new web application
|
||||
- Automating certificate renewal with Let's Encrypt
|
||||
- Deploying cert-manager in Kubernetes
|
||||
- Configuring TLS for internal service-to-service communication
|
||||
- Auditing cipher suites and TLS versions for compliance
|
||||
- Responding to an expiring or compromised certificate
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Domain name with DNS control for public certificates
|
||||
- Root/sudo access on web servers
|
||||
- `certbot` installed for Let's Encrypt
|
||||
- `openssl` CLI available (installed by default on most Linux distros)
|
||||
- Kubernetes cluster with Helm for cert-manager deployment
|
||||
- Understanding of X.509 certificate chain of trust
|
||||
|
||||
## Let's Encrypt with Certbot
|
||||
|
||||
### Installation and Certificate Issuance
|
||||
|
||||
```bash
|
||||
# Install
|
||||
apt install certbot python3-certbot-nginx
|
||||
# Install certbot (Ubuntu/Debian)
|
||||
apt update && apt install -y certbot python3-certbot-nginx
|
||||
|
||||
# Get certificate
|
||||
# Obtain certificate for nginx (interactive)
|
||||
certbot --nginx -d example.com -d www.example.com
|
||||
|
||||
# Auto-renewal
|
||||
certbot renew --dry-run
|
||||
# Cron: 0 0 * * * certbot renew --quiet
|
||||
# Non-interactive mode for automation
|
||||
certbot certonly --nginx \
|
||||
-d example.com \
|
||||
-d www.example.com \
|
||||
--non-interactive \
|
||||
--agree-tos \
|
||||
--email admin@example.com
|
||||
|
||||
# Standalone mode (when no web server is running)
|
||||
certbot certonly --standalone \
|
||||
-d example.com \
|
||||
--preferred-challenges http
|
||||
|
||||
# DNS challenge (for wildcard certs)
|
||||
certbot certonly --manual \
|
||||
--preferred-challenges dns \
|
||||
-d "*.example.com" \
|
||||
-d example.com
|
||||
|
||||
# Using DNS plugin for automation (Cloudflare example)
|
||||
pip install certbot-dns-cloudflare
|
||||
cat > /etc/letsencrypt/cloudflare.ini << 'EOF'
|
||||
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
|
||||
EOF
|
||||
chmod 600 /etc/letsencrypt/cloudflare.ini
|
||||
|
||||
certbot certonly --dns-cloudflare \
|
||||
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
|
||||
-d "*.example.com" \
|
||||
-d example.com
|
||||
```
|
||||
|
||||
## cert-manager (Kubernetes)
|
||||
### Renewal Automation
|
||||
|
||||
```bash
|
||||
# Test renewal
|
||||
certbot renew --dry-run
|
||||
|
||||
# Systemd timer (preferred over cron)
|
||||
cat > /etc/systemd/system/certbot-renewal.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Certbot certificate renewal
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
|
||||
EOF
|
||||
|
||||
cat > /etc/systemd/system/certbot-renewal.timer << 'EOF'
|
||||
[Unit]
|
||||
Description=Run certbot renewal twice daily
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 00,12:00:00
|
||||
RandomizedDelaySec=3600
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
|
||||
systemctl enable --now certbot-renewal.timer
|
||||
|
||||
# Verify timer is active
|
||||
systemctl list-timers certbot-renewal.timer
|
||||
|
||||
# Renewal hooks for post-renewal actions
|
||||
mkdir -p /etc/letsencrypt/renewal-hooks/deploy
|
||||
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-services.sh << 'HOOK'
|
||||
#!/bin/bash
|
||||
systemctl reload nginx
|
||||
# Also reload other services using the cert
|
||||
systemctl reload haproxy 2>/dev/null || true
|
||||
HOOK
|
||||
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-services.sh
|
||||
```
|
||||
|
||||
## cert-manager for Kubernetes
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install with Helm
|
||||
helm repo add jetstack https://charts.jetstack.io
|
||||
helm repo update
|
||||
|
||||
helm install cert-manager jetstack/cert-manager \
|
||||
--namespace cert-manager \
|
||||
--create-namespace \
|
||||
--version v1.14.0 \
|
||||
--set installCRDs=true \
|
||||
--set prometheus.enabled=true
|
||||
|
||||
# Verify installation
|
||||
kubectl get pods -n cert-manager
|
||||
kubectl get crds | grep cert-manager
|
||||
```
|
||||
|
||||
### ClusterIssuer Configurations
|
||||
|
||||
```yaml
|
||||
# letsencrypt-staging (use for testing first)
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-staging
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
email: admin@example.com
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-staging
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: nginx
|
||||
---
|
||||
# letsencrypt-prod
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
@@ -39,58 +171,316 @@ spec:
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: nginx
|
||||
- http01:
|
||||
ingress:
|
||||
class: nginx
|
||||
---
|
||||
# DNS challenge solver (for wildcard certs with Cloudflare)
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-prod-dns
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
email: admin@example.com
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod-dns
|
||||
solvers:
|
||||
- dns01:
|
||||
cloudflare:
|
||||
apiTokenSecretRef:
|
||||
name: cloudflare-api-token
|
||||
key: api-token
|
||||
---
|
||||
# Self-signed CA issuer for internal services
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: selfsigned-ca
|
||||
spec:
|
||||
selfSigned: {}
|
||||
```
|
||||
|
||||
### Certificate Resources
|
||||
|
||||
```yaml
|
||||
# Public-facing certificate
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: example-cert
|
||||
namespace: default
|
||||
spec:
|
||||
secretName: example-tls
|
||||
issuerRef:
|
||||
name: letsencrypt-prod
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- example.com
|
||||
- example.com
|
||||
- www.example.com
|
||||
duration: 2160h # 90 days
|
||||
renewBefore: 720h # 30 days before expiry
|
||||
---
|
||||
# Wildcard certificate
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: wildcard-cert
|
||||
namespace: default
|
||||
spec:
|
||||
secretName: wildcard-tls
|
||||
issuerRef:
|
||||
name: letsencrypt-prod-dns
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- "*.example.com"
|
||||
- example.com
|
||||
---
|
||||
# Ingress with automatic TLS
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: example-ingress
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- example.com
|
||||
secretName: example-tls
|
||||
rules:
|
||||
- host: example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: web
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
## Strong Configuration
|
||||
## OpenSSL Commands Reference
|
||||
|
||||
```bash
|
||||
# Generate a private key
|
||||
openssl genrsa -out server.key 4096
|
||||
|
||||
# Generate an ECDSA key (preferred for performance)
|
||||
openssl ecparam -genkey -name prime256v1 -out server-ec.key
|
||||
|
||||
# Generate a CSR (Certificate Signing Request)
|
||||
openssl req -new -key server.key -out server.csr \
|
||||
-subj "/C=US/ST=California/L=San Francisco/O=Acme Corp/CN=example.com"
|
||||
|
||||
# Generate CSR with SAN (Subject Alternative Names)
|
||||
openssl req -new -key server.key -out server.csr -config <(cat <<EOF
|
||||
[req]
|
||||
default_bits = 4096
|
||||
distinguished_name = dn
|
||||
req_extensions = san
|
||||
prompt = no
|
||||
|
||||
[dn]
|
||||
CN = example.com
|
||||
O = Acme Corp
|
||||
C = US
|
||||
|
||||
[san]
|
||||
subjectAltName = DNS:example.com,DNS:www.example.com,DNS:api.example.com
|
||||
EOF
|
||||
)
|
||||
|
||||
# Generate self-signed certificate (development/testing)
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:4096 \
|
||||
-keyout selfsigned.key -out selfsigned.crt \
|
||||
-subj "/CN=localhost"
|
||||
|
||||
# View certificate details
|
||||
openssl x509 -in cert.pem -noout -text
|
||||
|
||||
# Check certificate expiration date
|
||||
openssl x509 -in cert.pem -noout -dates
|
||||
|
||||
# Check remote certificate
|
||||
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
|
||||
openssl x509 -noout -dates -subject -issuer
|
||||
|
||||
# Verify certificate chain
|
||||
openssl verify -CAfile ca-bundle.crt server.crt
|
||||
|
||||
# Check certificate chain from remote server
|
||||
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | \
|
||||
openssl x509 -noout -text
|
||||
|
||||
# Convert PEM to PKCS12
|
||||
openssl pkcs12 -export -out cert.pfx -inkey server.key -in server.crt -certfile ca.crt
|
||||
|
||||
# Convert PKCS12 to PEM
|
||||
openssl pkcs12 -in cert.pfx -out cert.pem -nodes
|
||||
|
||||
# Test TLS connection and cipher negotiation
|
||||
openssl s_client -connect example.com:443 -tls1_3
|
||||
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384'
|
||||
```
|
||||
|
||||
## Strong TLS Configuration
|
||||
|
||||
### Nginx
|
||||
|
||||
```nginx
|
||||
# nginx ssl config
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name example.com;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=63072000" always;
|
||||
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
|
||||
|
||||
# Protocol versions
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
# Cipher suites (TLS 1.2)
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
# Session settings
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
# OCSP stapling
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
|
||||
resolver 8.8.8.8 8.8.4.4 valid=300s;
|
||||
resolver_timeout 5s;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header X-Frame-Options DENY always;
|
||||
|
||||
# HTTP to HTTPS redirect (in separate server block)
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com www.example.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
```
|
||||
|
||||
### Apache
|
||||
|
||||
```apache
|
||||
<VirtualHost *:443>
|
||||
ServerName example.com
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
|
||||
|
||||
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
|
||||
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
|
||||
SSLHonorCipherOrder off
|
||||
|
||||
SSLUseStapling on
|
||||
SSLStaplingCache shmcb:/tmp/stapling_cache(128000)
|
||||
|
||||
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
## Certificate Monitoring
|
||||
|
||||
```bash
|
||||
# Check expiration
|
||||
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
|
||||
openssl x509 -noout -dates
|
||||
#!/bin/bash
|
||||
# cert-monitor.sh - Monitor certificate expiration across hosts
|
||||
|
||||
# Check certificate chain
|
||||
openssl s_client -connect example.com:443 -showcerts
|
||||
WARN_DAYS=30
|
||||
CRIT_DAYS=7
|
||||
HOSTS=(
|
||||
"example.com:443"
|
||||
"api.example.com:443"
|
||||
"admin.example.com:443"
|
||||
)
|
||||
|
||||
for host in "${HOSTS[@]}"; do
|
||||
expiry=$(echo | openssl s_client -connect "$host" -servername "${host%%:*}" 2>/dev/null | \
|
||||
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
|
||||
|
||||
if [ -z "$expiry" ]; then
|
||||
echo "ERROR: Cannot connect to $host"
|
||||
continue
|
||||
fi
|
||||
|
||||
expiry_epoch=$(date -d "$expiry" +%s)
|
||||
now_epoch=$(date +%s)
|
||||
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
|
||||
|
||||
if [ "$days_left" -le "$CRIT_DAYS" ]; then
|
||||
echo "CRITICAL: $host expires in $days_left days ($expiry)"
|
||||
elif [ "$days_left" -le "$WARN_DAYS" ]; then
|
||||
echo "WARNING: $host expires in $days_left days ($expiry)"
|
||||
else
|
||||
echo "OK: $host expires in $days_left days ($expiry)"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
### Prometheus cert-manager Metrics
|
||||
|
||||
```yaml
|
||||
# Alert on expiring certificates in Kubernetes
|
||||
groups:
|
||||
- name: cert-manager
|
||||
rules:
|
||||
- alert: CertificateExpiringSoon
|
||||
expr: certmanager_certificate_expiration_timestamp_seconds - time() < 7 * 24 * 3600
|
||||
for: 1h
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Certificate {{ $labels.name }} expires in less than 7 days"
|
||||
|
||||
- alert: CertificateNotReady
|
||||
expr: certmanager_certificate_ready_status{condition="True"} == 0
|
||||
for: 15m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Certificate {{ $labels.name }} is not ready"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Cause | Solution |
|
||||
|---------|-------|----------|
|
||||
| Certbot fails with "connection refused" | Port 80 blocked by firewall | Open port 80 for ACME HTTP-01 challenge |
|
||||
| "Too many certificates already issued" | Let's Encrypt rate limit hit | Use staging endpoint for testing; wait for rate limit reset |
|
||||
| cert-manager challenge stuck pending | Ingress or DNS misconfigured | Check `kubectl describe challenge`; verify DNS records |
|
||||
| Mixed content warnings | HTTP resources on HTTPS page | Update all asset URLs to HTTPS; use CSP headers |
|
||||
| OCSP stapling not working | Resolver not configured | Add `resolver` directive in nginx; verify outbound DNS |
|
||||
| Intermediate cert missing | Incomplete chain served | Use `fullchain.pem` not `cert.pem`; verify with `openssl s_client -showcerts` |
|
||||
| TLS handshake failure | Client doesn't support offered ciphers | Add TLS 1.2 support; check cipher suite compatibility |
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Automate renewal
|
||||
- Monitor expiration
|
||||
- Use strong ciphers
|
||||
- Enable HSTS
|
||||
- Regular security audits
|
||||
- Automate renewal with systemd timers or cert-manager
|
||||
- Monitor expiration dates with alerting (30-day and 7-day warnings)
|
||||
- Use only TLS 1.2 and TLS 1.3
|
||||
- Enable HSTS with long max-age and includeSubDomains
|
||||
- Enable OCSP stapling to improve handshake performance
|
||||
- Use ECDSA keys for better performance where possible
|
||||
- Test configuration with SSL Labs (ssllabs.com/ssltest)
|
||||
- Keep private keys secure with proper file permissions (0600)
|
||||
- Rotate certificates before expiry, not after
|
||||
- Maintain a certificate inventory across all services
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [hashicorp-vault](../../secrets/hashicorp-vault/) - PKI management
|
||||
- [waf-setup](../waf-setup/) - Web protection
|
||||
- [zero-trust](../zero-trust/) - mTLS and identity-based access
|
||||
|
||||
Reference in New Issue
Block a user