【AWS】iam:PassRoleとsts:AssumeRoleの違い

AWSでロールを扱う際、iam:PassRolests:AssumeRole の両方が説明に出てくることが多い。どちらもロールを使えるようにするために必要なアクションだが、いまいち違いがわからなかったので改めて調べてみた。

iam:PassRole

まず iam:PassRole は、ユーザに対して付与するアクセス許可ポリシーで指定できるアクションである。
例えばAWS Batchを使うとき、ジョブの内容に応じたアクセス許可ポリシーをロールに付与しておき、そのロールをAWS Batchで指定しておく必要がある。ジョブが作成されると、AWS Batchがロールを使ってジョブの内容を実行する。
このとき、もしユーザ自身が aws batch submit-job コマンド等でジョブを登録する場合、 iam:PassRole アクション内で当該ロールをResourceに指定したポリシーを作り、それをユーザにアタッチする必要がある。これを行わないと、ロールをサービスに渡すことができない。 AWS Batchの公式ドキュメントにある例では、iam:PassRole アクションはアクセス許可ポリシー内で以下のように記述されている。

docs.aws.amazon.com

{
    "Effect": "Allow",
    "Action": [
        "iam:PassRole"
    ],
    "Resource": [
        "arn:aws:iam::<aws_account_id>:role/TestRole"
    ],
    "Condition": {
        "StringEquals": {
            "iam:PassedToService": [
                "batch.amazonaws.com"
            ]
        }
    }
}

このポリシーをアタッチされたユーザは、 Resource で指定されたロールをアタッチできるようになる。加えて、 IAM条件キー iam:PassedToService を使うことで、アタッチの対象にできるサービスがAWS Batchのみに制限されている。

docs.aws.amazon.com

(補足だが、ロールをアタッチできる対象はAWSサービスのみではない。他のAWSアカウントのユーザにロールを付与して、特定のアクションをクロスアカウントで行ってもらうという使い方もある)

個人開発をしている場合、 iam:PassRole なんて指定した覚えがないという人も多いかもしれない。個人用環境でAWSを触るときによく使われる AdministratorAccess ポリシーでは、全てのアクションが全てのリソースに対して許可されているので、 iam:PassRole を意識する必要はなくなっている。また、各サービスごとにあらかじめ用意されているアクセス許可ポリシーを使っている場合も、既に iam:PassRole が許可されていることが多い。 AWS Batchであれば、 AWSBatchFullAccess ポリシー内で、Batch関連のあらかじめ用意されたロールについて iam:PassRole が許可されている。

docs.aws.amazon.com

sts:AssumeRole

sts:AssumeRole は、ロールに対して付与する信頼ポリシーの中で指定できるアクションである。この「信頼ポリシー」は iam:PassRole に登場した「アクセス許可ポリシー」とは概念が異なる。アクセス許可ポリシーがアイデンティティベースのポリシーなのに対し、信頼ポリシーはリソースベースのポリシーの一種である。アイデンティティベースのポリシーにはリソースネーム(ARN)が付与され、様々なアイデンティティに使い回してアタッチすることが可能だが、リソースベースは特定のリソースと1対1・不可分の関係である。

docs.aws.amazon.com

sts.AssumeRole は、そのロールをどのリソースにAssume(引き受けさせる)できるかを定義する。もし Assume を管理するという概念がなかった場合、例えばAWSサービスAのために強い権限を持つロールを作ったとして、そのロールを別のAWSサービスBに与えることができてしまう。その場合、サービスBが意図しない強い権限を持ち、セキュリティ上の問題となりうる。そこで、そのロールをどのリソースに渡すことができるか、ロール自体と不可分のインラインポリシーとして定義できるようになっている。もちろん sts.AssumeRole を何も定義しない場合、そのロールはどのリソースにも渡せない。
iam:PassRole の場合と同様に、AWSによってあらかじめ用意されているロールには、既に sts:AssumeRole が定義されている。例えば AWSBatchServiceRole には以下の信頼ポリシーが付与されており、もしこのポリシーがない場合は改めて作成してからロールを使うよう案内されている。

