OpenSSLでTLS証明書を作る

etcdクラスタをTLS有効にして運用するため,TLS証明書を作成する必要があった.
ちゃんとした手順をあまり理解できていなかったため,備忘録として残しておく.

TLS証明書発行までの流れ

TLS証明書は次にような流れで発行する.

  1. 秘密鍵を作成
  2. CSRを作成
  3. TLS証明書を作成

これ以降,上記の具体的な手順について説明する.

今回使用したOpenSSLのバージョンは次の通り.

$ openssl version
OpenSSL 1.1.1  11 Sep 2018

秘密鍵を作成

秘密鍵の作成は, genpkey サブコマンドを使用する.
genrsa サブコマンドでもできるようだが,マニュアルに genrsa 含めいくつかのコマンドは genpkey に置き換えられたという記述があるので,今回は genpkey を使用する.

今回は次のような鍵を作成する.
* 公開鍵アルゴリズム: RSA
* 鍵長: 2048bit
* 秘密鍵を暗号化するためのアルゴリズム: AES 128bit

この条件で秘密鍵を作成するには次のようなコマンドを使用する.

$ openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 -aes128 -out ca.key

コマンドを実行するとパスフレーズを求められるので,適当なものを入力する.
実行すると次のようになる.

$ openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 -aes128 -out ca.key
..........................................................+++++
................................................+++++
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:

オプションの意味は次の通り.
* -algorithm rsa : 公開鍵アルゴリズムとしてRSAを使用する
* -pkeyopt rsa_keygen_bits:2048 : RSAの鍵長を2048bitにする
* -aes128 : 秘密鍵をAES 128bitで暗号化する
* -out ca.key : ca.key という名前で秘密鍵を生成する

CSRを作成

TLS証明書を作成するには,まずCSR (Certificate Signing Request) を作成する必要がある.
このCSRを元に,CA (Certificate Authority) がTLS証明書を作成するという流れになる.

この手順では,前手順で作成した秘密鍵を使ってCSRを作成する.
これは req サブコマンドで行うことができる.

$ openssl req -new -key ca.key -out ca.csr

実行すると次のようになる.
まずパスフレーズを聞かれるので,秘密鍵を作成したときに入力したのと同じものを入力する.

その後,いくつか情報を聞かれるので必要に応じて入力する.
このとき,何も入力せずにEnterを押すとデフォルト値が使用されるが,フィールドを空にしておきたい場合は . (ピリオド)を入力してからEnterを押すようにする.
. をつけることにより,このフィールドは空だと明示的に指定できる.

$ openssl req -new -key ca.key -out ca.csr
Enter pass phrase for ca.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:.
Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
Organizational Unit Name (eg, section) []:.
Common Name (e.g. server FQDN or YOUR name) []:mas9612.net
Email Address []:.

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:.
An optional company name []:.

オプションの意味は次の通り.
* -new : 新しいCSRを作成するときに指定する
* -key ca.key : 秘密鍵を指定する.この鍵とペアになる公開鍵が署名される
* -out ca.csr : ca.csr という名前でCSRを作成する

TLS証明書を作成

CSRが作成できたら,最後にTLS証明書を作成する.

今回は,次の2種類の方法を試す.
* 自己署名: 自分の秘密鍵を使って署名する
* 別に用意したCAによる署名: CAの秘密鍵を使って署名する

自己署名

自己署名を行うには,次のようなコマンドを実行する.

$ openssl x509 -req -in ca.csr -out ca.crt -signkey ca.key -days 365

コマンドを実行すると次のようになる.
ここでもパスフレーズを聞かれるので,秘密鍵作成時のものを入力する.

$ openssl x509 -req -in ca.csr -out ca.crt -signkey ca.key -days 365
Signature ok
subject=C = JP, CN = mas9612.net
Getting Private key
Enter pass phrase for ca.key:

オプションの意味は次の通り.
* -req : このオプションを指定すると,CSRを読み込んでTLS証明書を作成する
* -in ca.csr : 読み込むCSRファイルを指定
* -out ca.crt : ca.crt という名前でTLS証明書を出力する
* -signkey ca.key : ca.key を使って署名を行う
* -days 365 : TLS証明書の期限を365日にする

CAによる署名

次に,CAによる署名を試してみる.
といっても,自分で何かCAを運用しているわけではないので,今回は先程作った秘密鍵とTLS証明書をCAのものと仮定し,それを使って署名をするということを試す.

まず,先ほどとは別の秘密鍵とCSRを作成しておく.

$ openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 -aes128 -out etcd0.key
$ openssl req -new -key ca.key -out etcd0.csr

