EC2 Instance Connect が ABAC (Attribute Based Access Control) に対応しました! [Alternative Ver.]

EIC の ABAC について ASAP で調べたのですが一歩出遅れました。

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

コンバンハ、千葉(幸)です。

ネタが被りましてね。

EC2 Instance Connect now supports Attribute Based Access Control (ABAC)

EICABAC に対応した!という発表を受けていそいそとブログの下書きを書いていたのですが、颯爽とあおやぎがブログ化していました。(検証がとても丁寧......)

ほぼ書きあがっていたのでお蔵入りにするのも忍びないと思い、公開します。大筋はどちらも一緒なのですが、細かい部分で違いがあります。同一のソースからアウトプットするときにどこを切り取るかの観点の違いを比較したり、オリジナル・バージョンを楽しんだ後にちょっと別のバージョンも触れたい、という場合にご参照ください(?)

目次

EIC (EC2 Instance Connect) とは

EC2 Instance Connect とは、EC2インスタンスに対するSSH接続の手法の一つとして提供されている機能です。SSHキーの管理が不要になり、IAMを用いてアクセスコントロールが可能になります。また、接続リクエストがCloud Trailに記録されるというメリットもあります。

機能の詳細は以下のエントリに分かりやすくまとまっています。

同じくEC2インスタンスに接続するためのサービスとして、Systems Managerのセッションマネージャーがあります。

セッションマネージャーはEC2インスタンス以外にもオンプレミスのインスタンスにも使用できますが、EC2インスタンスに接続することを想定してEICと比較すると、ざっくり以下のような違いがあります。

EIC セッションマネージャー
SecuriryGroupインバウンド SSH用の開放が必要 SSH用の開放は不要
接続先インスタンスのパブリックIP 必須(ブラウザアクセスの場合) NAT GatewayやVPCエンドポイントを使用すれば不要
いわゆる「ログイン」の有無(※) 有り 無し
セッションログのS3出力 不可 設定によって可能
対応OS Amazon Linux2、Ubuntu 16.04 以降 多くのLinux、Windows
接続先インスタンスのIAMロール EIC用には不要 セッションマネージャー用に必要
接続先OSにインストールが必要なもの ec2-instance-connect パッケージ AWS Systems Manager エージェント

(※)いわゆる「ログイン」が何を指しているかについては、以下記事を参照してください。

また、EC2 Instance Connect の詳細については、以下ドキュメントを参照してください。

EC2 Instance Connect を使用して Linux インスタンスに接続する

ABAC (Attribute Based Access Control) とは

ABAC とは、簡単に言うと「タグベースでの権限管理ができること」を指します。特に、アクションの実行元であるプリンシパルと実行先であるリソースに同一のタグを付与することによってコントロールすることを指します。タグベースはともかくABACという言葉は聞いたことがなかったので調べると、AWSにおいては2019年11月に登場した考え方であるようです。

新しい ID フェデレーション – AWS でアクセスコントロールに従業員属性を使用する

イメージは、以下の通りです。

AWS の ABAC とは より

左側にスイッチ先のロール、右側に操作対象のリソース(S3バケット、EC2インスタンス)が描かれています。それぞれプロジェクトごとに、Heart(ハート)、Sun(太陽)、Lightning(稲妻)のタグが付与されています。

ここで、「自身が所属するプロジェクトのタグがついているリソースのみ操作可能」というアクセスコントロールをする場合、IAMポリシーを一つ定義するだけで実現できます。ロールが増えてもリソースが増えても、タグ付けさえきちんと行えば、IAMポリシーを変更する必要がありません。

それに対して従来の考え方はRBAC(Role Based Access Controll)と呼ばれ、こちらは「リソースベース」での権限管理を行います。

(画像引用元同上)

プロジェクトごとにロールを作成し、それぞれ個別のIAMポリシーを割り当てます。各ポリシーにおいては、リソースレベルで操作可能な対象を指定していきます。例えば「Heartプロジェクト用のロールではインスタンスAとS3バケットXを操作できる」といったものです。インスタンスやS3バケットが追加された場合、ポリシー内で定義されているリソースも併せて修正する必要があります。