docs.aws.amazon.com

{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {"Service": "batch.amazonaws.com"},
        "Action": "sts:AssumeRole"
    }]
}

この信頼ポリシーにより、 AWSBatchServiceRoleAWS Batch以外のAWSサービスには引き受けられないようになっている。
なお、 sts:AssumeRole のPrincipalはAWSサービスのみではなく、他のAWSアカウントのユーザなどにもできる。

iam:PassRolests.AssumeRole により、「ユーザはどのロールを付与することができるか」「そのロールはどのリソースに付与されることができるか」の両面からロールを管理できるようになっている。ロールは通常であれば持っていない権限を一時的に付与するものであるため、強い権限が意図しない相手に渡らないようにするための仕組みが必要だったのだろう。

ロールを作って使うときの全体像は、以下のドキュメントがわかりやすい。
docs.aws.amazon.com

【AWS】CloudTrailログからリクエストのGlobal Condition Keyを推測する

AWSのポリシーを作るとき、 Condition 句を使って特定のプリンシパルをポリシー適用の例外として指定したい時がある。 Condition 句の中で指定した条件は、リクエストに含まれるGlobal Condition Keyと照らし合わせて確認され、リクエストが許可されるかどうかが決まる。

docs.aws.amazon.com

しかし、業務で特定のアカウントやVPC・サービスのみがS3バケットにアクセスできるようバケットポリシーを作ろうとした際、テストの方法がわからず、ポリシーを変えてはリクエストを送ってみて確認、という作業を繰り返すことになってしまった。
より効率的な方法はないかカスタマーサポートに聞いてみたところ、Global Condition Keyを直接確認できる方法は存在しないが、CloudTrailでリクエストログを取得していればそこから推測できると教えてもらった。

CloudTrailのログのフィールドは以下のドキュメントにまとまっている。

docs.aws.amazon.com

例えば、AWS glueサービスからS3バケットに置いてあるスクリプトをダウンロードしようとして、Access Deniedのエラーが出たとする。その場合、エラーメッセージに requestId が表示されていれば、それを使ってCloudTrailのログを検索することで、当該リクエストの詳細なログを取得できる。
ポリシーの Condition によく使うキーとの対応として、 aws:userid は CloudTrailログの useridentity.principalId に、 aws:PrincipalAccountuserIdentity.accountId に相当する。また、VPC Endpoint経由でのアクセスかどうか確認するには、CloudTrailログ内に vpcEndpointId フィールドが存在するかを確認すればよい。
リクエストのログを取得することができれば、トライアンドエラーでポリシーを何度も変更する必要がなくなり、ポリシー設計が非常に楽になる。

【Terraform】AWS providerの認証で詰まった

先日リモート環境から、terraformで管理しているAWSのresourceを更新する作業を始めようとして terraform init したところ、アクセスができずに詰まってしまった。

$ terraform init
Initializing modules...

Initializing the backend...
Error refreshing state: AccessDenied: Access Denied
    status code: 403, request id: XXXXXXXXXXXXXXXX, host id: xxxxxxxxxxxxxxxxxxxxxxxxxxxx

リモート環境でもawsに接続できるよう ~.aws/credentials ファイルにaccess keyとsecretを入れておいたのだが、Terraformのドキュメントを見たところ、 ~.aws/credentials を作るだけでは認証ができないことがわかった。

registry.terraform.io

shared credentials fileで認証しようと思う場合、 provider resourceの shared_credential_file にファイルパスを渡しておかなければいけない。よく考えればTerraformで使えるbackendはAWSだけではないので、shared credentials fileを単に置いておいただけでデフォルトで読み取ってくれることはない。

今回はproviderの設定を変えることができない状況だったが、Terraformの認証では環境変数があればshared credentials fileよりも高い優先度で認証に使われる。 AWS_ACCESS_KEY_ID , AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION環境変数を正しく設定すると terraform init を行うことができた。

kubectlのコマンド補完を有効にする