CSRまで作成できたら,それをCAの証明書で署名する手順に移る.
これも自己署名と同様に, x509 サブコマンドを使うと簡単にできる.
次のようなコマンドを実行する.

$ openssl x509 -req -in etcd0.csr -out etcd0-ca.crt -days 365 -CA ca.crt -CAkey ca.key -CAcreateserial

実行すると次のようになる.

$ openssl x509 -req -in etcd0.csr -out etcd0-ca.crt -days 365 -CA ca.crt -CAkey ca.key -CAcreateserial
Signature ok
subject=C = JP, CN = mas9612.net
Getting CA Private Key
Enter pass phrase for ca.key:

CA関連のオプションは次の通り.
* -CA ca.crt : 署名に使用するCAのTLS証明書を指定する
* -CAkey ca.key : 署名に使用するCA秘密鍵を指定する
* -CAcreateserial : CAのシリアルナンバーファイルが存在しない場合,自動で作成する

秘密鍵やCSR,TLS証明書の内容を確認する

上記の手順でTLS証明書までの作成ができた.
作成した各種ファイルは,opensslコマンドを使用することでその内容を確認することができる.

ここではそれについて説明する.

秘密鍵の内容確認

秘密鍵の内容を確認するには, rsa サブコマンドを使用する.

普通に秘密鍵を読み込むには, -in オプションに秘密鍵のファイル名を指定するだけでできる.
なお,このコマンドを実行してもただ単にファイルの内容がそのまま表示されるだけである.

$ openssl rsa -in ca.key
Enter pass phrase for ca.key:
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAoTo44Vgr5vUZvhlfhDGrUK3DBVKexWoG5Hq29oMhEc5HCSTk
XBL28/gGVoW6NtW7HMiM2zkPE0ETC/Hi8ef9CVjE414F5OpIgppBjYxjjmDEDita
...(省略)
bBxlNDpyMteIfxg1cix3U2V+D1mWhBAKqF95xJNASQZtfeabZHZzCH7YbO0eGFIv
m9ZFXwYPhq+ORWBJE9+hL1PsgvkiruEECIKTE2Pfeb8TkiO1Gls=
-----END RSA PRIVATE KEY-----

これに -text オプションを指定すると,秘密鍵の内容を調べてNやE,Dを値を表示してくれる.

$ openssl rsa -in ca.key -text
Enter pass phrase for ca.key:
RSA Private-Key: (2048 bit, 2 primes)
modulus:
    00:a1:3a:38:e1:58:2b:e6:f5:19:be:19:5f:84:31:
    ...(省略)
    43:c8:f7:b1:7f:e0:9f:5f:9c:25:83:55:1d:d4:b7:
    de:9f
publicExponent: 65537 (0x10001)
privateExponent:
    58:d7:6d:5a:77:2c:91:f2:c3:81:a6:17:a5:0f:7d:
    ...(省略)
    b4:d7:70:bb:59:56:df:92:9f:99:40:a4:42:97:4d:
    c9
prime1:
    00:d1:a0:9d:d8:96:8d:8d:48:d0:76:c8:76:8e:b9:
    ...(諸略)
    a0:30:e1:b3:b5:d2:e8:d4:00:f3:65:93:ab:d5:b3:
    2f:0e:aa:bd:94:75:2d:a2:05
prime2:
    00:c4:e4:ac:2a:c5:59:aa:a1:d2:3c:2a:8c:dd:bf:
    ...(省略)
    bf:32:c3:4b:98:dc:57:ab:53
exponent1:
    4c:d1:5f:06:8f:a5:2f:b1:0f:33:78:22:7a:0a:ef:
    ...(省略)
    ac:16:45:82:b1:ae:17:41
exponent2:
    41:b8:e3:0f:53:d8:de:70:2d:b1:0f:b2:fd:c2:17:
    ...(省略)
    ee:9d:e9:fa:18:72:db:29
coefficient:
    2f:ba:6e:47:c5:bb:60:2e:4f:35:4f:c2:d1:12:61:
    ...(省略)
    79:bf:13:92:23:b5:1a:5b
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAoTo44Vgr5vUZvhlfhDGrUK3DBVKexWoG5Hq29oMhEc5HCSTk
XBL28/gGVoW6NtW7HMiM2zkPE0ETC/Hi8ef9CVjE414F5OpIgppBjYxjjmDEDita
...(省略)
m9ZFXwYPhq+ORWBJE9+hL1PsgvkiruEECIKTE2Pfeb8TkiO1Gls=
-----END RSA PRIVATE KEY-----

