TerraformのTLS Providerを試す

TerraformのProviderを眺めていたらTLS Providerというものを発見したので試してみる。まず、TLS Providerが提供するリソースは4種類ある。 1. tls_private_key 秘密鍵をPEM形式で生成するリソース。RSAとECDSAをアルゴリズムとして選択できる。注意点として、これによって生成された秘密鍵はtfstateファイルに暗号化されずに保存される。Production環境での利用は推奨していない。
  1. tls_self_signed_cert
自己署名付きTLS証明書をPEM形式で生成するリソース。
  1. tls_cert_request
証明書署名要求(CSR)をPEM形式で生成するリソース。
  1. tls_locally_signed_cert
証明書署名要求(CSR)を用いてTLS証明書を生成し、提供された認証局(CA)の秘密鍵で署名するリソース。 なるほど、結構便利そうだ。軽く試してみよう。今回は、自己署名したルートCAと、それを使ってSSL証明書を生成する。その後、SSL証明書を読み込ませたHTTPSサーバーへリクエストを投げてみよう。まずはルートCAを作る。allowed_usesのパラメータには気をつける必要がある。Provider側の文字列とマッチしていない場合、設定されないようだ。
resource "tls_private_key" "root" {
  algorithm   = "RSA"
}

resource "tls_self_signed_cert" "root" {
  key_algorithm   = "RSA"
  private_key_pem = "${tls_private_key.root.private_key_pem}"

  validity_period_hours = 26280
  early_renewal_hours   = 8760

  is_ca_certificate = true

  allowed_uses = ["cert_signing"]

  subject {
    common_name  = "hello2018.repl.info"
  }
}

resource "local_file" "root_ca_pem" {
  filename = "root_ca.pem"
  content  = "${tls_self_signed_cert.root.cert_pem}"
}

resource "local_file" "root_ca_key" {
  filename = "root_ca.key"
  content  = "${tls_private_key.root.private_key_pem}"
}
terraform applyすると、秘密鍵と証明書がファイルとして出力されているはずだ。
$ ls root_ca.*
root_ca.key  root_ca.pem
root_ca.pemを見てみる。tfファイルで設定した内容が反映された証明書になっていることがわかる。
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            db:d7:70:35:81:54:70:11:e8:cc:7d:92:d9:7b:56:7f
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=hello2018.repl.info
        Validity
            Not Before: Aug 29 08:03:01 2018 GMT
            Not After : Aug 28 08:03:01 2021 GMT
        Subject: CN=hello2018.repl.info
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:c3:b3:12:27:39:e2:5d:6f:ac:ec:c1:92:b6:3f:
                    77:3e:19:bb:56:14:80:0c:03:5a:15:a3:21:80:55:
                    c3:61:93:5f:33:a4:c9:ae:ab:68:91:80:50:e0:f0:
                    89:4c:88:02:17:d8:f0:f3:c1:2f:93:b4:27:cc:35:
                    76:8f:7d:c7:7e:ef:8d:0d:01:8a:ad:8e:1b:d2:69:
                    be:e1:5f:76:bf:d9:90:86:97:89:21:f4:dc:5b:f5:
                    7f:5c:1d:5f:7c:cb:cc:c2:e5:0a:71:51:0b:39:a5:
                    f0:ca:7b:5c:d8:d7:9e:2f:5f:ff:c1:ff:f5:bf:85:
                    2e:a4:5a:a3:0e:ee:2f:81:81:82:fc:4f:fd:7f:a0:
                    80:59:73:6e:57:03:42:74:d2:3a:b4:57:b7:ad:1c:
                    22:20:7d:28:8c:47:34:72:46:22:2b:34:ab:7b:45:
                    26:7d:fd:86:1e:b9:fc:6b:9e:f5:a2:1a:fa:23:16:
                    9a:69:b0:1d:a1:f5:6e:fb:43:03:22:8b:68:d5:cd:
                    a8:8f:52:89:0b:57:af:bf:24:79:2c:7a:fe:ab:74:
                    6b:5a:31:e6:7a:2d:55:0c:37:d6:f1:d8:eb:0a:c6:
                    df:60:cd:04:1b:11:42:d9:86:65:a8:8a:7f:cc:d9:
                    6e:f4:54:e2:d4:75:5a:19:36:e2:1e:bd:56:5b:87:
                    d0:e1
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Subject Key Identifier:
                8F:F3:3D:D0:54:63:37:62:2E:CA:97:D8:BC:EF:0B:4F:D9:37:A0:51
    Signature Algorithm: sha256WithRSAEncryption
         49:31:d0:04:d3:27:43:ae:d9:4b:46:a6:90:34:b2:cd:fa:b1:
         e6:a6:ed:38:92:ca:96:82:cf:2d:90:e7:36:f4:b6:77:1b:40:
         f5:0a:06:06:a4:d8:61:9c:fc:6d:02:30:b1:41:50:2a:59:ef:
         00:db:a0:d6:5c:03:50:9e:0d:70:80:b9:ce:46:29:91:99:12:
         45:9a:fa:f5:c2:4f:71:60:6b:ce:bb:b0:0f:04:f6:e3:d8:63:
         e6:d4:70:bd:a0:99:14:71:3e:40:a1:bb:e1:46:79:77:ef:23:
         b7:ec:27:1b:80:ad:33:ef:93:92:ae:04:70:15:c0:f2:b2:34:
         63:81:92:88:4f:a1:78:6f:74:f3:f0:64:09:e2:d5:e1:ed:77:
         d3:26:d5:b2:6e:2f:3a:81:5c:00:f8:ec:4b:e3:36:45:7b:ec:
         26:92:b9:42:28:f5:38:bf:0b:49:8c:eb:d6:47:28:ca:d9:41:
         63:e1:51:01:b5:05:e6:a3:3d:93:7f:02:9e:f4:8a:60:86:13:
         9a:4a:df:ef:38:1c:4c:63:bc:4d:38:30:f3:d3:01:3c:2b:2a:
         77:d7:73:b5:55:3c:53:69:d9:99:8a:ce:90:cd:d0:10:56:c5:
         76:d7:49:b2:a8:35:2b:5a:9e:f2:5b:9d:2a:83:3f:ce:e0:8f:
         40:6a:b4:4c
