repl.info

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形式で生成するリソース。

  2. tls_cert_request 証明書署名要求(CSR)をPEM形式で生成するリソース。

  3. 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を使うのも一つの手なのだろうか…