インストール

# yum install mod_ssl

自己署名証明書

  • SAN 項目を追加した設定ファイルを作成。

    # cd /etc/pki/tls/
    # cp openssl.cnf openssl-san.cnf
  • openssl.cnf と openssl-san.cnf の差分

    --- openssl.cnf
    +++ openssl-san.cnf
    @@ -104,7 +104,7 @@
     ####################################################################
     [ req ]
     default_bits           = 2048
    -default_md             = sha1
    +default_md             = sha256
     default_keyfile        = privkey.pem
     distinguished_name     = req_distinguished_name
     attributes             = req_attributes
    @@ -222,6 +222,11 @@
    
     basicConstraints = CA:FALSE
     keyUsage = nonRepudiation, digitalSignature, keyEncipherment
    +subjectAltName=@alt_names
    +
    +[ alt_names ]
    +DNS.1=takeash.net
    +DNS.2=*.takeash.net
    
    [ v3_ca ]
    
  • 証明書フォルダへ移動
    # cd certs/
  • サーバー用秘密鍵作成 (server.key)
    # openssl genrsa -aes128 2048 > server.key
  • パスフレーズ削除
    httpd 再起動時にパスフレーズが要求されないようにするため。
    # openssl rsa -in server.key -out server.key
  • サーバー用自己署名証明書作成 (server.crt)

    # openssl req -utf8 -new -key server.key -x509 -days 3650 -out server.crt -set_serial 0 \
        -subj '/C=JP/ST=Tokyo/L=Chuo-ku/O=TakeAsh.net/CN=takeash.net' -extensions v3_req -config ../openssl-san.cnf
    • サブジェクト例 (TakeAsh.net)

      項目 用途 サンプル
      C 国名コード JP
      ST 都道府県 Tokyo
      L 区市町村 Chuo-ku
      O 組織名 TakeAsh.net
      CN コモンネーム(ドメイン名) takeash.net
    • 証明書確認
      「X509v3 extensions - X509v3 Subject Alternative Name」項目が存在すれば SAN が含まれている。

      # openssl x509 -in server.crt -text
      ...
          X509v3 Subject Alternative Name:
              DNS:takeash.net, DNS:*.takeash.net
      ...
  • /etc/httpd/conf.d/ssl.conf (抜粋)

    SSLCertificateFile /etc/pki/tls/certs/server.crt
    SSLCertificateKeyFile /etc/pki/tls/certs/server.key
    DocumentRoot "/var/www/html"
    SSLProtocol all -SSLv2 -SSLv3
  • httpd 再起動

    • CentOS 6
      # service httpd restart
    • CentOS 7
      # systemctl restart httpd
  • 動作テスト

Certbot

  • Certbot 使用前準備

    • ホスト名が正引きできること。(ワイルドカード不可)
    • バーチャルホストのサーバ名と要求するドメイン名のどれかが一致すること。
    • インターネットから https でアクセス可能になっていること。
      • https ポート解放
      # firewall-cmd --permanent --add-service=https
      
    • CertBot は Apache が稼働しているサーバ上で実行する。
  • Certbot インストール (EPEL リポジトリ)

    # yum install python-certbot-apache
  • 証明書取得
    取得に成功すると「/etc/letsencrypt/live/<ドメイン1>/」に証明書が作成される。

    # certbot certonly --apache \
        -m <メールアドレス> --agree-tos \
        -d <ドメイン1> [-d <ドメイン2> ...]
  • /etc/httpd/conf.d/ssl.conf (抜粋)

    Listen 443 https
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/<ドメイン1>/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/<ドメイン1>/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/<ドメイン1>/chain.pem
  • /etc/httpd/conf.d/VirtualHosts.conf (抜粋)
    バーチャルホスト毎に SSL 設定が必要。

    <VirtualHost *:80 *:443>
            ServerName      vh1.<ドメイン1>
            DocumentRoot    /var/www/vh1-html/
            SSLEngine on
            SSLCertificateFile /etc/letsencrypt/live/<ドメイン1>/cert.pem
            SSLCertificateKeyFile /etc/letsencrypt/live/<ドメイン1>/privkey.pem
            SSLCertificateChainFile /etc/letsencrypt/live/<ドメイン1>/chain.pem
            <Directory "/var/www/vh1-html">
                    AllowOverride   All
            </Directory>
    </VirtualHost>
  • 自動更新スクリプト /etc/cron.monthly/certbot.sh

    #!/bin/bash
    /bin/certbot renew