ABAC か RBAC かで言えば、ABACの方が柔軟な管理に対応しているため、対応しているAWSサービス・機能については積極的にABACを採用する方が好ましいでしょう。

EIC で ABAC がサポートされたとは

ここまで学んだことを組み合わせると、ようやく意味が見えてきました。簡単に言えば、以下の制約が取り払われた、ということになります。

 Instance Connect のタグベースの認証は現在サポートされていません。

EC2 Instance Connect のセットアップ

2020/5/19現在の日本語版のドキュメントでは記述が確認できます。(英語版では既に更新されています。)

従来は、IAMユーザー(ないしロール)がどのインスタンスに対してSSHできるかどうかを制御したい場合、そのエンティティに割り当てるIAMポリシーを以下のように定義する必要がありました。

{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": "ec2-instance-connect:SendSSHPublicKey",
        "Resource": [
            "arn:aws:ec2:region:account-id:instance/i-1234567890abcdef0",
            "arn:aws:ec2:region:account-id:instance/i-0598c7d356eba48d7"
        ],
        "Condition": {
            "StringEquals": {
                "ec2:osuser": "ami-username"
            }
        }
      },
      {
        "Effect": "Allow",
        "Action": "ec2:DescribeInstances",
        "Resource": "*"
      }
    ]
}

Resourceのセクションにおいて、EICによる接続を許可するリソースをインスタンスごとに定義しています。接続を許可したいインスタンスが増えた場合、都度ポリシーをメンテナンスしていく必要があります。これはリソースベースの考え方です。

今回のアップデートにより、以下のようなポリシーの書き方がサポートされました。

{ 
   "Version":"2012-10-17",
   "Statement":[ 
      { 
         "Effect":"Allow",
         "Action":"ec2-instance-connect:SendSSHPublicKey",
         "Resource": "arn:aws:ec2:region:account-id:instance/*",
         "Condition":{ 
            "StringEquals":{ 
               "aws:ResourceTag/tag-key":"tag-value"
            }
         }
      },
      {
        "Effect": "Allow",
        "Action": "ec2:DescribeInstances",
        "Resource": "*"
      }
   ]
}

Resourceではインスタンスを制限せず、Conditionにおけるリソースタグによってコントロールしています。タグベースでの認証が可能になったため、条件として指定するタグキーと値の組み合わせを工夫することで、ABAC に対応することが可能になりました。

タグベースの認証?タグベースの認可?

上記ドキュメントでは「タグベースの認証」という表現が使われています。原文を確認すると、「tag-based authorization」であったことが分かります。

Documentation updates · awsdocs/amazon-ec2-user-guide@2a8a180

We currently do not support tag-based authorization for Instance Connect.

「認証と認可」という表現がされる時、それぞれ以下のような単語が割り当てられます。

  • 認証:Authentication
  • 認可:Authorization

原文の単語と、タグベースでアクセス許可をコントロールする、という働きを鑑みると、「タグベースの認可」という表現の方が適切そうですね。

やってみた

Project タグ「Chiba」をIAMユーザーとEC2インスタンスに付与し、同一の値を持つ組み合わせの場合のみEICによる接続を許可するような形を目指します。

EICによる接続には以下のオプションがありますが、今回はマネジメントコンソールからブラウザベースでの接続を行います。

  • ブラウザベースのクライアントを使用した接続
  • EC2 Instance Connect CLI を使用した接続
  • 独自のキーと SSH クライアントを使用した接続

EC2インスタンスのセットアップ

EC2インスタンスをパブリックサブネットに作成します。今回は以下の条件で作成しました。

  • amzn2-ami-hvm-2.0.20200406.0-x86_64-gp2 (ami-0f310fced6141e627)
  • t3.micro
  • パブリックIP有効化
  • 東京リージョン

EICによるブラウザ経由での接続時には、特定のIPレンジからのSSHのインバウンドを許可する必要があります。

こちらのリストから、"service": "EC2_INSTANCE_CONNECT"となっているIPプレフィックスを許可する必要があります。今回は東京リージョンで試すため、3.112.23.0/29を許可しました。

