AWS Secrets ManagerでEC2キーペア秘密鍵の安全な受け渡しを管理してみる

EC2キーペアのプライベートキー(秘密鍵)の安全な受け渡しを管理するために、AWS Secrets Managerを使う方法を検討してみました。
2020.02.26

みなさん、こんにちは! AWS事業本部の青柳@福岡オフィスです。

チームやプロジェクトで作業をする際、EC2の「キーペア」のプライベートキー (秘密鍵) をどのように管理していますか?

まず、最初にキーペアを作成した人が、秘密鍵を入手してローカルディスクに保存します。 そこからが問題です。 チーム/プロジェクトの複数のメンバーも秘密鍵を使う必要がある場合、秘密鍵ファイルをどうやって共有 (あるいは受け渡し) するのかが悩みどころです。

  • 社内ファイルサーバーの共有フォルダーに置く (フォルダーのアクセス権をしっかり管理しなければ・・・)
  • メールでやり取りする (社内と言えども平文は心配・・・添付ファイルの暗号化/復号は手間がかかる・・・)

社内だけでなく社外の取引先や協力会社とプロジェクトを進めている場合などは、なおさらセキュリティに気を払う必要が出てきます。

そこで、AWSで秘匿情報を取り扱うためのサービスとして用意されている AWS Secrets Manager を使って、EC2キーペア秘密鍵の安全な受け渡しを管理する方法を検討してみました。

Secrets Managerマネジメントコンソールを使って試してみる

PEM形式の秘密鍵ファイルは、Base64でエンコードされたテキストファイルです。

example-keypair.pem

-----BEGIN RSA PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKL
MNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWX
YZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij
klmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuv
wxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz01234567
89+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGH
IJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRST
UVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef
ghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqe
stuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123
456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCD
EFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOP
QRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZab
cdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn
opqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz
0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKL
MNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWX
YZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij
klmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuv
wxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz01==
-----END RSA PRIVATE KEY-----

(これはサンプルのダミーファイルです)

Secrets Managerで新しいシークレットを作成する際に、シークレットの内容として秘密鍵ファイルの内容をまるごとコピーペーストしてみます。

作成したシークレットを開き、値を取得してみます。

一見うまく行っているようですが・・・

改行コードがスペースに置き換えられていて、ここからコピーペーストして新しいPEMファイルを作ろうとしても上手く行きませんでした。

そもそも、画面上に秘密鍵の内容を表示して作業するというのは、精神衛生上よくありません・・・

秘密鍵をバイナリデータとしてSecrets Managerに登録する

何か良い手はないものか・・・ と考えたところ、Secrets Managerは、テキストデータだけでなくバイナリデータもシークレットとして登録できることを思い出しました。

Secrets Managerへバイナリデータを登録する方法については、弊社コンサルティング部の八幡がブログを執筆していましたので、参考にしました。

AWS Secrets Managerでバイナリデータを管理する | Developers.IO

秘密鍵をSecrets Managerに登録する

バイナリデータの登録・参照・取り出しは、AWSマネジメントコンソールではサポートされておらず、AWS CLIやSDKを使う必要があります。

今回はAWS CLIを使いました。

aws secretsmanager create-secret コマンドの引数に直接ファイルパスを指定できます。

$ KEYPAIR_NAME=example-keypair
$ aws secretsmanager create-secret \
    --name ${KEYPAIR_NAME} \
    --secret-binary file://~/.ssh/${KEYPAIR_NAME}.pem
{
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:example-keypair-XXXXXX",
    "Name": "example-keypair",
    "VersionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
}

どのようにシークレットに格納されたのか確認してみます。

