この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
みなさん、こんにちは! AWS事業本部の青柳@福岡オフィスです。
EC2インスタンスにリモートログインする手段の一つである EC2 Instance Connect において、アップデートが発表されました。
EC2 Instance Connect now supports Attribute Based Access Control (ABAC)
前提知識
「EC2 Instance Connect」とは
EC2 Instance Connectとは、EC2インスタンスに対するSSH接続をIAMを用いて管理することができる機能です。
接続の度に一時的な公開鍵/秘密鍵の組を生成して使用するため、鍵の管理が不要であることが大きなメリットとなっています。
EC2 Instance Connectの詳しい説明や利用方法については、下記ブログ記事を参照してください。
また、EC2 Instance ConnectやSSMセッションマネージャーなど、EC2インスタンスにリモートログインする複数の方法について比較・説明したブログ記事もありますので、是非参考にしてください。
「属性ベースのアクセスコントロール (ABAC)」とは
「属性ベースのアクセスコントロール (ABAC)」と対比されるものとして「役割ベースのアクセスコントロール (RBAC)」があります。
RBACは、アクセス権限を記述したポリシーをユーザーに割り当てることによって、アクセスを制御する方式です。 ポリシーは一般的に「〇〇サービスの利用者」や「△△プロジェクトの管理者」といった括りで権限を記述することから「役割ベース」と呼ばれます。
一方、ABACは、アクセス元やアクセス対象が持つ「属性」をキーにしてアクセスを制御する方式です。 「属性」には「名前」「所属」「日付」など様々なものがあるため、RBACに比べてきめ細やかなアクセス制御が行えることが特徴です。
AWSにおいては、ABACの「属性」としてリソースの「タグ」を用いることができます。
AWSにおけるABACの詳細については、AWS公式ドキュメントも併せて参照してください。 AWS の ABAC とは - AWS Identity and Access Management
今回のアップデートで何が変わったのか?
ユーザーに対してEC2 Instance Connectによるインスタンス接続を許可するためのIAMポリシーの記述は、これまでは以下のような内容でした。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2-instance-connect:SendSSHPublicKey",
"Resource": [
"arn:aws:ec2:ap-northeast-1:123456789012:instance/i-XXXXXXXXXXXXXXXXX",
"arn:aws:ec2:ap-northeast-1:123456789012:instance/i-YYYYYYYYYYYYYYYYY"
]
},
{
"Effect": "Allow",
"Action": "ec2:DescribeInstances",
"Resource": "*"
}
]
}
Resource
の記述によって、接続許可の対象インスタンスを明示して指定しています。
このIAMポリシーをユーザーへアタッチすることで、指定されたインスタンスのみに対して接続を許可するというアクセス制御を行う訳です。
今回のアップデートによって、以下のような記述をすることが可能になりました。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2-instance-connect:SendSSHPublicKey",
"Resource": "arn:aws:ec2:ap-northeast-1:123456789012:instance/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/tag-key": "tag-value"
}
}
},
{
"Effect": "Allow",
"Action": "ec2:DescribeInstances",
"Resource": "*"
}
]
}
Resource
の記述はinstance/*
となっており、対象を特定していません。
代わりに、Condition
に対象インスタンスを絞り込む条件が記述されています。
具体的には「リソース (この場合はEC2インスタンス) の指定したタグ (tag-key
) が指定した値 (tag-value
) と一致している場合」を条件として設定しています。
従来のIAMポリシー記述方法ですと、インスタンスが追加されたり削除されたりする毎にIAMポリシーのResource
の記述内容をメンテナンスする必要がありました。
新しい記述方法ですと、IAMポリシーを変更する必要が無く、対象のインスタンスにタグを付与するだけで対応することができます。 これによりアクセス権限の管理を部門やプロジェクトで行えるようになるなどのメリットがあります。
(しかしメリットはそれだけではありません・・・ 後ほど詳しく解説します)
試してみた
ABACで記述したポリシーによってユーザーのアクセス制御を行う
それでは実際に試してみましょう。
リソースの準備
まず、以下のリソースを準備します。
- EC2インスタンス × 2
- 各インスタンスの名前を以下のようにします
server-a1
server-b1
- Amazon Linux 2のバージョン2.0.20190618以降 (=EC2 Instance Connectが構成済み)
- インターネット経由でSSHアクセスが行えるようにしておきます
- パブリックサブネットに配置
- インバウンドルール「0.0.0.0/0からのTCP/22を許可」を設定したセキュリティグループを割り当て *1
- 各インスタンスに以下のタグを付与しておきます
- インスタンス
server-a1
→ タグキー:Project
、タグ値:project-a
- インスタンス
server-b1
→ タグキー:Project
、タグ値:project-b
- IAMユーザー × 2
- 各ユーザーの名前を以下のようにします
user01
user02
- 「プログラムによるアクセス」「マネジメントコンソールへのアクセス」の両方を有効にします
- アクセス権限 (IAMポリシー) は現時点では設定しません
IAMポリシーの作成とユーザーへの割り当て
EC2 Instance Connectのアクセス権限を定義するIAMポリシーを作成します。 (アカウントID部分はご自身の環境に合わせて修正してください)
Project: project-a
のタグが付与されたインスタンスへの接続を許可するポリシー:
instance-connect-project-a-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2-instance-connect:SendSSHPublicKey",
"Resource": "arn:aws:ec2:ap-northeast-1:123456789012:instance/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Project": "project-a"
}
}
},
{
"Effect": "Allow",
"Action": "ec2:DescribeInstances",
"Resource": "*"
}
]
}
$ aws iam create-policy \
--policy-name instance-connect-project-a-policy \
--policy-document file://instance-connect-project-a-policy.json
Project: project-b
のタグが付与されたインスタンスへの接続を許可するポリシー:
instance-connect-project-b-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2-instance-connect:SendSSHPublicKey",
"Resource": "arn:aws:ec2:ap-northeast-1:123456789012:instance/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Project": "project-b"
}
}
},
{
"Effect": "Allow",
"Action": "ec2:DescribeInstances",
"Resource": "*"
}
]
}
$ aws iam create-policy \
--policy-name instance-connect-project-b-policy \
--policy-document file://instance-connect-project-b-policy.json
ユーザーuser01
に対して前者のポリシー、ユーザーuser02
に対して後者のポリシーをそれぞれ割り当てます。
$ aws iam attach-user-policy \
--user-name user01 \
--policy-arn arn:aws:iam::123456789012:policy/instance-connect-project-a-policy
$ aws iam attach-user-policy \
--user-name user02 \
--policy-arn arn:aws:iam::123456789012:policy/instance-connect-project-b-policy
これで設定は全て終わりました。
EC2 Instance Connectによるインスタンスへの接続
各ユーザーでEC2 Instance Connectを使ってインスタンスへの接続を試みます。
マネジメントコンソールを使う場合
マネジメントコンソールへIAMユーザーuser01
でサインインを行い、EC2インスタンスの一覧画面を表示します。
インスタンスserver-a1
を右クリックして「接続」を選び、「接続方法: EC2 Instance Connect (ブラウザベースのSSH接続)」を選択して「接続」をクリックします。
インスタンスに対してSSH接続できました。
一方、アクセス権限の無いインスタンスserver-b1
への接続を試みた場合は、接続ができません。(黒い画面のまま応答が無い)
同様にして、IAMユーザーuser02
でサインインを行い、各インスタンスへの接続を試みると、以下の結果になります。
- インスタンス
server-a1
: 接続できない - インスタンス
server-b1
: 接続できる
EC2 Instance Connect CLIを使う場合
以下のAWSドキュメントページの手順を参照して、クライアントに「EC2 Instance Connect CLI」をインストールします。 EC2 Instance Connect のセットアップ / ステップ 3: (オプション) EC2 Instance Connect CLI をインストールする
AWS CLIのコマンドaws configure
を実行して、ユーザーuser01
のアクセスキーIDとシークレットアクセスキーを設定したプロファイルを作成します。
$ aws configure --profile user01
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]: (省略)
EC2 Instance Connect CLIのmssh
コマンドを使用して、作成したuser01
のプロファイルとインスタンスserver-a1
のインスタンスIDを指定して、接続を試みます。
$ mssh --profile user01 i-019e3d6446403f44a
インスタンスに対してSSH接続できました。
$ mssh --profile user01 i-019e3d6446403f44a
The authenticity of host 'XX.XX.XX.XX (XX.XX.XX.XX)' can't be established.
ECDSA key fingerprint is SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'XX.XX.XX.XX' (ECDSA) to the list of known hosts.
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-2/
1 package(s) needed for security, out of 10 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-192-168-1-62 ~]$
一方、アクセス権限の無いインスタンスserver-b1
への接続を試みた場合は、接続が拒否されました。
$ mssh --profile user01 i-0f34acc253faad893
Error while pushing the public key:
An error occurred (AuthException) when calling the SendSSHPublicKey operation: Access Denied.
同様にして、IAMユーザーuser02
からインスタンスserver-b1
へ接続できること、インスタンスserver-a1
へ接続できないことが確認できます。
IAMロールとスイッチロールを使うことにより、より管理をし易くする
インスタンスに付与した「タグ」を使ってEC2 Instans Connectのアクセス制御が行えるようになった訳ですが、次のような問題点も見えてきます。
- プロジェクトの数だけIAMポリシーを用意しなければならない
- 個々のIAMユーザに対して、アクセス権限に応じたIAMポリシーを割り当てなければならない
- ユーザーが100人、1000人いたらどうしましょう? 管理が大変ですよね・・・
- ユーザーが複数のプロジェクトに所属していた場合に対応できない
- プロジェクトAのサーバーへ接続する時、プロジェクトBのサーバーへ接続する時、、、その都度IAMポリシーを付け替えれば対応可能ですが、現実的ではないですよね・・・
そこで、ABACを使ったアクセス制御を以下のように変更します。
- プロジェクト毎にIAMロールを用意して、アクセス権限の管理をロール単位に集約する
- プリンシパル (=アクセスの主体、つまりIAMユーザー/IAMロール) 側にも「タグ」を付与する
具体的な設定方法を説明していきます。
IAMロールの作成
まずは、IAMロールを作成します。
ここで用いるIAMロールは、IAMユーザーがロールの切り替え (スイッチロール) を行うためのものですので、以下のポリシードキュメントを用意します。
プロジェクトAへのアクセスに用いるIAMロール:
(スイッチロールを許可するユーザーとしてuser01
のみを指定)
project-a-role-trust-policy.json
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:user/user01"
},
"Action": "sts:AssumeRole"
}
}
プロジェクトBへのアクセスに用いるIAMロール:
(スイッチロールを許可するユーザーとしてuser01
およびusre02
を指定)
project-b-role-trust-policy.json
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::123456789012:user/user01",
"arn:aws:iam::123456789012:user/user02"
]
},
"Action": "sts:AssumeRole"
}
}
各プロジェクト毎のIAMロールを作成します。
その際に「タグ」の設定も行います。
(タグの内容はインスタンスに付与したものと同じProject: project-a
、Project: project-b
を設定します)
$ aws iam create-role \
--role-name project-a-role \
--assume-role-policy-document file://project-a-role-trust-policy.json \
--tags Key=Project,Value=project-a
$ aws iam create-role \
--role-name project-b-role \
--assume-role-policy-document file://project-b-role-trust-policy.json \
--tags Key=Project,Value=project-b
IAMユーザーの設定 (前節での設定をリセット)
前節で各ユーザーに割り当てていたIAMポリシーinstance-connect-project-a-policy
、instance-connect-project-b-policy
を解除 (デタッチ) します。
IAMポリシーの作成とロールへの割り当て
EC2 Instance Connectのアクセス権限を定義するIAMポリシーを作成します。
ここでのポイントは、前節のように各プロジェクト毎にIAMポリシーを作成するのではなく、全てのプロジェクトで共通のIAMポリシーを1つだけ作成すればよいということです。
instance-connect-abac-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2-instance-connect:SendSSHPublicKey",
"Resource": "arn:aws:ec2:ap-northeast-1:123456789012:instance/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Project": "${aws:PrincipalTag/Project}"
}
}
},
{
"Effect": "Allow",
"Action": "ec2:DescribeInstances",
"Resource": "*"
}
]
}
Condition
の記述が"aws:ResourceTag/Project": "${aws:PrincipalTag/Project}"
となっています。
これは「リソース (EC2インスタンス) のタグProject
に設定されているタグ値が、プリンシパル (アクセスの主体すなわちIAMロール) のタグProject
に設定されているタグ値と一致する場合」という条件を示しています。
つまり、以下のようなアクセス制御が行われることになります。
- IAMロール
project-a-role
にはProject: project-a
というタグが付与されている → よって、同じタグProject: project-a
が付与されているインスタンスserver-a1
へ接続できる -
IAMロール
project-b-role
にはProject: project-b
というタグが付与されている → よって、同じタグProject: project-b
が付与されているインスタンスserver-b1
へ接続できる
上記の定義内容でIAMポリシーを作成します。
$ aws iam create-policy \
--policy-name instance-connect-abac-policy \
--policy-document file://instance-connect-abac-policy.json
各IAMロールへポリシーを割り当てます。
$ aws iam attach-role-policy \
--role-name project-a-role \
--policy-arn arn:aws:iam::123456789012:policy/instance-connect-abac-policy
$ aws iam attach-role-policy \
--role-name project-b-role \
--policy-arn arn:aws:iam::123456789012:policy/instance-connect-abac-policy
これで全ての設定が終わりました。
EC2 Instance Connectによるインスタンスへの接続
前節と同様にして、マネジメントコンソールまたはEC2 Instance Connect CLIを使って、各ユーザーから各インスタンスへの接続を試みます。
マネジメントコンソールを使う場合
マネジメントコンソールへIAMユーザーuser01
でサインインを行い、「スイッチロール」を選択します。
アカウントID、IAMロール名project-a
を入力して、「ロールの切り替え」をクリックします。
EC2インスタンス一覧画面を開き、対象インスタンスへ「EC2 Instance Connect」による接続を試みます。
インスタンスserver-a1
に対しては接続できて、server-b1
に対しては接続できないという結果になったかと思います。
同様にして、以下のパターンでも各インスタンスへの接続を試みて、結果を確認します。
- ユーザー
user01
でサインインを行い、IAMロールproject-b
へスイッチロールする - ユーザー
user02
でサインインを行い、IAMロールproject-b
へスイッチロールする
(ユーザーuser02
からIAMロールproject-a
へのスイッチロールは、権限が無いため行うことができません)
EC2 Instance Connect CLIを使う場合
ファイル~/.aws/config
を開き、スイッチロールを行うためのプロファイル設定を追記します。
[profile user01_project-a]
region = ap-northeast-1
role_arn = arn:aws:iam::123456789012:role/project-a-role
source_profile = user01
[profile user01_project-b]
region = ap-northeast-1
role_arn = arn:aws:iam::123456789012:role/project-b-role
source_profile = user01
[profile user02_project-b]
region = ap-northeast-1
role_arn = arn:aws:iam::123456789012:role/project-b-role
source_profile = user02
以下のようにmssh
コマンドを実行して、ユーザー・スイッチロール・接続先インスタンスの組み合わせを変えた場合の結果を確認します。
ユーザーuser01
からIAMロールproject-a-role
へスイッチロール:
$ mssh --profile user01_project-a i-019e3d6446403f44a
$ mssh --profile user01_project-a i-0f34acc253faad893
ユーザーuser01
からIAMロールproject-b-role
へスイッチロール:
$ mssh --profile user01_project-b i-019e3d6446403f44a
$ mssh --profile user01_project-b i-0f34acc253faad893
ユーザーuser02
からIAMロールproject-b-role
へスイッチロール:
$ mssh --profile user02_project-b i-019e3d6446403f44a
$ mssh --profile user02_project-b i-0f34acc253faad893
ユーザー、スイッチロールの組み合わせによるインスタンス接続可否の結果
マネジメントコンソール、EC2 Instance Connect CLIのいずれを使用した場合でも、全パターンにおける確認結果は以下の通りになると思います。
- ユーザー
user01
- ロール
project-a-role
へスイッチロールした場合 - インスタンス
server-a1
: 接続できる - インスタンス
server-b1
: 接続できない - ロール
project-b-role
へスイッチロールした場合 - インスタンス
server-a1
: 接続できない - インスタンス
server-b1
: 接続できる -
ユーザー
user02
- ロール
project-a-role
へスイッチロールすることはできない - ロール
project-b-role
へスイッチロールした場合 - インスタンス
server-a1
: 接続できない - インスタンス
server-b1
: 接続できる
図でイメージすると、こんな感じです。
ABACによるIAMポリシーの定義通りに、アクセス制御が行われることが確認できました。
おわりに
ABACを使うことにより、管理コストを下げることができ、かつ、きめ細やかなアクセス制御によりセキュリティ面も向上できるのではないかと思います。
インスタンスやユーザーの数が多いほど恩恵を受けることができると思いますので、大規模・煩雑なインスタンス接続の管理に悩まされているという方がいらっしゃいましたら、是非とも試してみて頂ければ幸いです。
脚注
- ※ 今回は検証ということもあり、マネジメントコンソールからのEC2 Instans Connect接続を受け付けるために不特定多数のIPからの接続を許可しています。実際の運用ではAWSサービスのIPアドレス範囲を確認した上で設定することを推奨します。 ↩