AWS CLI で IAM Role を作りつつ、「Assume」や「Principal」について考える

CLI でやってみようシリーズ。

とりあえずロールを作ってみる

インラインの Assume Role に何を指定して良いのか分からなかったので、とりあえずブラウザコンソールから一つ適当なロールを作り、それを取得してみる。 ∥ get-role — AWS CLI 1.10.32 Command Reference

$ aws iam get-role --role-name myLambdaRole | jq .
{
  "Role": {
    "AssumeRolePolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Action": "sts:AssumeRole",
          "Effect": "Allow",
          "Principal": {
            "Service": "lambda.amazonaws.com"
          }
        }
      ]
    },
    "RoleId": "AROA~NYHE",
    "CreateDate": "2016-05-16T08:59:21Z",
    "RoleName": "myLambdaRole",
    "Path": "/",
    "Arn": "arn:aws:iam::~:role/myLambdaRole"
  }
}

それをコピペしてみる。jq(1) を通しておけば、とりあえずフォーマットとして不正な JSON は飛ばない。 ∥ create-role — AWS CLI 1.10.32 Command Reference

#!/bin/bash
# -*- coding: utf-8 -*-
set -o nounset -o errexit -o pipefail

name=myNewRole
path="/division_x/project_y/"
assume_role_principal_service=lambda.amazonaws.com

assume_role_policy_document=$(jq -c . <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Effect": "Allow",
      "Principal": {
        "Service": "$assume_role_principal_service"
      }
    }
  ]
}
EOF
)
role=$(aws iam create-role \
  --role-name "$name" \
  --path "$path" \
  --assume-role-policy-document "$assume_role_policy_document" | jq -c . )
jq . <<<"$role"

実行結果:

$ ./aws-iam-create-role
{
  "Role": {
    "AssumeRolePolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Action": "sts:AssumeRole",
          "Effect": "Allow",
          "Principal": {
            "Service": "lambda.amazonaws.com"
          }
        }
      ]
    },
    "RoleId": "AROA~UOW2",
    "CreateDate": "2016-05-22T10:08:43.394Z",
    "RoleName": "myNewRole",
    "Path": "/division_x/project_y/",
    "Arn": "arn:aws:iam::~:role/division_x/project_y/myNewRole"
  }
}

説明

だんだん語源学みたいになって行きますが。

ロール名

まず、ロール名のつけ方について。ここでは “myNewRole” としている。ブラウザコンソールから新規ロールを作ろうとすると、サンプルとして出てくるロール名が “myRole” と lowerCamelCase 形式なので、それに従ってみた。

Assume Role Policy について

次に AssumeRolePolicyDocument がそもそも何なのかである。

コンソールで言うと、“Role Type” の “AWS Service Roles” から選んだものに相当し、このサービスからしかこのロールが使えなくなる。

それを AWS 用語で言うと、この Assume Role Policy は、今回作ったこのロールが、AWS STS (AWS Security Token Service) によって使われる(STS が AssumePolicy アクションを起こすことを許す)ことを意味するポリシーである。 ∥ 一時的セキュリティ認証情報 - AWS Identity and Access Management

IAM における普通のポリシー(普通の Inline Policy や Managed Policy)が、「当該ロールが持つ、何かをする権限」について規定しているのに対して、このポリシーは「このロールが STS によって使われる際の権限」について規定しているので、別枠になっているのである。

“assume” は普通は「仮定する」「みなす」などの意味で用いられるが、やや形式張った用法として「〈実権など〉を握る; 〈役割責任任務など〉を引き受ける(undertake)」“take or begin to have (power or responsibility)” の意味がある。認証・認可系のコンテキストで出てくる “assume” はこちらだろうな。

どうやら “assume” は、原義としては「証拠無しに認める」ということのようです。よって、以下のような 2 通りの語義が出てきたのでしょう。 ∥ assume - Wiktionary

1.「(証拠は無いけれどとりあえず状況を認めて)仮定する」「みなす」 2.「(これといった credential は提示せずに、役割を)引き受ける」「(権限を)得る」

つまりこれは、とある Role なりサービスなりが、他の Role に対して sts:AssumeRole する(他の Role(の権限)を assume(取得する))ことができるという意味なんだな。 ∥ AssumeRole - AWS Security Token Service

Principal って?

次に、“principal” について。

ポリシーでは何かと出てくる「プリンシパル」だが、これもどうも今まで意味がピンと来ていなかった。一般的には「(演劇などの)主役, 主演者; (コンサートの)独奏者, 主席奏者; (犯罪の)主犯」の方の意味なんだろうが、別の意味で「〖しばしば~s〗(代理人に対して)本人; (決闘の介添人に対し)決闘する本人」“a person for whom another acts as an agent or representative” がある。

原義としては「(何らかの集団の中で)一番目の人」の意味なので、下記の意味が出てきたのだろう。

  1. 「(演劇などの他の端役に対して)主役」「(オーケストラの他の楽器担当に対して)独奏」
  2. 「(一緒に働く代理人に対して)本人」

つまりは、そのポリシーを持ったもの(User や Role)の権限で動く際はいわば agent であり、それを使う側(ユーザやサービス)が principal(代理人ではなく、代理人を使う本人)である、という意味になる。 ∥ IAM ポリシーエレメントの参照 - AWS Identity and Access Management

パス

web コンソールではパスが付けられず、API 経由ならば付けられるので嬉しくなってつけてみた。 ∥ IAM ID - AWS Identity and Access Management

要は、ディレクトリサービスにおける部署のようなもので、ユーザやロールの数が多くなってきたら、「パス」に基いて何かを出来るように用意されている属性です。あくまでも我々の便宜のために用意されている属性であり、AWS のシステム側では全く使っていない模様。

Managed Policy の付与

せっかくなので、role に managed policy が attach されているか否かを確認して、attach されていなかったら attach するようにしてみます。このへんの jq(1) の使い方については、前回の投稿を参考にしてください。 ∥ jq(1) の力を借りて Bash スクリプト内で JSON を操作する - Qiita

#!/bin/bash
# -*- coding: utf-8 -*-
set -o nounset -o errexit -o pipefail

role_name="myNewRole"
policy_arns=(
  "arn:aws:iam::aws:policy/AWSLambdaFullAccess"
  "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
)
policies_attached=$(aws iam list-attached-role-policies \
  --role-name "$role_name" | jq -c .AttachedPolicies )
for policy_arn in "${policy_arns[@]}"
do
  if test "$(jq -c --arg policy_arn "$policy_arn" \
    '. | map(select(.PolicyArn == $policy_arn))' <<<"$policies_attached" )" \
    = "[]"
  then
    aws iam attach-role-policy \
      --role-name "$role_name" \
      --policy-arn "$policy_arn"
    echo Attached "$policy_arn"
  else
    echo Policy "$policy_arn" already attached.
  fi
done

実行結果:

$ aws iam attach-role-policy \
  --role-name myNewRole \
  --policy-arn "arn:aws:iam::aws:policy/AWSLambdaFullAccess"
$ ./aws-iam-attach-role-policy
Policy arn:aws:iam::aws:policy/AWSLambdaFullAccess already attached.
Attached arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
$ aws iam list-attached-role-policies --role-name myNewRole | jq .
{
  "AttachedPolicies": [
    {
      "PolicyName": "AWSLambdaFullAccess",
      "PolicyArn": "arn:aws:iam::aws:policy/AWSLambdaFullAccess"
    },
    {
      "PolicyName": "AWSLambdaBasicExecutionRole",
      "PolicyArn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
    }
  ]
}