$ aws secretsmanager get-secret-value \
    --secret-id ${KEYPAIR_NAME}
{
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:example-keypair-XXXXXX",
    "Name": "example-keypair",
    "VersionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "SecretBinary": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxZXN0dXZ3eHl6MDEyMzQ1Njc4OSsvQUJDREVGR0hJSktMCk1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFlc3R1dnd4eXowMTIzNDU2Nzg5Ky9BQkNERUZHSElKS0xNTk9QUVJTVFVWV1gKWVphYmNkZWZnaGlqa2xtbm9wcWVzdHV2d3h5ejAxMjM0NTY3ODkrL0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpagprbG1ub3BxZXN0dXZ3eHl6MDEyMzQ1Njc4OSsvQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcWVzdHV2Cnd4eXowMTIzNDU2Nzg5Ky9BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxZXN0dXZ3eHl6MDEyMzQ1NjcKODkrL0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFlc3R1dnd4eXowMTIzNDU2Nzg5Ky9BQkNERUZHSApJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcWVzdHV2d3h5ejAxMjM0NTY3ODkrL0FCQ0RFRkdISUpLTE1OT1BRUlNUClVWV1hZWmFiY2RlZmdoaWprbG1ub3BxZXN0dXZ3eHl6MDEyMzQ1Njc4OSsvQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWYKZ2hpamtsbW5vcHFlc3R1dnd4eXowMTIzNDU2Nzg5Ky9BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxZQpzdHV2d3h5ejAxMjM0NTY3ODkrL0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFlc3R1dnd4eXowMTIzCjQ1Njc4OSsvQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcWVzdHV2d3h5ejAxMjM0NTY3ODkrL0FCQ0QKRUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxZXN0dXZ3eHl6MDEyMzQ1Njc4OSsvQUJDREVGR0hJSktMTU5PUApRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFlc3R1dnd4eXowMTIzNDU2Nzg5Ky9BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiCmNkZWZnaGlqa2xtbm9wcWVzdHV2d3h5ejAxMjM0NTY3ODkrL0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW4Kb3BxZXN0dXZ3eHl6MDEyMzQ1Njc4OSsvQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcWVzdHV2d3h5egowMTIzNDU2Nzg5Ky9BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxZXN0dXZ3eHl6MDEyMzQ1Njc4OSsvCkFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFlc3R1dnd4eXowMTIzNDU2Nzg5Ky9BQkNERUZHSElKS0wKTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcWVzdHV2d3h5ejAxMjM0NTY3ODkrL0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWApZWmFiY2RlZmdoaWprbG1ub3BxZXN0dXZ3eHl6MDEyMzQ1Njc4OSsvQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqCmtsbW5vcHFlc3R1dnd4eXowMTIzNDU2Nzg5Ky9BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxZXN0dXYKd3h5ejAxMjM0NTY3ODkrL0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFlc3R1dnd4eXowMT09Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": 1582595237.313
}

SecretBinary に、秘密鍵ファイルの内容がBase64でエンコードされた文字列が格納されています。

(元々Base64でエンコードされているテキストファイルを更にBase64でエンコードするというのも変な感じですが・・・ 改行を含むテキストをSecrets Managerに格納するにはこの方法が確実だと思います)

Secrets Managerから秘密鍵を取り出してファイルに保存する

ここからは、Secrets Managerに登録された秘密鍵を、別のユーザーが取り出すというシチュエーションで進めます。

さきほど説明した通り、Secrets Managerに格納されたバイナリデータはBase64でエンコードされています。

したがって、ファイルとして取り出す場合には base64 コマンドでデコードすれば良いということになります。

$ KEYPAIR_NAME=example-keypair
$ aws secretsmanager get-secret-value \
    --secret-id ${KEYPAIR_NAME} \
    --query 'SecretBinary' \
    --output text \
    | base64 -d > ${KEYPAIR_NAME}.pem

ファイルの中身を確認してみましょう。

$ cat example-keypair.pem
-----BEGIN RSA PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKL
MNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWX
YZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij
klmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuv
wxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz01234567
89+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGH
IJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRST
UVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef
ghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqe
stuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123
456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCD
EFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOP
QRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZab
cdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn
opqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz
0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKL
MNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWX
YZabcdefghijklmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij
klmnopqestuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuv
wxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqestuvwxyz01==
-----END RSA PRIVATE KEY-----

登録した秘密鍵ファイルの内容と同じですね。

これで、Secrets Managerを介して別のユーザーへ秘密鍵を受け渡すことができました。