Certbot (ワイルドカード, 手動)

  • Certbot インストール (EPEL リポジトリ)

    # yum -y install yum-utils
    # yum-config-manager --enable rhui-REGION-rhel-server-extras rhui-REGION-rhel-server-optional
    # yum install certbot-apache
  • 証明書取得(手動)
    途中HTTPへのテキストファイルの配置とDNSへのTXTレコードの追加を指示されるので、追加してからEnterを押して先へ進む。
    取得に成功すると「/etc/letsencrypt/live/<ドメイン>/」に証明書が作成される。

    # certbot certonly --manual \
        --server https://acme-v02.api.letsencrypt.org/directory \
        -d "*.example.com" -d example.com
  • /etc/httpd/conf.d/ssl.conf (抜粋)

    Listen 443 https
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/<ドメイン>/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/<ドメイン>/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/<ドメイン>/chain.pem
  • /etc/httpd/conf.d/VirtualHosts.conf (抜粋)
    バーチャルホスト毎に SSL 設定が必要。

    <VirtualHost *:80 *:443>
            ServerName      vh1.<ドメイン1>
            DocumentRoot    /var/www/vh1-html/
            SSLEngine on
            SSLCertificateFile /etc/letsencrypt/live/<ドメイン1>/cert.pem
            SSLCertificateKeyFile /etc/letsencrypt/live/<ドメイン1>/privkey.pem
            SSLCertificateChainFile /etc/letsencrypt/live/<ドメイン1>/chain.pem
            <Directory "/var/www/vh1-html">
                    AllowOverride   All
            </Directory>
    </VirtualHost>
  • apache 再起動
    CentOS 7

    # systemctl restart httpd
  • 証明書更新
    「--manual」で取得した場合は「renew」による自動更新ができないので、既存の証明書を削除し同名で取得し直す。

    # certbot delete

