mcpField
S
ssl-tls-management

ssl-tls-management

github.com/bagelholeセキュリティ1.2k426 回インストール23 週間前に更新
リポジトリ

ssl-tls-management について

Webサーバー、Kubernetesクラスター、内部サービスのTLS証明書と安全な通信を扱うガイドです。HTTPSの設定や証明書の更新・監視を担う運用担当者向けです。

主な機能

  1. 01Certbotによる証明書の発行と更新
  2. 02Kubernetesへのcert-manager導入
  3. 03鍵の生成と証明書チェーンの確認
  4. 04NginxとApacheのTLS設定
  5. 05証明書期限の監視とアラート

活用シーン

  1. 01新しいWebアプリでHTTPSを有効化
  2. 02Let's Encrypt証明書の更新を自動化
  3. 03Kubernetesサービス向け証明書の導入
  4. 04期限切れ間近または侵害された証明書への対応

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill ssl-tls-management

インストール後、Claude Code で/ssl-tls-managementを入力して読み込まれたか確認します。

詳細

SSL/TLS 管理

Web サーバー、Kubernetes クラスター、内部サービス全体で証明書を管理し、安全な通信を実現します。

このスキルを使用する場面

次のような場合にこのスキルを使用します。

  • 新しい Web アプリケーションで HTTPS を設定する
  • Let's Encrypt による証明書の更新を自動化する
  • Kubernetes に cert-manager をデプロイする
  • 内部サービス間通信の TLS を設定する
  • コンプライアンス対応のため、暗号スイートと TLS バージョンを監査する
  • 期限切れまたは侵害された証明書に対応する

前提条件

  • パブリック証明書を取得するための DNS 管理権限があるドメイン名
  • Web サーバーへの root/sudo アクセス
  • Let's Encrypt 用の certbot がインストールされていること
  • openssl CLI が利用可能であること(ほとんどの Linux ディストリビューションではデフォルトでインストールされています)
  • cert-manager のデプロイに使用する Helm がインストールされた Kubernetes クラスター
  • X.509 証明書の信頼チェーンに関する知識

Certbot を使用した Let's Encrypt

インストールと証明書の発行
# Install certbot (Ubuntu/Debian)
apt update && apt install -y certbot python3-certbot-nginx

# Obtain certificate for nginx (interactive)
certbot --nginx -d example.com -d www.example.com

# Non-interactive mode for automation
certbot certonly --nginx \
  -d example.com \
  -d www.example.com \
  --non-interactive \
  --agree-tos \
  --email [email protected]

# 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
更新の自動化
# 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

Kubernetes 用 cert-manager

インストール
# 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 の設定
# 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: [email protected]
    privateKeySecretRef:
      name: letsencrypt-staging
    solvers:
      - http01:
          ingress:
            class: nginx
---
# letsencrypt-prod
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:
      - 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: [email protected]
    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 リソース
# 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
    - 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

OpenSSL コマンドのリファレンス

# 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'

強固な TLS 設定

Nginx
server {
    listen 443 ssl http2;
    server_name example.com;

    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
<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>

証明書の監視

#!/bin/bash
# cert-monitor.sh - Monitor certificate expiration across hosts

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 メトリクス
# 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"

トラブルシューティング

問題原因解決策
Certbot が「connection refused」で失敗するファイアウォールでポート 80 がブロックされているACME HTTP-01 チャレンジ用にポート 80 を開放します
「Too many certificates already issued」Let's Encrypt のレート制限に達しているテストにはステージングエンドポイントを使用し、レート制限がリセットされるまで待ちます
cert-manager のチャレンジが pending のまま停止するIngress または DNS の設定に誤りがあるkubectl describe challenge を確認し、DNS レコードを検証します
混在コンテンツの警告が表示されるHTTPS ページ上のリソースが HTTP を使用しているすべてのアセット URL を HTTPS に更新し、CSP ヘッダーを使用します
OCSP ステープリングが機能しないリゾルバーが設定されていないnginx に resolver ディレクティブを追加し、外部 DNS を検証します
中間証明書が欠落している不完全なチェーンが配信されているcert.pem ではなく fullchain.pem を使用し、openssl s_client -showcerts で検証します
TLS ハンドシェイクに失敗するクライアントが提示された暗号に対応していないTLS 1.2 のサポートを追加し、暗号スイートとの互換性を確認します

ベストプラクティス

  • systemd タイマーまたは cert-manager を使って更新を自動化します
  • アラートを設定して有効期限を監視します(30日前と7日前に警告)
  • TLS 1.2 と TLS 1.3 のみを使用します
  • 長い max-age と includeSubDomains を設定して HSTS を有効にします
  • ハンドシェイクのパフォーマンスを向上させるため、OCSP ステープリングを有効にします
  • 可能な場合は、パフォーマンス向上のため ECDSA キーを使用します
  • SSL Labs(ssllabs.com/ssltest)で設定をテストします
  • 適切なファイル権限(0600)を設定して秘密鍵を保護します
  • 期限切れになってからではなく、期限前に証明書をローテーションします
  • すべてのサービスの証明書一覧を管理します

関連スキル

コメント