SiamCafe.net Blog
Cybersecurity

Pulumi IaC SSL TLS Certificate

pulumi iac ssl tls certificate
Pulumi IaC SSL TLS Certificate | SiamCafe Blog
2025-08-09· อ. บอม — SiamCafe.net· 9,960 คำ

Pulumi SSL/TLS

Pulumi IaC SSL TLS Certificate ACM Let's Encrypt Auto-renewal CloudFront ALB API Gateway DNS Validation Infrastructure as Code Python TypeScript

CertificateProviderCostAuto-renewเหมาะกับ
ACMAWSFree (AWS)AutoCloudFront ALB
Let's EncryptISRGFreeCertbot 90dAny Server
DigiCertDigiCert$$$Manual/AutoEnterprise EV
CloudflareCloudflareFree (CF)AutoCloudflare Users
cert-managerKubernetesFreeAutoK8s Ingress

ACM Certificate with Pulumi

# === Pulumi ACM Certificate (Python) ===

# pip install pulumi pulumi-aws

import pulumi
import pulumi_aws as aws

# ACM Certificate
certificate = aws.acm.Certificate("site-cert",
    domain_name="example.com",
    subject_alternative_names=[
        "*.example.com",
        "api.example.com",
        "cdn.example.com",
    ],
    validation_method="DNS",
    tags={"Environment": "production", "ManagedBy": "pulumi"},
)

# Route 53 Zone
zone = aws.route53.get_zone(name="example.com")

# DNS Validation Records
validation_records = []
for i, dvo in enumerate(certificate.domain_validation_options):
    record = aws.route53.Record(f"cert-validation-{i}",
        zone_id=zone.zone_id,
        name=dvo.resource_record_name,
        type=dvo.resource_record_type,
        records=[dvo.resource_record_value],
        ttl=300,
    )
    validation_records.append(record)

# Wait for validation
cert_validation = aws.acm.CertificateValidation("cert-validation",
    certificate_arn=certificate.arn,
    validation_record_fqdns=[r.fqdn for r in validation_records],
)

# CloudFront Distribution with SSL
# cdn = aws.cloudfront.Distribution("cdn",
#     origins=[aws.cloudfront.DistributionOriginArgs(
#         domain_name="origin.example.com",
#         origin_id="origin",
#         custom_origin_config=aws.cloudfront.DistributionOriginCustomOriginConfigArgs(
#             http_port=80,
#             https_port=443,
#             origin_protocol_policy="https-only",
#             origin_ssl_protocols=["TLSv1.2"],
#         ),
#     )],
#     default_cache_behavior=aws.cloudfront.DistributionDefaultCacheBehaviorArgs(
#         target_origin_id="origin",
#         viewer_protocol_policy="redirect-to-https",
#         allowed_methods=["GET", "HEAD"],
#         cached_methods=["GET", "HEAD"],
#         forwarded_values=aws.cloudfront.DistributionDefaultCacheBehaviorForwardedValuesArgs(
#             query_string=False,
#             cookies=aws.cloudfront.DistributionDefaultCacheBehaviorForwardedValuesCookiesArgs(
#                 forward="none",
#             ),
#         ),
#     ),
#     viewer_certificate=aws.cloudfront.DistributionViewerCertificateArgs(
#         acm_certificate_arn=cert_validation.certificate_arn,
#         ssl_support_method="sni-only",
#         minimum_protocol_version="TLSv1.2_2021",
#     ),
#     aliases=["example.com", "www.example.com"],
#     enabled=True,
# )

pulumi.export("certificate_arn", certificate.arn)
pulumi.export("certificate_status", certificate.status)

from dataclasses import dataclass

@dataclass
class CertConfig:
    domain: str
    sans: list
    validation: str
    service: str
    tls_version: str

configs = [
    CertConfig("example.com", ["*.example.com"], "DNS", "CloudFront", "TLSv1.2"),
    CertConfig("api.example.com", [], "DNS", "ALB", "TLSv1.2"),
    CertConfig("admin.example.com", [], "DNS", "ALB", "TLSv1.3"),
]