いろいろ出力されるが,NやEに対応するのは次の部分.
* N: modulus
* E: publicExponent
* D: privateExponent
* prime1: p
* prime2: q

また, -noout オプションを指定すると,秘密鍵をエンコーディングした内容は出力されなくなる( BEGIN RSA PRIVATE KEY から END RSA PRIVATE KEY の部分).

CSRの内容確認

CSRの内容確認には, req サブコマンドを使用する.
秘密鍵の内容確認と同じように, -in-text-noout が使える.

$ openssl req -in ca.csr -text -noout

TLS証明書の内容確認

TLS証明書の内容確認には, x509 サブコマンドを使用する.
これも同じように, -in-text-noout が使える.

$ openssl x509 -in ca.crt -text -noout

etcd入門 (1)

Kubernetesにも採用されている分散型KVSについて,何回かに分けて勉強していく.
今回のモチベーションとして,Kubernetesのアーキテクチャを詳しく勉強したい,Terraformのstate保存をローカルではなくetcdにしたいという2つがある.

まず,インストールとクラスタの作成について見ていく.

インストール

クラスタ作成のためには,まずetcd本体のバイナリが必要になる.
GitHubのreleaseページから,自分のOSにあったバイナリをダウンロードしてくる.

$ curl -LO https://github.com/etcd-io/etcd/releases/download/v3.3.10/etcd-v3.3.10-linux-amd64.tar.gz

ダウンロードしたファイルは圧縮されており,それを解凍するといくつかのファイルの中に2つのバイナリが確認できる.

$ tar xzf etcd-v3.3.10-linux-amd64.tar.gz
$ cd etcd-v3.3.10-linux-amd64.tar.gz
$ ll etcd*
-rwxr-xr-x. 1 yamazaki yamazaki 19237536 Oct 11  2018 etcd
-rwxr-xr-x. 1 yamazaki yamazaki 15817472 Oct 11  2018 etcdctl

etcd はetcd本体, etcdctl はetcdのクライアントとなるプログラム.
これら2つをPATHが通っている場所に置いてあげる.

$ sudo mv etcd* /usr/local/bin/
$ ll /usr/local/bin/etcd*
-rwxr-xr-x. 1 yamazaki yamazaki 19237536 Oct 11  2018 /usr/local/bin/etcd
-rwxr-xr-x. 1 yamazaki yamazaki 15817472 Oct 11  2018 /usr/local/bin/etcdctl

これでインストールは完了.
きちんとインストールされているか一応確認する.

$ etcd --version
etcd Version: 3.3.10
Git SHA: 27fc7e2
Go Version: go1.10.4
Go OS/Arch: linux/amd64

クラスタの作成

まず,1台のみでetcdクラスタを作成して簡単に使い方を把握し,その後複数メンバでのetcdクラスタを作成していく.

1台のetcdクラスタ作成

基本的にGitHubに書いてある手順通りに試していく.

1台のみでetcdクラスタを作成するときは,特に何も考えずに etcd コマンドを実行する.

$ etcd

etcd コマンドを実行すると,多くのログが出力される.
ここで,作成したetcdクラスタを使って,データの登録と取得を試してみる.

etcdクラスタとのやり取りには, etcdctl コマンドを使用する.
etcd コマンドを実行しているシェルとは別にもう1つシェルを起動し,そこで etcdctl コマンドを使用していく.
なお,etcd APIにはバージョンがいくつかあるが,今回はバージョン3を使用する.
etcdctl を普通に使うとv2 APIが使われてしまうので,v3 APIを使うために ETCDCTL_API という環境変数の値を 3 に設定する必要があることに注意する.
公式のREADMEのように etcdctl コマンドを実行するごとに毎回 ETCDCTL_API を指定してもよいが,毎回記述するのも面倒なのであらかじめexportしておく.

$ export ETCDCTL_API=3

etcdクラスタにデータを登録するには,putコマンドを使用する.

# Usage
$ etcdctl put <key> <value>

$ etcdctl put mykey "this is awesome"
OK

putコマンドを実行して, OK と表示されれば成功している.

登録した値を取得するにはgetコマンドを使用する.

# Usage
$ etcdctl get <key>

$ etcdctl get mykey
mykey
this is awesome

指定したキーの名前とデータが続けて出力されれば成功.

次に,複数台のetcdクラスタを作成してみる.
その前に,今使っていたetcdクラスタを停止させておく.
etcd コマンドを実行していたシェルに戻り,Ctrl-Cで終了する.

複数メンバでのetcdクラスタ作成

今回は1つのノードの中にetcdを複数立ち上げることで,複数メンバで構成されるetcdクラスタを作成する.
もちろん,複数のノードを使ってノード1台につきetcdを1つ動作させるという形で作成することもできる.