kubectlを使っていると、current-contextの確認やnamespaceの切り替えなどでコマンドを打つのが面倒になってくる。こんなときはプラグインをインストールするのが一般的だが、その前に素の状態でできることはないか探してみたところ、kubectl自体に補完機能があることに気づいた。

kubernetes.io

補完機能はbashzshに対応している。zshであれば、以下の1行を.zshrcに足せば良い。

source <(kubectl completion zsh)

kubectl completion zsh は、zshで補完を行うためのスクリプトを自動生成している。 補完機能を有効にすると、例えば configcurrent-context などkubectlのサブコマンドや引数をTabで補完してくれるようになるため、タイピングが楽になる。ただし、namespaceやPodの名前等までは補完してくれない。

【Terraform】Dynamic BlockでModuleのArgumentを出し分ける

業務ではAWS Lambda FunctionをTerraformのModuleで管理しているが、先日Lambda Functionの1つに失敗時のアラートをつけるべく、dead letter queueを設定したいと思い立った。Terraformでは dead_letter_config Argumentを使うことで設定することができる。

registry.terraform.io

しかし、単に dead_letter_config ArgumentをModule内に付け足してしまうと、全てのLambda Functionでdead letter queueの設定をしなければならなくなってしまう。かといってModuleの呼び出し先で特定のArgumentを追加する方法も見当たらず、他のLambda Functionに変更を及ぼさずに済む方法がないか詰まってしまった。

このような場合、Terraform v0.12以上であればDynamic Blockを使うことでArgument自体の出し分けが可能となる。

www.terraform.io

Dynamic Blockは、resource内でネストされるブロックに対して使うことで、同じ構造のブロックを動的に生成できるようになるもの。ネストしたブロックを使って記述するArgument限定ではあるが、引数を空にすることでそのブロックを生成させないこともできる。

今回の場合、Moduleに以下のような記述を追加した。

