[アップデート] EC2 Instance Connectが「属性ベースのアクセスコントロール (ABAC)」をサポートしました

「ABAC」って、聞いたことはあるけど良く理解していなかったので、この機会に調べてみました。
2020.05.20

みなさん、こんにちは!
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を使ったアクセス制御を以下のように変更します。

  1. プロジェクト毎にIAMロールを用意して、アクセス権限の管理をロール単位に集約する
  2. プリンシパル (=アクセスの主体、つまり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-aProject: 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-policyinstance-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を使うことにより、管理コストを下げることができ、かつ、きめ細やかなアクセス制御によりセキュリティ面も向上できるのではないかと思います。

インスタンスやユーザーの数が多いほど恩恵を受けることができると思いますので、大規模・煩雑なインスタンス接続の管理に悩まされているという方がいらっしゃいましたら、是非とも試してみて頂ければ幸いです。

脚注

  1. ※ 今回は検証ということもあり、マネジメントコンソールからのEC2 Instans Connect接続を受け付けるために不特定多数のIPからの接続を許可しています。実際の運用ではAWSサービスのIPアドレス範囲を確認した上で設定することを推奨します。