公式には,etcdはTCP 2379番・2380番のポートを使用する.

  • 2379/tcp: クライアントとの通信
  • 2380/tcp: etcdメンバ間の通信

今回は同じノード内に複数etcdを立ち上げるため,それぞれのetcdでポートが競合しないようにしておく.
今回は3つのetcdメンバを動作させ,それぞれの名前,ポートは次の表のようにした.

メンバ クライアント通信用 メンバ間通信用
etcd1 2379 2380
etcd2 12379 12380
etcd3 22379 22380

実際に3つのetcdメンバを動かしていく.
ターミナルのウィンドウを3つ開いて,それぞれのターミナルにつき1つのetcdメンバを起動する.
それぞれのターミナルで,次のようなコマンドを実行する.

1つ目のターミナル

$ etcd --name etcd1 \
    --initial-advertise-peer-urls http://localhost:2380 \
    --listen-peer-urls http://localhost:2380 \
    --advertise-client-urls http://localhost:2379 \
    --listen-client-urls http://localhost:2379 \
    --initial-cluster etcd1=http://localhost:2380,etcd2=http://localhost:12380,etcd3=http://localhost:22380 \
    --initial-cluster-state new \
    --initial-cluster-token etcd-cluster-1

2つ目のターミナル

$ etcd --name etcd2 \
    --initial-advertise-peer-urls http://localhost:12380 \
    --listen-peer-urls http://localhost:12380 \
    --advertise-client-urls http://localhost:12379 \
    --listen-client-urls http://localhost:12379 \
    --initial-cluster etcd1=http://localhost:2380,etcd2=http://localhost:12380,etcd3=http://localhost:22380 \
    --initial-cluster-state new \
    --initial-cluster-token etcd-cluster-1

3つ目のターミナル

$ etcd --name etcd3 \
    --initial-advertise-peer-urls http://localhost:22380 \
    --listen-peer-urls http://localhost:22380 \
    --advertise-client-urls http://localhost:22379 \
    --listen-client-urls http://localhost:22379 \
    --initial-cluster etcd=http://localhost:2380,etcd2=http://localhost:12380,etcd3=http://localhost:22380 \
    --initial-cluster-state new \
    --initial-cluster-token etcd-cluster-1

正しくコマンドを入力できていれば,正常にetcdクラスタが起動しているはず.
出力されるログでエラー等が出ていなければひとまずOK.

コマンドラインオプションをたくさん使っているので複雑に見えるが,一度理解してしまうとそこまで難しくはないと思う.
今回指定しているコマンドラインオプションは,大きく分けて次の2つに分かれている.

  • 新しいクラスタを作成するときに使用するもの(Clustering flags
  • 他のメンバやクライアントとの通信や,etcdの設定に関するもの(Member flags

Clustering flagsでは,新しいクラスタを一から作成するときに必要となる情報を指定する.
今回使用したものは次の通り.

flags description
--initial-advertise-peer-urls クラスタ内の他のetcdメンバからの通信を受け付けるURLを指定する
--advertise-client-urls クライアントからの通信を受け付けるURLを指定する
--initial-cluster クラスタを構成するetcdメンバの情報をカンマ区切りで指定する
--initial-cluster-state 新しいクラスタを作成する場合は new を指定する
--initial-cluster-token クラスタ作成中に用いられるトークン.複数クラスタを管理している際,意図せず別のクラスタに影響を与えるのを防ぐ

Member flagsでは,作成するメンバに関する情報等の設定ができる.
今回使用したものは次の通り.

flags description
--name メンバ名
--listen-peer-urls クラスタ内の他のetcdメンバからの通信を受け付けるURLを指定する
--listen-client-urls クライアントからの通信を受け付けるURLを指定する

注意点として, --initial-cluster で指定しているメンバ情報は, --name--listen-peer-urls で指定した名前とURLに一致させなければならない.
もし一致していないと,クラスタ起動時にエラーとなる.

次のコマンドで,きちんと3つのメンバが表示されたら成功.

$ etcdctl member list
29727fd1bdf9fb62, started, etcd1, http://localhost:2380, http://localhost:2379
44dd3cd8faa339d0, started, etcd3, http://localhost:22380, http://localhost:22379
b59b01c27098773e, started, etcd2, http://localhost:12380, http://localhost:12379

次は,作成したクラスタを使ってもう少しetcdctlの使い方を勉強し,その後TLSの設定やDiscoveryを使ったクラスタ作成,無停止でのメンバアップグレード等について勉強していく予定.