今回紹介したAWS CLIのコマンドラインをスニペット化やbashスクリプト化することで、Secrets Managerの操作に慣れない開発者やオペレーター等でも秘密鍵の受け渡しの操作が行えるようになるかと思います。

Secrets Managerのアクセス制御

Secrets Managerに登録した秘密鍵ですが、誰でもアクセスできる状態では当然問題があるため、適切にアクセス制御を行う必要があります。

Secrets Managerにおけるアクセス制御の方法には、次の2通りがあります。

  1. アイデンティティベースのアクセス制御
  2. リソースベースのアクセス制御

アイデンティティベースのアクセス制御

IAMユーザー、IAMグループ、IAMロール等に対してIAMポリシーを適用することによりアクセス権限を付与する方法です。

IAMポリシーは以下のように定義されます。

identity-based-policy.json

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action": [
            "secretsmanager:Describe*",
            "secretsmanager:Get*",
            "secretsmanager:List*" 
        ],
        "Resource": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:project-a/*"
    }
}

Action には、Secrets Managerのシークレットに対する「参照」「値の取り出し」の権限を指定します。

Resource には、アクセス権限を与える対象シークレットをARNで指定します。

Secrets Managerのシークレットの名前は「xxxxxx/xxxxxx」のように「/」で区切ることで疑似的な階層構造を取ることができます。 この場合、「project-a/*」というようにワイルドカード指定をすることで、「project-a/」で始まるシークレットに対して一律でアクセス権限を設定することもできます。

ポリシーJSONファイルからIAMポリシーを作成します。

$ aws iam create-policy \
    --policy-name secrets-manager-access-policy \
    --policy-document file://identity-based-policy.json

作成したIAMポリシーを、IAMグループ「project-a-group」にアタッチします。

$ aws iam attach-group-policy \
    --group-name project-a-group \
    --policy-arn arn:aws:iam::123456789012:policy/secrets-manager-access-policy

これにより、IAMグループ「project-a-group」に対して、「project-a/」の階層にある全てのシークレットへのアクセス許可を与えることができます。

リソースベースのアクセス制御

リソース、つまり「シークレット」に対してアクセス権限を記述したポリシーを適用することで、アクセス制御を行う方法です。

ポリシーは以下のように記述します。

resource-based-policy.json

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action": [
            "secretsmanager:Describe*",
            "secretsmanager:Get*",
            "secretsmanager:List*" 
        ],
        "Principal": {"AWS": "arn:aws:iam::123456789012:user/testuser1"},
        "Resource": "*"
    }
}

Principal で、アクセス権限を与える対象のプリンシパル (IAMユーザー等) を指定します。

注意点として、リソースベースのポリシーではプリンシパルにIAMグループを指定することはできません。

リソースに対して設定するポリシーですので Resource の指定は無意味となりますが、Resource エントリーを省略することはできません。 固定で「*」を指定する必要があります。

作成したポリシーをシークレットに適用するには、aws secretsmanager put-resource-policy コマンドを使用します。

$ aws secretsmanager put-resource-policy \
    --secret-id project-a/keypair-1 \
    --resource-policy file://resource-based-policy.json

これにより、IAMユーザー「testuser1」に対して、シークレット「project-a/keypair-1」へのアクセス許可を与えることができます。

リソースベースのアクセス制御は、シークレットの管理担当者がIAMに対する管理権限を持っていない場合に、Secrets Manager側でアクセス制御を行うことができるという利点があります。

その代わり、アイデンティティベースのアクセス制御と比べると「IAMグループに対してアクセス制御を行うことができない」「シークレット一つ一つに対してアクセス制御を行う必要がある」という制約があります。

おわりに

Secrets Managerを使用してキーペアの秘密鍵を安全に管理する方法について紹介しました。

より安全性を高めるために、いくつかの方法が考えられます。 例えば、今回はシークレットの暗号化に「デフォルトのサービス暗号化キー」を使いましたが、KMSのカスタマー管理キーを使うことで、より安全なシークレット管理の運用ができるかもしれません。

また、今回はAWS CLIを使って一連の操作を行いましたが、秘密鍵の利用者がWindowsユーザーの場合はどうすればよいのか? といったことも考える余地があると思います。