Certbot (ワイルドカード, 自動)

  • 注意点

    • テスト時は certbot のオプションに「--test-cert」を付け、テスト用の証明書を取得する。
      certbot -h testing
    • 本番用証明書は取得回数に上限がある。7日間に5回まで。
    • BIND で VIEW を使って、内向き/外向きで問い合わせに対し別々の回答をするようにしている。
    • 内向きの VIEW があると certbot がそちらに challenge 用レコードを作り誤動作するので、証明書取得/更新時は内向き VIEW を無効化する。
  • Certbot, DNS Plugin インストール (EPEL リポジトリ)

    # yum -y install yum-utils
    # yum-config-manager --enable rhui-REGION-rhel-server-extras rhui-REGION-rhel-server-optional
    # yum install -y python-requests python-six python-urllib3 pyOpenSSL certbot-apache python2-certbot-dns-rfc2136
  • 設定ファイル, スクリプト

    パーミッション オーナー パス 概要
    640 root:named /etc/named.conf BIND 設定ファイル
    644 root:root /etc/named/Kcertbot-key.+165+43987.key BIND 用キーファイル
    600 root:root /etc/named/Kcertbot-key.+165+43987.private BIND 用キーファイル
    600 root:root /etc/named/certbot_rfc2136.ini RFC2136 用認証ファイル
    640 root:named /etc/named/named-multi-view.conf 外部/内部問い合わせ両用設定
    640 root:named /etc/named/named-external-view.conf 外部問い合わせ専用設定
    640 root:named /etc/named/common.conf 共通設定
    640 root:named /etc/named/internal.view 内部問い合わせ用 view 設定
    640 root:named /etc/named/external.view 外部問い合わせ用 view 設定
    644 root:root /etc/named/<ドメイン>.lan.zone 内部問い合わせ用 zone 設定
    644 root:root /etc/named/<ドメイン>.wan.zone 外部問い合わせ用 zone 設定
    644 root:root /etc/named/_acme-challenge.<ドメイン>.wan.zone Let's Encrypt 問い合わせ用 zone 設定
    644 named:named /var/named/<ドメイン>.lan.db 内部問い合わせ用権威サーバ設定
    644 named:named /var/named/<ドメイン>.wan.db 外部問い合わせ用権威サーバ設定
    644 named:named /var/named/_acme-challenge.<ドメイン>.wan.db Let's Encrypt 問い合わせ用権威サーバ設定
    750 root:root /etc/letsencrypt/renewal-hooks/pre/external-view.sh 更新前処理スクリプト
    755 root:root /etc/letsencrypt/renewal-hooks/deploy/restartServices.sh 更新成功時処理スクリプト
    750 root:root /etc/letsencrypt/renewal-hooks/post/multi-view.sh 更新後処理スクリプト
  • BIND 用認証キーの作成
    Kcertbot-key.+165+43987.key, Kcertbot-key.+165+43987.private の2つのファイルが作成される。

    # cd /etc/named/
    # dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST certbot-key
    # cat Kcertbot-key.+165+43987.key
    certbot-key. IN KEY 512 3 165 <ハッシュ値>
  • 認証ファイル /etc/named/certbot_rfc2136.ini

    # Target DNS server
    dns_rfc2136_server = 127.0.0.1
    # Target DNS port
    dns_rfc2136_port = 53
    # TSIG key name
    dns_rfc2136_name = certbot-key.
    # TSIG key secret
    dns_rfc2136_secret = <Kcertbot-key.+165+43987.key の中のハッシュ値>
    # TSIG key algorithm
    dns_rfc2136_algorithm = HMAC-SHA512
  • /etc/named/named-multi-view.conf

    include "/etc/named/common.conf";
    include "/etc/named/internal.view";
    include "/etc/named/external.view";
  • /etc/named/named-external-view.conf

    include "/etc/named/common.conf";
    include "/etc/named/external.view";
  • /etc/named/common.conf

    //
    // named.conf
    //
    // Provided by Red Hat bind package to configure the ISC BIND named(8) DNS
    // server as a caching only nameserver (as a localhost DNS resolver only).
    //
    // See /usr/share/doc/bind*/sample/ for example named configuration files.
    //
    // See the BIND Administrator's Reference Manual (ARM) for details about the
    // configuration located in /usr/share/doc/bind-{version}/Bv9ARM.html
    
    options {
    #       listen-on port 53 { 127.0.0.1; };
    #       listen-on-v6 port 53 { ::1; };
            version         "unknown";
            directory       "/var/named";
            dump-file       "/var/named/data/cache_dump.db";
            statistics-file "/var/named/data/named_stats.txt";
            memstatistics-file "/var/named/data/named_mem_stats.txt";
            allow-query     { localhost; localnets; };
            allow-transfer  { none; };
    
            /*
            - If you are building an AUTHORITATIVE DNS server, do NOT enable recursion.
            - If you are building a RECURSIVE (caching) DNS server, you need to enable
              recursion.
            - If your recursive DNS server has a public IP address, you MUST enable access
              control to limit queries to your legitimate users. Failing to do so will
              cause your server to become part of large scale DNS amplification
              attacks. Implementing BCP38 within your network would greatly
              reduce such attack surface
            */
            recursion no;
    
            dnssec-enable yes;
            dnssec-validation yes;
    
            /* Path to ISC DLV key */
            bindkeys-file "/etc/named.iscdlv.key";
    
            managed-keys-directory "/var/named/dynamic";
    
            pid-file "/run/named/named.pid";
            session-keyfile "/run/named/session.key";
    
            forwarders {
                    8.8.8.8;
                    8.8.4.4;
            };
    };
    
    key "certbot-key." {
      algorithm hmac-sha512;
      secret "<Kcertbot-key.+165+43987.key の中のハッシュ値>";
    };
    
    logging {
            channel default_debug {
                    file "data/named.run";
                    severity dynamic;
            };
            category lame-servers { null; };
    };
  • /etc/named/internal.view

    view "internal" {
            match-clients { localhost; localnets; };
            match-destinations { localhost; localnets; };
            recursion yes;
    
            zone "." IN {
                    type hint;
                    file "named.ca";
            };
    
            include "/etc/named.rfc1912.zones";
            include "/etc/named.root.key";
            include "/etc/named/<ドメイン>.lan.zone";
    };
  • /etc/named/external.view

    view "external" {
            match-clients { any; };
            match-destinations { any; };
            allow-query { any; };
            recursion no;
            include "/etc/named/_acme-challenge.<ドメイン>.wan.zone";
            include "/etc/named/<ドメイン>.wan.zone";
    };
  • /etc/named/<ドメイン>.lan.zone

    zone "<ドメイン>" {
      type master;
      file "<ドメイン>.lan.db";
    };
  • /etc/named/<ドメイン>.wan.zone

    zone "<ドメイン>" {
      type master;
      file "<ドメイン>.wan.db";
    };
  • /etc/named/_acme-challenge.<ドメイン>.wan.zone

    zone "_acme-challenge.<ドメイン>" {
      type master;
      file "_acme-challenge.<ドメイン>.wan.db";
      update-policy {
        grant certbot-key. name _acme-challenge.<ドメイン>. txt;
      };
    };
  • /var/named/<ドメイン>.lan.db

    $ORIGIN .
    $TTL 86400      ; 1 day
    <ドメイン>               IN SOA  ns1.<ドメイン>. root.<ドメイン>. (
                                    2018090900 ; serial
                                    28800      ; refresh (8 hours)
                                    14400      ; retry (4 hours)
                                    2592000    ; expire (4 weeks 2 days)
                                    86400      ; minimum (1 day)
                                    )
                            NS      <ドメイン>.net.
                            A       192.168.1.1
                            MX      10 mail.<ドメイン>.
    $ORIGIN <ドメイン>.
    ns1                     A       192.168.1.1
    mail                    A       192.168.1.1
    *                       A       192.168.1.1
  • /var/named/<ドメイン>.wan.db

    $TTL    86400
    @       IN SOA  ns1.<ドメイン>.        root.<ドメイン>. (
                            2018090500 ; Serial
                            28800      ; Refresh
                            14400      ; Retry
                            2592000    ; Expire
                            86400      ; Minimum
                    )
            IN NS   ns1.<ドメイン>.
            IN MX 10 mail.<ドメイン>.
    @       IN A    <グローバル IP アドレス>
    ns1     IN A    <グローバル IP アドレス>
    www     IN A    <グローバル IP アドレス>
    mail    IN A    <グローバル IP アドレス>
    _acme-challenge IN NS ns1.<ドメイン>.
    *       IN A    <グローバル IP アドレス>
    <ドメイン>.    IN TXT "v=spf1 a mx ~all"
  • /var/named/_acme-challenge.<ドメイン>.wan.db

    $TTL    86400
    @       IN SOA  ns1.<ドメイン>.  root.<ドメイン>. (
                            2018090500 ; Serial
                            1h      ; Refresh
                            15m     ; Retry
                            30d     ; Expire
                            1h      ; Minimum
                    )
            IN NS   ns1.<ドメイン>.
  • /etc/letsencrypt/renewal-hooks/pre/external-view.sh

    #!/bin/bash
    
    /bin/systemctl stop named-chroot
    cp -f /etc/named/named-external-view.conf /etc/named.conf
    /bin/systemctl start named-chroot
    echo "external view"
  • /etc/letsencrypt/renewal-hooks/deploy/restartServices.sh

    #!/bin/bash
    
    LANG=en_us.UTF-8
    services="httpd postfix dovecot"
    
    if [ $(/bin/id -u) != 0 ]; then
      echo "This command requires root previlege." 1>&2
      exit 1
    fi
    
    echo "RENEWED_LINEAGE: ${RENEWED_LINEAGE}"
    echo "RENEWED_DOMAINS: ${RENEWED_DOMAINS}"
    
    for service in ${services}; do
      echo "restart ${service}"
      /bin/systemctl restart ${service} || exit $?
    done
    exit 0
  • /etc/letsencrypt/renewal-hooks/post/multi-view.sh

    #!/bin/bash
    
    /bin/systemctl stop named-chroot
    cp -f /etc/named/named-multi-view.conf /etc/named.conf
    /bin/systemctl start named-chroot
    echo "multi view"
  • <ドメイン>.jnl: create: permission denied 対策
    /var/named/chroot/var/named/data/named.run

    # chmod 770 /var/named/
    # setsebool -P named_write_master_zones 1
  • 証明書取得

    # /etc/letsencrypt/renewal-hooks/pre/external-view.sh
    # certbot certonly \
      --dns-rfc2136 \
      --dns-rfc2136-credentials /etc/named/certbot_rfc2136.ini \
      -d "*.<ドメイン>" -d <ドメイン>
    # /etc/letsencrypt/renewal-hooks/post/multi-view.sh
  • サービス登録

    # systemctl status certbot-renew.timer
    # systemctl enable --now certbot-renew.timer
    # systemctl status certbot-renew.timer
    # systemctl list-timers

