Cloudflare를 사용한 LetsEncrypt DNS 검증 템플릿

여러분, 안녕하세요.

Let’s Encrypt에서 SSL 인증서 소유권을 인증하기 위해 DNS 방법을 사용해야 했습니다.

/var/discourse/templates/에 있는 기존 web.letsencrypt.ssl.template.yml 파일의 사본을 가져와서 자동 DNS API 통합(Automatic DNS API Integration) 방법을 사용하도록 수정했습니다. 아래는 제 샘플 템플릿입니다. 개선에 대한 제안이 있다면 기꺼이 받아들일 것입니다.

이 파일의 이름을 web.letsencrypt.ssl.dns.template.yml로 지었습니다.

env:
  LETSENCRYPT_DIR: "/shared/letsencrypt"
  DISCOURSE_FORCE_HTTPS: true

hooks:
  after_ssl:
    - exec:
       cmd:
         - if [ -z "$LETSENCRYPT_ACCOUNT_EMAIL" ]; then echo "LETSENCRYPT_ACCOUNT_EMAIL ENV variable is required and has not been set."; exit 1; fi
         - /bin/bash -c "if [[ ! \"$LETSENCRYPT_ACCOUNT_EMAIL\" =~ ([^@]+)@([^\.]+) ]]; then echo \"LETSENCRYPT_ACCOUNT_EMAIL is not a valid email address\"; exit 1; fi"

    - exec:
       cmd:
         - cd /root && git clone --branch 3.0.7 --depth 1 https://github.com/acmesh-official/acme.sh.git && cd /root/acme.sh
         - touch /var/spool/cron/crontabs/root
         - install -d -m 0755 -g root -o root $LETSENCRYPT_DIR
         - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --install --log "${LETSENCRYPT_DIR}/acme.sh.log"
         - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --upgrade --auto-upgrade
         - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --set-default-ca  --server  letsencrypt 

    - file:
       path: /etc/runit/1.d/letsencrypt
       chmod: "+x"
       contents: |
        #!/bin/bash
        
        issue_cert() {
          export CF_Token="$$ENV_LETSENCRYPT_CF_TOKEN"
          export CF_Account_ID="$$ENV_LETSENCRYPT_CF_ACCOUNT_ID"
          export CF_Zone_ID="$$ENV_LETSENCRYPT_CF_ZONE_ID"
          LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh --issue --dns $$ENV_LETSENCRYPT_DNS_PROVIDER $2 -d $$ENV_DISCOURSE_HOSTNAME --keylength $1 -w /var/www/discourse/public
        }

        cert_exists() {
          [[ "$(cd $$ENV_LETSENCRYPT_DIR/$$ENV_DISCOURSE_HOSTNAME$1 && openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer | grep "OK")" ]]
        }

        ########################################################
        # RSA cert
        ########################################################
        issue_cert "4096"

        if ! cert_exists ""; then
          # Try to issue the cert again if something goes wrong
          issue_cert "4096" "--force"
        fi

        LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh \
          --installcert \
          -d $$ENV_DISCOURSE_HOSTNAME \
          --fullchainpath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.cer \
          --keypath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.key \
          --reloadcmd "sv reload nginx"

        ########################################################
        # ECDSA cert
        ########################################################
        issue_cert "ec-256"

        if ! cert_exists "_ecc"; then
          # Try to issue the cert again if something goes wrong
          issue_cert "ec-256" "--force"
        fi

        LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh \
          --installcert --ecc \
          -d $$ENV_DISCOURSE_HOSTNAME \
          --fullchainpath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.cer \
          --keypath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.key \
          --reloadcmd "sv reload nginx"

        if cert_exists "" || cert_exists "_ecc"; then
          grep -q 'force_https' "/var/www/discourse/config/discourse.conf" || echo "force_https = 'true'" >> "/var/www/discourse/config/discourse.conf"
        fi

    - replace:
       filename: "/etc/nginx/conf.d/discourse.conf"
       from: /ssl_certificate.+/
       to: |
         ssl_certificate /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.cer;
         ssl_certificate /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.cer;

    - replace:
       filename: /shared/letsencrypt/account.conf
       from: /#?ACCOUNT_EMAIL=.+/
       to: |
         ACCOUNT_EMAIL=$$ENV_LETSENCRYPT_ACCOUNT_EMAIL

    - replace:
       filename: "/etc/nginx/conf.d/discourse.conf"
       from: /ssl_certificate_key.+/
       to: |
         ssl_certificate_key /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.key;
         ssl_certificate_key /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.key;

    - replace:
       filename: "/etc/nginx/conf.d/discourse.conf"
       from: /add_header.+/
       to: |
         add_header Strict-Transport-Security 'max-age=63072000';

Cloudflare를 DNS 제공업체로 사용하지 않는 경우, app.yml에 추가해야 하는 몇 가지 환경 변수가 있으며 수정이 필요할 수 있습니다. 다양한 제공업체의 API 설정은 여기에서 확인할 수 있습니다.

app.yml의 templates 섹션 아래에 추가한 내용은 다음과 같습니다.

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  ## IPv6 리스너를 활성화하려면 다음 주석을 해제하세요
  #- "templates/web.ipv6.template.yml"
  - "templates/web.ratelimited.template.yml"
  ## Lets Encrypt(https)를 추가하려면 아래 두 줄의 주석을 해제하세요
  - "templates/web.ssl.template.yml"
  - "templates/web.letsencrypt.ssl.dns.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

그리고 아래 env 섹션에도 추가했습니다.

## Lets Encrypt 템플릿을 추가했다면 무료 SSL 인증서를 받으려면 아래 주석을 해제하세요
  LETSENCRYPT_ACCOUNT_EMAIL: me@mydomain.com
  LETSENCRYPT_CF_TOKEN: "YOUR_TOKEN"
  LETSENCRYPT_CF_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
  LETSENCRYPT_CF_ZONE_ID: "YOUR_ZONE_ID"
  LETSENCRYPT_DNS_PROVIDER: "YOUR_DNS_PROVIDER" ## 예: dns_cf

해당 파일들을 업데이트한 후, 단순히 docker 앱을 다시 빌드하는 명령을 실행했습니다.

cd /var/discourse
./launcher rebuild app 

다시 빌드된 후에는 앱이 https:// 주소에서 실행되고, 인증서가 업데이트되는지 매일 확인하는 cron 작업이 있어야 합니다. 업데이트가 필요하면 새 인증서를 발급받아 자동으로 설치합니다.

누군가에게 도움이 되기를 바랍니다.

수정: 방금 letsencrypt의 5개 인증서(도메인별) 주간 제한에 도달했습니다.

_ecc 인증서 생성 시 문제가 있었나요?

작성하신 게시물을 참고하여 메인 website.com.cer 파일은 성공적으로 생성되었으나, website.com_ecc.cer는 0바이트로 표시되고 있습니다(_ecc.key 파일은 정상입니다).

하지만 두 인증서 모두 동일한 issue_cert 메서드로 발급되는데, 어떤 이유에서인지 메인 인증서는 작동하고 _ecc 인증서는 실패합니다.