print("=== Certificate Configurations ===")
for c in configs:
    sans_str = ", ".join(c.sans) if c.sans else "None"
    print(f"  [{c.domain}] SANs: {sans_str}")
    print(f"    Validation: {c.validation} | Service: {c.service} | TLS: {c.tls_version}")

Let's Encrypt with Pulumi

# === Let's Encrypt + cert-manager on Kubernetes ===

# Pulumi Kubernetes — cert-manager setup
# import pulumi_kubernetes as k8s
#
# # Install cert-manager via Helm
# cert_manager = k8s.helm.v3.Release("cert-manager",
#     chart="cert-manager",
#     repository_opts=k8s.helm.v3.RepositoryOptsArgs(
#         repo="https://charts.jetstack.io",
#     ),
#     namespace="cert-manager",
#     create_namespace=True,
#     values={
#         "installCRDs": True,
#         "global": {"leaderElection": {"namespace": "cert-manager"}},
#     },
# )
#
# # ClusterIssuer for Let's Encrypt
# cluster_issuer = k8s.apiextensions.CustomResource("letsencrypt-prod",
#     api_version="cert-manager.io/v1",
#     kind="ClusterIssuer",
#     metadata=k8s.meta.v1.ObjectMetaArgs(name="letsencrypt-prod"),
#     spec={
#         "acme": {
#             "server": "https://acme-v02.api.letsencrypt.org/directory",
#             "email": "admin@example.com",
#             "privateKeySecretRef": {"name": "letsencrypt-prod-key"},
#             "solvers": [
#                 {"http01": {"ingress": {"class": "nginx"}}},
#             ],
#         },
#     },
#     opts=pulumi.ResourceOptions(depends_on=[cert_manager]),
# )
#
# # Ingress with auto TLS
# ingress = k8s.networking.v1.Ingress("app-ingress",
#     metadata=k8s.meta.v1.ObjectMetaArgs(
#         annotations={
#             "cert-manager.io/cluster-issuer": "letsencrypt-prod",
#             "nginx.ingress.kubernetes.io/ssl-redirect": "true",
#         },
#     ),
#     spec=k8s.networking.v1.IngressSpecArgs(
#         tls=[k8s.networking.v1.IngressTLSArgs(
#             hosts=["app.example.com"],
#             secret_name="app-tls-cert",
#         )],
#         rules=[k8s.networking.v1.IngressRuleArgs(
#             host="app.example.com",
#             http=k8s.networking.v1.HTTPIngressRuleValueArgs(
#                 paths=[k8s.networking.v1.HTTPIngressPathArgs(
#                     path="/",
#                     path_type="Prefix",
#                     backend=k8s.networking.v1.IngressBackendArgs(
#                         service=k8s.networking.v1.IngressServiceBackendArgs(
#                             name="app-service",
#                             port=k8s.networking.v1.ServiceBackendPortArgs(number=80),
#                         ),
#                     ),
#                 )],
#             ),
#         )],
#     ),
# )

# Certbot standalone
# certbot certonly --standalone -d example.com -d www.example.com \
#   --email admin@example.com --agree-tos --non-interactive
#
# # Auto-renewal cron
# 0 0 1 * * certbot renew --quiet --post-hook "systemctl reload nginx"

@dataclass
class CertMethod:
    method: str
    provider: str
    auto_renew: str
    complexity: str
    use_case: str

methods = [
    CertMethod("ACM + Pulumi", "AWS", "Automatic", "ง่าย", "AWS CloudFront ALB"),
    CertMethod("cert-manager + Pulumi", "Let's Encrypt", "Automatic", "ปานกลาง", "Kubernetes"),
    CertMethod("Certbot Cron", "Let's Encrypt", "Cron Job", "ง่าย", "Standalone Server"),
    CertMethod("Cloudflare SSL", "Cloudflare", "Automatic", "ง่าย", "Cloudflare Proxy"),
    CertMethod("Pulumi + DigiCert", "DigiCert", "Manual/API", "ซับซ้อน", "Enterprise EV"),
]

