今回から何回かに分けて、Hashicorp製品であるConsulとNomad関連のトピックを扱っていきたいと思います。
第1弾は、ConsulのHTTP APIのTLS化です。
Consulではクラスタ内のサーバ間の通信や、外部からの要求を受け付ける際にいくつかポートをリッスンし、要求を受け入れています。
今回は、その一部であるHTTP APIをTLS化し、HTTPSでのAPIコールを受け付けるようにします。
さらにクライアント認証も有効化し、かつ、デフォルトで有効であったHTTPでのアクセスも無効化します。
これにより意図しない箇所からAPIをコールされないように強めのガードをかけることにします。
なお、注意点としては、上記のようにHTTP APIのTLSを有効化し、HTTPを無効にした場合、Consulの管理UI画面へのアクセスもHTTPSに限られます。
これは管理UIがHTTP APIと同じポートで提供されているからです。
またHTTP APIだけでなく、ConsulのCLIを使った操作にも内部でHTTP APIが使用されるため、こちらも必要な証明書、鍵を指定することが必要になります。
TLS有効化に必要な作業ステップは以下のとおりです。
1.各種の鍵、証明書の作成
1-① ルートCAの証明書と鍵を作成する
1-② Consulサーバ用サーバ証明書と鍵を作成する
1-③ Consulクライアント用サーバ証明書と鍵を作成する
1-④ APIにリクエストを発行するクライアントのためのクライアント証明書と鍵を作成する
2.証明書、鍵の配置
2-① Consulサーバに必要な証明書や鍵を配置し、Consul設定ファイルに記載する
2-② Consulクライアントに必要な証明書や鍵を配置し、Consul設定ファイルに記載する
2-③ CLIやHTTP APIを実行するノードに必要な証明書や鍵を配置する
さて、上記を実現するためにはいくつかの選択肢がありますが、今回は以下のアプローチをとることにします。
まず、1-③、2-②ですが、Consulの「auto-encryption」機能を使うことで、手間を省くことができます。
これはConsul Connect CAの機能を使い、Consulクライアント向けのサーバ証明書と鍵を生成し、自動的にすべてのConsulクライアントに配布する機能です。
今回はこのアプローチを選択します。
では、進めていきましょう。
1-①ルートCAの証明書と鍵を作成する
Consul Connectで扱うルートCAの証明書と鍵を生成します。これはプライベートなルートCAです。
Consul Connect CAは、ルート証明書がSPIFFE SVID署名証明書である必要があるとのことです。
しかしながらSPIFFE SVID署名証明書としてルートCAに求められる要件が不透明なため、公式のチュートリアルに示されている手順をベースに進めていきます。
ここではまず、consulのCLIを使ってCAの証明書と鍵を生成します。
CNの項目値を指定できるようなので指定しておきましょう。
$ consul tls ca create -common-name="My Consul CA"
==> Saved consul-agent-ca.pem
==> Saved consul-agent-ca-key.pem
$
$ cat consul-agent-ca.pem
-----BEGIN CERTIFICATE-----
MIICljCCAj2gAwIBAgIQYiBa30DnStxyYStLYGmmdjAKBggqhkjOPQQDAjCBjjEL
MAkGA1.....(中略)...........
.........................kNYxxa2
-----END CERTIFICATE-----
$
$ consul-agent-ca-key.pem
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIHHeRyQ7iv9lCWd12S9N9+WR9j6yliEYJweHR8ZpP/00oAoGCCqGSM49
...(中略)...
nFIOOLpdblfmD5U1l4SS1CFIDTvW9NM+og==
-----END EC PRIVATE KEY-----
$
$ openssl x509 -text -noout < consul-agent-ca.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
62:20:5a:df:40:e7:4a:dc:72:61:2b:4b:60:69:a6:76
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = US, ST = CA, L = San Francisco, street = 101 Second Street, postalCode = 94105, O = HashiCorp Inc., CN = My Consul CA
Validity
Not Before: Mar 29 05:51:10 2021 GMT
Not After : Mar 28 05:51:10 2026 GMT
Subject: C = US, ST = CA, L = San Francisco, street = 101 Second Street, postalCode = 94105, O = HashiCorp Inc., CN = My Consul CA
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:a4:e3:08:0c:32:b1:43:a8:0d:a3:0e:59:ea:25:
...(中略)...
d6:f4:d3:3e:a2
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Certificate Sign, CRL Sign
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Subject Key Identifier:
2E:C5:6A:6E:06:5E:35:FF:4B:52:29:7D:14:2C:1D:FC:AF:C1:07:73:39:67:C9:BE:F1:55:F3:11:CB:BE:D3:AD
X509v3 Authority Key Identifier:
keyid:2E:C5:6A:6E:06:5E:35:FF:4B:52:29:7D:14:2C:1D:FC:AF:C1:07:73:39:67:C9:BE:F1:55:F3:11:CB:BE:D3:AD
Signature Algorithm: ecdsa-with-SHA256
30:44:02:20:2d:04:19:d6:14:3b:44:82:0a:da:98:03:2b:e7:
...(中略)...
d1:e5:5b:fb:6b:10:3d:cb:1d:ec:46:43:58:c7:16:b6
$
$ openssl ec -text -noout < consul-agent-ca-key.pem
read EC key
Private-Key: (256 bit)
priv:
71:de:47:24:3b:8a:ff:65:09:67:75:d9:2f:4d:f7:
...(中略)...
fd:34
pub:
04:a4:e3:08:0c:32:b1:43:a8:0d:a3:0e:59:ea:25:
...(中略)...
d6:f4:d3:3e:a2
ASN1 OID: prime256v1
NIST CURVE: P-256
PEM形式のCAの証明書(consul-agent-ca.pem)と鍵(consul-agent-ca-key.pem)ができました。
opensslコマンドで中身を確認しています。
SubjectにはHashicorpの情報が埋められていますが、プライベートCAなので気にしないで行きましょう。
ECDSA 256bitの鍵ペアが作られていて、自己署名されていることがわかります。
1-② Consulサーバ用サーバ証明書と鍵を作成する
続いてConsulサーバに配置するサーバ証明書を作成します。
ここではサーバにアクセスするときに使用するDNS名を「-additional-dnsname」で追加しています。
$ consul tls cert create -server \
-additional-dnsname=consul.mydomain.tk \
-additional-dnsname=consul.service.consul \
-ca ./consul-agent-ca.pem -key ./consul-agent-ca-key.pem
==> WARNING: Server Certificates grants authority to become a
server and access all state in the cluster including root keys
and all ACL tokens. Do not distribute them to production hosts
that are not server nodes. Store them as securely as CA keys.
==> Using ./consul-agent-ca.pem and ./consul-agent-ca-key.pem
==> Saved dc1-server-consul-0.pem
==> Saved dc1-server-consul-0-key.pem
$
$ openssl x509 -text -noout < dc1-server-consul-1.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
53:d1:96:b8:e4:44:2a:60:04:c8:11:fc:c8:49:c9:77
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = US, ST = CA, L = San Francisco, street = 101 Second Street, postalCode = 94105, O = HashiCorp Inc., CN = My Consul CA
Validity
Not Before: Mar 29 06:41:19 2021 GMT
Not After : Mar 29 06:41:19 2022 GMT
Subject: CN = server.dc1.consul
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:68:a5:d9:e1:e9:1c:cd:01:c8:3a:af:fd:8a:5e:
...(中略)...
30:1e:09:c4:dc
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Key Identifier:
7B:1B:08:C8:CD:7F:B9:82:A0:AE:3B:32:47:00:C3:68:0A:AC:95:67:8F:AE:0E:3E:89:56:C3:2C:64:86:3B:E3
X509v3 Authority Key Identifier:
keyid:2E:C5:6A:6E:06:5E:35:FF:4B:52:29:7D:14:2C:1D:FC:AF:C1:07:73:39:67:C9:BE:F1:55:F3:11:CB:BE:D3:AD
X509v3 Subject Alternative Name:
DNS:consul.mydomain.tk, DNS:consul.service.consul, DNS:server.dc1.consul, DNS:localhost, IP Address:127.0.0.1
Signature Algorithm: ecdsa-with-SHA256
30:45:02:21:00:83:c0:bd:69:17:20:8f:e8:6b:ab:e3:5c:e7:
--(中略)--
22:eb:40:69:cd:6b:1f:fe:61:99:65:aa:69:3d:1b:fb:95
サーバ証明書「dc1-server-consul-0.pem」とサーバ鍵「dc1-server-consul-0-key.pem」が生成されました。
「Issuer」や「X509v3 Authority Key Identifier:」keyid部分を見ると作成したルートCA(2E:C5:6A:6E:06:5E:~)で署名されているのがわかります。
また、オプションで追加指定したDNS名もSAN(Subject Alternative Name)に設定されているのがわかります。
1-③ Consulクライアント用サーバ証明書と鍵を作成する
前述のように、Consulクライアント用サーバ証明書・鍵はConsul側で自動で生成・配置する機能に頼るので、ここでの作業はありません。
次に進みましょう。
1-④ APIにリクエストを発行するクライアントのためのクライアント証明書と鍵を作成する
Consul CLIもしくはHTTP APIを実行するクライアントのためにクライアント証明書も作成します。
$ consul tls cert create -cli -ca ./consul-agent-ca.pem -key ./consul-agent-ca-ke
y.pem
==> Using ./consul-agent-ca.pem and ./consul-agent-ca-key.pem
==> Saved dc1-cli-consul-0.pem
==> Saved dc1-cli-consul-0-key.pem
クライアント証明書「dc1-cli-consul-0.pem」とクライアント用の鍵「dc1-cli-consul-0-key.pem」が作成できました。
さて、このクライアント証明書はConsulの管理UI画面をhttpsでアクセスする際も要求されることになるので、
Webブラウザ側にこのクライアント証明書と鍵を使うように設定する必要があります。
まず、opensslコマンドを使って、クライアント証明書と鍵をPCKS.12形式にパッケージングします。
$ openssl pkcs12 -export -out consul-ui-client.p12 -passout pass: \
-in dc1-cli-consul-0.pem -inkey dc1-cli-consul-0-key.pem -certfile ./consul-agent-ca.pem
これでパスフレーズなしのPKCS.12ファイル「consul-ui-client.p12」が生成されました。
このファイルをPC側のWebブラウザに登録することで管理UI画面をHTTPSでアクセス可能です。
(Windowsの場合、consul-ui-client.p12をダブルクリックすると「証明書のインポートウイザード」が起動しますので、ウイザードに従って登録します)
それでは生成したファイルをサーバに配置して設定を加えていきましょう。
2-① Consulサーバに必要な証明書や鍵を配置し、Consul設定ファイルに記載する
Consulサーバには以下の4つのファイルをサーバに配置します。
- consul-agent-ca.pem(ルートCAの証明書)
- dc1-server-consul-0.pem(サーバ証明書)
- dc1-server-consul-0-key.pem(サーバ証明書のペアとなる鍵ファイル)
以下はConsulサーバの設定ファイルの例です。
data_dir = "/opt/consul"
log_level = "INFO"
datacenter = "dc1"
server = true
bootstrap_expect = 3
bind_addr = "{{ GetInterfaceIP \"eth0\" }}"
client_addr = "0.0.0.0"
retry_join = ["provider=aws tag_key=node_type tag_value=consul_server"]
# Note: http not used
ports {
http = -1
https = 8501
grpc = 8502
}
connect {
enabled = true
ca_provider = "consul"
ca_config {
root_cert = <<EOF
-----BEGIN CERTIFICATE-----MIICmTCCAj+gAwIBAgIQUxo8wNlmKsjkAdaVu2cVATAKBggqhkjOPQQDAjCBjzELMAkGA1UEldDEOMAwGA1UE--(中略)--cDba60lZm+J5KyLoC9dzEl7s-----END CERTIFICATE-----
EOF
private_key =<<EOF
-----BEGIN EC PRIVATE KEY-----MHcCAQEEIItxNhgEb6jSAWqFhRhuRmkGNLMyr7vK3oWlyEOtUCzvoAoGCCqGSM49
AwEHoUQ--(中略)--Ui8EfaeA==-----END EC PRIVATE KEY-----
EOF
}
}
ui_config {
enabled = true
}
enable_agent_tls_for_checks = true
# Require TLS (client cert required)
verify_incoming = true,
verify_outgoing = true,
verify_server_hostname = true,
ca_file = "/opt/tls/consul-agent-ca.pem",
cert_file = "/opt/tls/dc1-server-consul-0.pem",
key_file = "/opt/tls/dc1-server-consul-0-key.pem",
auto_encrypt {
allow_tls = true
}
ポイントは以下です。
- httpを無効にし、httpsを8501ポートで有効にするため、portsブロックでポートを指定する。
- Consul Connect CAの証明書と鍵をデフォルトから置き換えるため、root_certとprivate_keyにそれぞれのPEMファイルの中身を張り付ける。
- verify_incoming: trueの場合、他のサーバやクライアントからの接続にクライアント認証を要求する
- verify_outgoing: trueの場合、他のサーバへのRPCにTLSを使用する
- verify_server_hostname: trueの場合、他のサーバへのRPC実行時にサーバ証明書のホスト名がserver..を検証する。
- datacenter名、domain名はConsulサーバのサーバ証明書作成時に指定したもの(デフォルトでは"dc1"と"consul"が使われる)
- ca_file,cert_file,key_fileで各種証明書ファイル、鍵ファイルの配置パスを指定する。
- サーバ証明書を自動でConsulクライアントへ配置するように、auto_encryptブロックで「allow_tls = true」を指定する。
2-② Consulクライアントに必要な証明書や鍵を配置し、Consul設定ファイルに記載する
Consulクライアントには以下のファイルを配置する必要があります。
サーバ証明書と鍵は自動で配置されるので、不要です。
- consul-agent-ca.pem(ルートCAの証明書)
以下はConsulサーバの設定ファイルの例です。
data_dir = "/opt/consul"
datacenter = "dc1"
Log_level = "INFO"
server = false
bind_addr = "{{ GetInterfaceIP \"eth0\" }}"
client_addr = "0.0.0.0"
retry_join = ["provider=aws tag_key=node_type tag_value=consul_server"]
enable_local_script_checks = true
ports {
http = -1
https = 8501
grpc = 8502
}
connect {
enabled = true
}
enable_agent_tls_for_checks = true
verify_incoming = false,
verify_outgoing = true,
verify_server_hostname = true,
ca_file = "/opt/tls/consul-agent-ca.pem",
# enable auto_deploy cert
auto_encrypt = {
tls = true
}
クライアント側は基本的にマニュアルに記載の通りの設定です。
2-③ CLIやHTTP APIを実行するノードに必要な証明書や鍵を配置する
ConsulのCLIやHTTP APIを実行する際に、以下のファイルを使用します。
- consul-agent-ca.pem(ルートCAの証明書)
- dc1-cli-consul-0.pem(クライアント証明書)
- dc1-cli-consul-0-key.pem(クライアント証明書のペアとなる鍵ファイル)
CLIを実行する場合、規定された環境変数を設定すると便利です。
以下のように設定します。
$ export CONSUL_HTTP_ADDR=https://consul.mydomain.tk:443
$ export CONSUL_CACERT=/path/to/consul-agent-ca.pem
$ export CONSUL_CLIENT_CERT=/path/to/dc1-cli-consul-0.pem
$ export CONSUL_CLIENT_KEY=/path/to/dc1-cli-consul-0-key.pem
$
$ #環境変数が使用されるため、特にオプションの指定は不要
$ consul members
curlでHTTP APIを呼び出す場合は、オプションで以下のように指定します。
```console
$ curl --cacert ${CONSUL_CACERT} --cert ${CONSUL_CLIENT_CERT} --key ${CONSUL_CLIENT_KEY} \
${CONSUL_HTTP_ADDR}/v1/catalog/datacenters
さて以上で、ConsulのHTTP APIをTLS化することができました。
クライアント認証を必須としているため、多少、面倒ではありますが、これで通信の秘匿化とサーバ、クライアントの真正性がチェックできるようになりました。
おまけ:Gossip通信の暗号化
上記のTLSの有効化では、エージェント間のRPC呼び出しやエージェントへのHTTP API呼び出しを保護します。
一方、エージェント間のゴシップ通信はUDPで行われるため、別の手段で通信内容を保護する必要があります。
ゴシップ通信の暗号化はチュートリアルに記載しているとおり、同一の共通鍵を全エージェント(Consulサーバ、Consulクライアントのどちらも)の設定ファイルに記載します。
consul keygenで生成した32byteのBase64値を設定ファイルのencryptに指定します。
$ consul keygen
pNb02VRrpzaaLsXMs9U0u9ZbwYsm4o+lcTWBmGEot7U=
data_dir = "/opt/consul"
datacenter = "dc1"
Log_level = "INFO"
...
encrypt = "pNb02VRrpzaaLsXMs9U0u9ZbwYsm4o+lcTWBmGEot7U=",
...
今回はConsulのHTTP APIのTLS化を扱いました。
次回はNomadのHTTP APIのTLS化を扱いたいと思います。