ひとまずEICではなく通常の方法でSSH接続し、EICに必要となるec2-instance-connect をインストールしようとしたところ、今回使用したAMIではすでにインストール済みでした。

[ec2-user@ip-192-168-0-233 ~]$ sudo yum install ec2-instance-connect
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                               | 2.4 kB     00:00     
パッケージ ec2-instance-connect-1.1-12.amzn2.noarch はインストール済みか最新バージョンです
何もしません
[ec2-user@ip-192-168-0-233 ~]$

インストールした際に、eic_から始まる名称のファイルが作成されるようです。

[ec2-user@ip-192-168-0-233 ~]$ ls -l /opt/aws/bin
合計 36
lrwxrwxrwx 1 root root    45  4月  7 01:51 cfn-elect-cmd-leader -> ../apitools/cfn-init/bin/cfn-elect-cmd-leader
lrwxrwxrwx 1 root root    41  4月  7 01:51 cfn-get-metadata -> ../apitools/cfn-init/bin/cfn-get-metadata
lrwxrwxrwx 1 root root    32  4月  7 01:51 cfn-hup -> ../apitools/cfn-init/bin/cfn-hup
lrwxrwxrwx 1 root root    33  4月  7 01:51 cfn-init -> ../apitools/cfn-init/bin/cfn-init
lrwxrwxrwx 1 root root    43  4月  7 01:51 cfn-send-cmd-event -> ../apitools/cfn-init/bin/cfn-send-cmd-event
lrwxrwxrwx 1 root root    44  4月  7 01:51 cfn-send-cmd-result -> ../apitools/cfn-init/bin/cfn-send-cmd-result
lrwxrwxrwx 1 root root    35  4月  7 01:51 cfn-signal -> ../apitools/cfn-init/bin/cfn-signal
lrwxrwxrwx 1 root root    21  4月  7 01:51 ec2-metadata -> /usr/bin/ec2-metadata
-rwxr-xr-x 1 root root  6497  1月 16 00:31 eic_curl_authorized_keys
-rwxr-xr-x 1 root root  7323  1月 16 00:31 eic_harvest_hostkeys
-rwxr-xr-x 1 root root 15696  1月 16 00:31 eic_parse_authorized_keys
-rwxr-xr-x 1 root root   823  1月 16 00:31 eic_run_authorized_keys

/etc/ssh/sshd_configが以下の記述になっていれば、EICによる接続に対応しています。

[ec2-user@ip-192-168-0-233 ~]$ sudo cat /etc/ssh/sshd_config | grep AuthorizedKeysCommand
AuthorizedKeysCommand /opt/aws/bin/eic_run_authorized_keys %u %f
AuthorizedKeysCommandUser ec2-instance-connect

IAMユーザーのセットアップ

ブラウザによる接続を行うIAMユーザーを準備します。

IAMユーザーにアタッチするIAMポリシー「test-eic-policy」を作成し、以下の内容を定義します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ec2-instance-connect:SendSSHPublicKey",
            "Resource": "arn:aws:ec2:ap-northeast-1:000000000000:instance/*",
            "Condition": {
                "StringEquals": {
                    "aws:ResourceTag/Project": "${aws:PrincipalTag/Project}"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": "ec2:DescribeInstances",
            "Resource": "*"
        }
    ]
}

ハイライト部分の記述によって、「リソースに付与されているProjectタグの値とプリンシパル(ここではIAMユーザー)に付与されているProjectタグの値が同一であればAllow」という制御を実現しています。

上記のポリシーをアタッチし、Projectタグの値として「Chiba」を付与したユーザー「Tes-EIC-User」を作成します。

EICによる接続

上記のユーザーでマネジメントコンソールにサインインし、EC2のインスタンス一覧画面に遷移します。

(参照用の権限はec2:DescribeInstancesしか付与していないため、一部の値が表示されないことに気付きました。知見。)対象のインスタンスにProjectタグ「Chiba」が付与されていることを確認し、「接続」を押下します。

接続方法の中からEICを選択し、接続先ユーザー名を指定して「接続」を押下します。