print("\n=== Certificate Methods ===")
for m in methods:
    print(f"  [{m.method}]")
    print(f"    Provider: {m.provider} | Renew: {m.auto_renew}")
    print(f"    Complexity: {m.complexity} | Use: {m.use_case}")

Security Best Practices

# === TLS Security Configuration ===

# Nginx TLS 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_session_tickets off;
# ssl_stapling on;
# ssl_stapling_verify on;
# add_header Strict-Transport-Security "max-age=63072000" always;

@dataclass
class TLSCheck:
    check: str
    expected: str
    tool: str
    severity: str
    status: str

checks = [
    TLSCheck("TLS Version", "1.2 / 1.3 only", "ssllabs.com", "Critical", "Pass"),
    TLSCheck("Certificate Chain", "Valid complete chain", "openssl s_client", "Critical", "Pass"),
    TLSCheck("HSTS Header", "max-age >= 31536000", "curl -I", "High", "Pass"),
    TLSCheck("OCSP Stapling", "Enabled", "openssl s_client -status", "Medium", "Pass"),
    TLSCheck("Cipher Suites", "AEAD only no CBC", "nmap --script ssl-enum-ciphers", "High", "Pass"),
    TLSCheck("Certificate Expiry", "> 30 days", "openssl x509 -enddate", "Critical", "Pass"),
    TLSCheck("CAA Record", "Present", "dig CAA example.com", "Medium", "Pass"),
    TLSCheck("Key Size", "RSA 2048+ or ECDSA P-256+", "openssl x509 -text", "High", "Pass"),
]

print("TLS Security Checklist:")
for c in checks:
    print(f"  [{c.status}] [{c.severity}] {c.check}: {c.expected}")
    print(f"    Tool: {c.tool}")

# Certificate monitoring
monitoring = {
    "Expiry Alert": "Alert 30 days before expiry",
    "CT Log Monitor": "Monitor Certificate Transparency logs",
    "SSL Labs Score": "Maintain A+ rating",
    "Renewal Check": "Verify auto-renewal weekly",
    "Revocation Check": "Monitor CRL OCSP status",
}

print(f"\n\nCertificate Monitoring:")
for k, v in monitoring.items():
    print(f"  [{k}]: {v}")

เคล็ดลับ

Pulumi คืออะไร

IaC Platform ภาษาจริง Python TypeScript Go Cloud Resources AWS Azure GCP State Preview Secrets CI/CD Loop Condition Function

จัดการ SSL/TLS Certificate ด้วย Pulumi อย่างไร

ACM Certificate Domain SANs DNS Validation Route 53 Auto-renewal CloudFront ALB API Gateway Let's Encrypt cert-manager Kubernetes

ACM Certificate ต่างจาก Let's Encrypt อย่างไร

ACM ฟรี AWS Auto-renewal Managed CloudFront ALB Let's Encrypt ฟรีทุก Server Certbot 90 วัน Nginx Apache Non-AWS On-premise

ทำไมต้องใช้ IaC จัดการ Certificate

Reproducible Version Control Git Review Automation Multi-environment Drift Detection Rollback Documentation Code ทุก Environment เหมือนกัน

สรุป

Pulumi IaC SSL TLS Certificate ACM Let's Encrypt cert-manager Auto-renewal CloudFront ALB DNS Validation TLS 1.3 HSTS Security Production

📖 บทความที่เกี่ยวข้อง

Pulumi IaC สำหรับมือใหม่ Step by Stepอ่านบทความ → Container Security Trivy SSL TLS Certificateอ่านบทความ → Pulumi IaC Micro-segmentationอ่านบทความ → Pulumi IaC Disaster Recovery Planอ่านบทความ → Pulumi IaC Internal Developer Platformอ่านบทความ →

📚 ดูบทความทั้งหมด →