variable "dead_letter_config_target" {
  default     = []
  type        = list
  description = "(Optional) arn of dead_letter_config's target"
}
resource "aws_lambda_function" "lambda" {

  # other arguments
  
  dynamic "dead_letter_config" {
    for_each = toset(var.dead_letter_config_target)
    content {
      target_arn = dead_letter_config.value
    }
  }

Lambda FunctionのModuleの中で、 dead_letter_config の設定をDynamic Blockで作るようにする。この中では、 dead_letter_config_target で指定したリスト内の要素がループされ、 target_arn の値として使われる。

そのうえで、dead_letter_queueを使いたいLambda Functionで、Module呼び出しの際に dead_letter_config_target 変数を指定する。

module "udomomo-lambda-function" {
  source = "../module/lambda"

  # specify other variable

  dead_letter_config_target = ["arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:target-sns"]
}

これでこのLambda Functionのみ dead_letter_config の設定が追加される。 dead_letter_config_target 変数を指定しなければ、他のLambda Functionには全く変更は生じない。

【CKAD】素のVimでYAMLファイルを扱う時のTips

www.cncf.io

CKADを受験しようと思い練習をしているが、YAMLファイルを扱うのが面倒になってきた。特にインデント調整にかなり精神力を削られる。
時間が足りないことで有名な試験なので何か便利な拡張ライブラリを探したいところだが、CKADの場合実行環境が運営側から与えられる形であり、おそらく素のVimを使わなければいけない。YAMLファイルの場合に役立ちそうなテクニックをまとめておく。

インデントを設定する

KubernetesYAMLファイルはインデントがスペース2文字になっていることが多いが、Vimの初期設定ではタブ(8文字分)になっている。この設定はVimのコマンドで可能である。

:set et
:set sw=2 ts=2 sts=2

:set et でタブをスペースに変換し、 :set sw=2 ts=2 sts=2 で幅を2文字分にしている。 また :set autoindent をつけると、行を改行したときもインデントが保持される。
(2020/10/19追記) :set autoindent をつけると、KubernetesのドキュメントからYAMLをコピペしたときに自動でインデントが追加されてしまい、非常に見辛くなるので削除した。

行のインデントを調整する

コマンドモードの状態で >> を押すと、カーソルがある行のインデントを1つ進めることができる。 << で戻すことができる。カーソルが行頭・行末になくても良いので、インデントを変えたいときに非常に便利。
また複数行のインデントを変えたいときは、ビジュアルモードで行を選択し shift + > ( shift + < )とすれば良い。 . で繰り返すことができる。

CKAD本番では、最初に簡単な.vimrcを作った方が良いかもしれない。

localstackでローカルにS3を建てる

先日、localstackの存在を知った。このライブラリはAWSの各サービスをローカルで再現できるもので、AWSを使った機能を開発環境下でテストするのに役立つ。

github.com

一部のサービスは有料版でないと使えないが、今回は無料で使えるS3の機能を試してみた。

localstackは既に公式Docker imageが用意されているので、docker-compose.yml を作成すればすぐに使える。

version: "3"
services:
  localstack:
    image: localstack/localstack:0.11.3
    container_name: localstack
    ports:
      - 4566:4566
      - 8080:8080
    environment:
      - DEFAULT_REGION=ap-northeast-1
      - SERVICES=s3
      - DATA_DIR=/tmp/localstack/data
    volumes:
      - /Users/naoya-otani/.localstack:/tmp/localstack/ 

ポート 4566 は、localstack上で動く全てのAWSサービスのデフォルトのエンドポイントのポート ( EDGE_PORT 環境変数で設定を変更できる)。 ポート 8080GUIダッシュボードのデフォルトのエンドポイントである。 SERVICES には使いたいサービス名を記載する。
今回はS3なので、データの永続化もしておきたい。 DATA_DIR はlocalstackの中でデータを保持するディレクトリを指すが、このディレクトリはlocalstackコンテナ内のものなので、 volumes においてこのディレクトリとホスト側のディレクトリをマウントする必要がある。

docker-compose up -d で起動したら、 http://localhost:4566 をエンドポイントとしてローカルのS3にアクセスできる。
ローカルのS3も、aws-cliコマンドで操作することができる。 --endpoint-url オプションに上記のエンドポイントを指定することで、localstackのS3に対してコマンドを実行することができる。 AWS_ACCESS_KEY_IDAWS_ACCESS_SECRET_KEY は任意の値で良い。ただし、 --endpoint-url をつけ忘れて間違ってリモートのS3にコマンド実行してしまう事故を防ぐため、まずlocalstack用のprofileを作成しておく方が良いだろう。(以下では両方とも値を dummy にしている)

$ cat >> ~/.aws/credentials << EOF
[localstack]
aws_access_key_id = dummy
aws_secret_access_key = dummy
EOF

$ cat >> ~/.aws/config << EOF
[profile localstack]
region = ap-northeast-1
output = json
EOF

$ aws configure list --profile localstack
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile               localstack           manual    --profile
access_key     ****************ummy shared-credentials-file    
secret_key     ****************ummy shared-credentials-file    
    region           ap-northeast-1      config-file    ~/.aws/config

profileを作成したら、localstackにS3のバケットを作成する。リモートのS3を操作する場合と同様、 s3api list-buckets コマンドでバケットを確認できる。

$ aws s3 mb s3://sample-bucket --endpoint-url=http://localhost:4566 --profile localstack
make_bucket: sample-bucket
$ aws s3api list-buckets --endpoint-url=http://localhost:4566 --profile localstack 
{
    "Buckets": [
        {
            "Name": "sample-bucket",
            "CreationDate": "2020-08-02T11:42:51.547110Z"
        }
    ],
    "Owner": {
        "DisplayName": "webfile",
        "ID": "xxxxxxxxxxxxxxxxxxxxxxxxxx"
    }
}

sample-bucket にファイルをアップロードする。 s3 ls コマンドでファイルが確認できれば成功。

$ aws s3 cp sample.txt s3://sample-bucket/sample-dir/ --endpoint-url=http://localhost:4566 --profile localstack
upload: ./sample.txt to s3://sample-bucket/sample-dir/sample.txt 
$ aws s3 ls s3://sample-bucket/sample-dir/ --endpoint-url=http://localhost:4566 --profile localstack
2020-08-02 21:27:04         15 sample.txt