このように接続ができます。

CloudTrailには以下のように記録されます。どのIAMユーザーがどのOSユーザーに対してEICによるSSH接続を実施したか、というのが記録に残るので管理が捗りますね。

{
    "eventVersion": "1.05",
    "userIdentity": {
        "type": "IAMUser",
        "principalId": "AIDAQ3BIXXXXXXXXXXGDH",
        "arn": "arn:aws:iam::000000000000:user/Test-EIC-User",
        "accountId": "000000000000",
        "accessKeyId": "ASIAIMXXXXXXXXXXEKHA",
        "userName": "Test-EIC-User",
        "sessionContext": {
            "sessionIssuer": {},
            "webIdFederationData": {},
            "attributes": {
                "mfaAuthenticated": "false",
                "creationDate": "2020-05-19T15:26:48Z"
            }
        }
    },
    "eventTime": "2020-05-19T16:26:36Z",
    "eventSource": "ec2-instance-connect.amazonaws.com",
    "eventName": "SendSSHPublicKey",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "XX.57.XX.181",
    "userAgent": "console.aws.amazon.com",
    "requestParameters": {
        "instanceId": "i-007b6efxxxxxxxx43",
        "osUser": "ec2-user",
        "SSHKey": {
            "publicKey": "ssh-rsa xxxxxxxxxxxxxxxxxxxxx"
        }
    },
    "responseElements": null,
    "requestID": "axxxxxa9-2add-4524-a8f6-6axxxxxxxxxx",
    "eventID": "75xxxxxa-7c9b-4b4b-b60b-bf2xxxxxxxx6",
    "eventType": "AwsApiCall",
    "recipientAccountId": "000000000000"
}

別のユーザーによる操作で、EC2インスタンスのProjectタグの値を、「Saitama」に変更してみました。

この状態で再度同じようにEICによる接続を試みると、以下のような画面が表示されたまま、特に何も起こりません。

特にコンソール上でエラー等は表示されないのですが、CloudTrailでは以下のように記録されます。

{
    "eventVersion": "1.05",
    "userIdentity": {
        "type": "IAMUser",
        "principalId": "AIDAQ3XXXXXXXXXXEBGDH",
        "arn": "arn:aws:iam::000000000000:user/Test-EIC-User",
        "accountId": "000000000000",
        "accessKeyId": "AXXXXXXXXXXZTTEFW5AA",
        "userName": "Test-EIC-User",
        "sessionContext": {
            "sessionIssuer": {},
            "webIdFederationData": {},
            "attributes": {
                "mfaAuthenticated": "false",
                "creationDate": "2020-05-19T15:26:48Z"
            }
        }
    },
    "eventTime": "2020-05-19T16:27:07Z",
    "eventSource": "ec2-instance-connect.amazonaws.com",
    "eventName": "SendSSHPublicKey",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "54.240.2XX.60",
    "userAgent": "Coral/Jakarta",
    "errorCode": "AccessDenied",
    "errorMessage": "User: arn:aws:iam::000000000000:user/Test-EIC-User is not authorized to perform: ec2-instance-connect:SendSSHPublicKey on resource: arn:aws:ec2:ap-northeast-1:000000000000:instance/i-00xxxxxxxxxx17e43",
    "requestParameters": null,
    "responseElements": null,
    "requestID": "xxxxx9b5-1c3a-46d1-a9a5-8xxxxxxxxxxc",
    "eventID": "dxxxxx09-281b-454d-a876-xxxxxxxxxx6c",
    "eventType": "AwsApiCall",
    "recipientAccountId": "000000000000"
}

こちらではどのOSユーザーに対して接続を試みたかまでは記録されないようですね。

ともかくこれでEICにおけるABACの確認ができました!

終わりに

EIC も ABAC も初めての概念だったのですが、どちらも便利そうですね。EICはブラウザから接続する際にはパブリックIPが必須になるので、そこが解消されたらより面白いなと感じました。

オリジナル・バージョンと併せて理解が深まれば何よりです。結局何事もオリジナルが一番だと思います。でもオルタナティヴっていう響きは好きです。