動作確認

証明書の内容をテキスト出力

# openssl x509 -text -in /etc/letsencrypt/live/<ドメイン>/cert.pem

証明書の有効期限を表示

getExpireDate.sh

#!/bin/bash

CommonName=example.net

NotAfter=`openssl x509 -noout -dates -in /etc/letsencrypt/live/${CommonName}/fullchain.pem | \
  grep notAfter | \
  sed -e "s/notAfter=//" | \
  date -f - --iso-8601`
echo ${NotAfter}

SSL 接続確認

$ openssl s_client -connect <ホスト>:443

設定失敗

指定したホスト(バーチャルホスト)に SSL 証明書が適用されていない。

CONNECTED(00000003)
140139752064912:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:s23_clnt.c:794:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 289 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : 0000
    Session-ID:
    Session-ID-ctx:
    Master-Key:
    Key-Arg   : None
    Krb5 Principal: None
    PSK identity: None
    PSK identity hint: None
    Start Time: 1535884386
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---

設定成功

CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = *.<ドメイン1>
verify return:1
---
Certificate chain
  0 s:/CN=*.<ドメイン1>
    i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
  1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
    i:/O=Digital Signature Trust Co./CN=DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIGETCCBPmgAwIBAgISA3VBvI0cSyzAQGtpIaQKQRZxMA0GCSqGSIb3DQEBCwUA
...

エラー対応

AttributeError: 'module' object has no attribute 'pyopenssl'

ImportError: 'pyOpenSSL' module missing required functionality. Try upgrading to v0.14 or newer.

リンク