さて、次はSSLサーバー証明書を作ってみる。
resource "tls_private_key" "foo" {
  algorithm   = "RSA"
}

resource "tls_cert_request" "foo" {
  key_algorithm   = "${tls_private_key.foo.algorithm}"
  private_key_pem = "${tls_private_key.foo.private_key_pem}"

  subject {
    common_name  = "foo.hello2018.repl.info"
  }

  dns_names = [
    "localhost",
  ]
}

resource "tls_locally_signed_cert" "foo" {
  ca_key_algorithm   = "${tls_private_key.root.algorithm}"

  cert_request_pem   = "${tls_cert_request.foo.cert_request_pem}"

  ca_private_key_pem = "${tls_private_key.root.private_key_pem}"
  ca_cert_pem        = "${tls_self_signed_cert.root.cert_pem}"

  validity_period_hours = 12

  allowed_uses = [
    "server_auth",
  ]
}

resource "local_file" "foo_cert_pem" {
  filename = "foo.pem"
  content  = "${tls_locally_signed_cert.foo.cert_pem}"
}

resource "local_file" "foo_cert_key" {
  filename = "foo.key"
  content  = "${tls_private_key.foo.private_key_pem}"
}
これで、証明書と秘密鍵がファイルとして生成されているはずだ。
$ ls foo*
foo.key  foo.pem
foo.pemの中身も見てみよう。
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            c3:55:e7:99:94:8b:41:3c:f2:86:e9:67:2f:d4:ea:7a
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=hello2018.repl.info
        Validity
            Not Before: Aug 29 13:04:46 2018 GMT
            Not After : Aug 30 01:04:46 2018 GMT
        Subject: CN=foo.hello2018.repl.info
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:d2:49:e6:14:a7:7b:4a:f5:51:1c:d6:27:62:05:
                    2d:c1:43:3a:53:44:f6:5d:01:91:1a:64:a7:82:6e:
                    e0:ae:de:ad:48:52:9e:ea:d8:10:99:d6:9a:6a:1f:
                    38:16:42:7a:5d:9a:a7:29:4f:03:d6:20:71:5b:2b:
                    e4:2e:42:ee:eb:6f:f0:dc:2b:bd:5c:45:6f:fe:41:
                    49:36:de:9a:0e:71:e4:95:15:e5:1a:61:9f:4d:9d:
                    77:b7:2a:44:a8:f1:95:cf:e5:35:97:53:83:08:1b:
                    ff:14:4c:78:c8:18:74:a4:19:7f:4a:c7:cd:e0:21:
                    0e:ef:c8:98:2a:8b:8d:d2:8b:39:1c:14:8b:b8:18:
                    23:56:23:e3:3c:60:2d:3d:5e:d6:d8:2e:65:1e:05:
                    b6:1d:16:4c:f3:41:5c:50:6d:e7:aa:a4:04:8a:f3:
                    3c:bc:32:b5:a1:8f:c0:c0:ff:ce:9e:f7:25:f8:d1:
                    ab:50:18:a4:bb:0b:61:6b:c5:dd:2e:3d:49:7d:e7:
                    e0:4e:92:17:96:88:dc:08:9c:f9:03:74:9c:17:8d:
                    95:a7:75:61:f4:41:87:6b:69:78:ed:5d:16:7a:d6:
                    e5:11:b3:7f:a0:0e:8e:64:a2:47:a3:65:45:84:2e:
                    a9:07:57:7f:f5:39:c5:fd:34:77:9c:d9:8e:57:fc:
                    44:73
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Authority Key Identifier:
                keyid:8F:F3:3D:D0:54:63:37:62:2E:CA:97:D8:BC:EF:0B:4F:D9:37:A0:51

            X509v3 Subject Alternative Name:
                DNS:localhost
    Signature Algorithm: sha256WithRSAEncryption
         46:4a:39:3e:bb:cd:fa:2d:ba:98:81:38:30:7b:68:a8:15:35:
         56:3c:36:1a:b1:95:f2:a2:f4:e5:c6:0b:38:ce:e8:bf:8a:12:
         63:b5:55:9a:27:c8:91:f8:ee:29:b4:3e:9c:80:7a:34:73:49:
         cc:6a:0c:8d:1f:9a:f9:4f:f4:ce:1a:0e:26:86:fb:fb:f8:95:
         25:5a:dd:8d:db:ec:7b:bf:90:59:aa:83:d9:7d:62:5a:f1:70:
         13:52:b4:a6:38:c1:8e:30:49:2d:9d:c1:43:71:7e:8b:76:28:
         88:5a:5b:3b:1a:e3:cf:7e:42:3d:04:c6:db:8a:9b:16:3e:92:
         77:f4:d7:b1:fa:a3:7e:1a:b7:f1:39:f7:b1:0b:f6:fb:1a:e1:
         7e:81:e6:29:fb:b4:21:b4:73:fc:8a:2e:b4:78:b9:ae:9e:82:
         4b:0a:ca:53:4a:d6:26:89:7c:a9:c2:0b:40:c1:3b:fc:76:ff:
         af:b1:d8:cb:9b:4f:16:2a:37:7a:3a:fe:05:71:27:bc:0d:9e:
         8c:45:73:59:07:76:b5:5a:7e:48:5a:b2:04:95:98:ed:42:cd:
         95:ed:93:d9:65:fd:c4:64:d6:24:be:a4:ae:8a:b6:8d:57:e8:
         fb:4c:cc:4a:e4:d1:b2:42:7f:2f:a6:d4:28:eb:d2:b1:5c:6d:
         ca:ab:e3:71
nginxなどを使ってもいいが、今回はRubyでHTTPSサーバーを動かすことにする。 ローカルサーバでSSLしようとして試行錯誤の上結局WEBrick使った話のコードを使わせてもらっています。
require 'webrick'
require 'webrick/https'

cert = ARGV[0]
key = ARGV[1]

server = WEBrick::HTTPServer.new(
    :BindAddress => '127.0.0.1',
    :Port => 3000,
    :DocumentRoot => '.',
    :SSLEnable  => true,
    :SSLCertificate => OpenSSL::X509::Certificate.new(open(cert).read),
    :SSLPrivateKey => OpenSSL::PKey::RSA.new(open(key).read))
Signal.trap('INT') { server.shutdown }
server.start
curlでcacertを指定してアクセス。証明書エラーにならずに無事繋がった。
$ curl -v --cacert ./root_ca.pem    https://localhost:3000/
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 3000 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 3000 (#0)
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: ./root_ca.pem
  CApath: /usr/local/etc/openssl/certs
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=foo.hello2018.repl.info
*  start date: Aug 29 13:04:46 2018 GMT
*  expire date: Aug 30 01:04:46 2018 GMT
*  subjectAltName: host "localhost" matched cert's "localhost"
*  issuer: CN=hello2018.repl.info
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.60.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html; charset="UTF-8"
< Server: WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) OpenSSL/1.0.2o
< Date: Wed, 29 Aug 2018 13:08:52 GMT
< Content-Length: 2417
< Connection: Keep-Alive
<
なかなか使いどころが限られるProviderだと思うが、うまく使えば証明書生成・管理を楽に行えそうである。tls_private_keyリソースの説明に書かれている通り、秘密鍵がtfstateファイルに含まれるため取り扱いには注意が必要だろう。本番環境では別途読み込むとか、Vault Providerを使うようにする、という工夫が必要になりそうだ。Kubernetesのクラスターを扱おうとすると証明書の管理どうすんの、という話になるけどどうやるのがいいのかよく分からない。Terraformを使ってインスタンスを扱う場合はこういうProviderを使うのも一つの手なのだろうか…

Leave a Reply

Your email address will not be published. Required fields are marked *