アプリケーションにレポートへのアクセスを付与する

AWSBeginner
オンラインで実践に進む

はじめに

稼働中のアプリケーションが、非公開レポートを読み取る必要があります。Terraform の参照を通じて IAM ロールとインスタンスプロファイルを接続し、対象オブジェクトへのアクセスを付与して、許可を削除する前後でアプリケーションの実際のリクエストをテストします。

Terraform の参照と EC2 デプロイに加えて、AWS ロールの信頼関係、インスタンスプロファイル、範囲を限定した S3 許可を理解していることを前提とします。新しい環境には、稼働中のアプリケーション、そのネットワーク、非公開のレポート用バケット、無関係の参照用アプリケーションが用意されています。用意されたデータと参照用環境をそのまま保持します。

認定試験との関連

認定資格 試験のタスク 演習内容
Cloud Practitioner (CLF-C02) Task 2.3 IAM ロールと最小権限のオブジェクト許可。
Solutions Architect – Associate (SAA-C03) Task 1.1 範囲を限定した許可による、ロールベースのワークロードアクセス。
CloudOps Engineer – Associate (SOA-C03) Task 4.1 基本的な IAM ロールとポリシーの実装。

実験の概要

概念の概要:EC2 アプリケーションがインスタンスプロファイルと IAM ロールを使って report.csv を読み取り、private.csv は引き続き拒否されます。

稼働中のアプリケーションにロール ID を付与する

このステップでは、EC2 用ロールとインスタンスプロファイルを定義し、用意されたアプリケーションにプロファイルを関連付けて、実際に有効なワークロード ID を観察します。

上部の AWS View は、Terminal で照会するのと同じリソースを表示します。Request path セレクターでは、アプリケーションのヘルス、ワークロード ID、どちらかの非公開オブジェクトを要求できます。Terminal は準備済みの運用担当者 ID を使用し、アプリケーションは独自のインスタンスロールを使用します。運用担当者のクエリが成功しても、アプリケーションに許可がある証拠にはなりません。

準備済みのルートを確認し、インスタンス ID を記録します。

cd /home/labex/project/application-reports
cat terraform.tfvars.json
cat application.tf
terraform state list
INSTANCE_ID=$(terraform output -raw instance_id)
echo "$INSTANCE_ID"
REPORT_BUCKET=$(terraform output -raw report_bucket)
echo "$REPORT_BUCKET"

既存の状態はアプリケーションインスタンスを管理しています。ネットワーク、AMI、バケット、参照用アプリケーションは別途用意されたものです。User Data は、すでに準備済みバケット内の report.csv をアプリケーションに指定していますが、アクセスキーは含んでいません。

AWS View で application-server、User · 198.51.100.10、Application · 8081、Health を選択し、Send request をクリックします。Connected · 200 と Report application が表示されるはずです。Private report を選び、再送信します。アプリケーションはまだ読み取れず、Connected · 503 と Application storage unavailable を報告します。HTTP アプリケーションには到達できていますが、ワークロードの認証情報がありません。

IAM ロールの信頼ポリシーは、誰がそのロールを引き受けられるかを制御します。インスタンスプロファイルはロールを EC2 に接続します。どちらも、それだけで S3 オブジェクトの読み取りを許可するものではありません。この 2 つのリソースを作成します。

cat > identity.tf <<'HCL'
resource "aws_iam_role" "reports" {
  name = "tf-report-reader"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = { Service = "ec2.amazonaws.com" }
      Action = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_instance_profile" "reports" {
  name = "tf-report-reader"
  role = aws_iam_role.reports.name
}
HCL
sed -i '/vpc_security_group_ids/a\  iam_instance_profile = aws_iam_instance_profile.reports.name' application.tf
terraform fmt
terraform validate
terraform plan

sed コマンドは、既存のセキュリティグループ属性の後にインスタンスプロファイル属性を挿入します。そのリソース参照が稼働中のインスタンスをプロファイルに接続し、プロファイルはロールを参照します。プランを確認してください。ロールとプロファイルを作成し、既存インスタンスの関連付けを更新します。インスタンスの置き換えや User Data の変更は行いません。

適用し、yes を入力します。

terraform apply

サービス側の関連付けとプロファイルを確認します。

aws ec2 \
  describe-iam-instance-profile-associations \
  --filters "Name=instance-id,Values=$INSTANCE_ID" \
  --query 'IamInstanceProfileAssociations[].{Instance:InstanceId,State:State,Profile:IamInstanceProfile.Arn}'
aws iam \
  get-instance-profile \
  --instance-profile-name tf-report-reader \
  --query 'InstanceProfile.{Name:InstanceProfileName,Roles:Roles[].RoleName}'

AWS View で application-server の Workload identity を選び、リクエストを送信します。Connected · 200 と、assumed-role/tf-report-reader/ を含む ARN が表示されるはずです。これは、ゲスト内の STS が一時的なロール認証情報を使って取得したもので、Terminal の運用担当者によるものではありません。

Private report を選び、再送信します。Connected · 403 と Access denied が表示されるはずです。ロール ID は機能していますが、オブジェクトの読み取り許可はまだありません。これは意図した認可の失敗です。

対象のレポートだけへのアクセスを付与する

このステップでは、アプリケーションのロールに 1 つのオブジェクト読み取り許可を付与し、許可されたレポートと無関係の非公開オブジェクトの両方をテストします。

ロールの許可ポリシーは、そのセッションが何を実行できるかを定めます。正確な report.csv の ARN に対して s3:GetObject を許可する、ID ベースのポリシーを使用します。バケット名は用意された入力から、ロールはリソース参照から取得します。User Data に静的な認証情報を含めてはいけません。

cat > permissions.tf <<'HCL'
resource "aws_iam_role_policy" "report" {
  name = "ReadApplicationReport"
  role = aws_iam_role.reports.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = ["s3:GetObject"]
      Resource = ["arn:aws:s3:::${var.report_bucket}/report.csv"]
    }]
  })
}
HCL
terraform fmt
terraform validate
terraform plan

プランはポリシーを 1 つ追加します。アプリケーションを置き換えたり、新しいバケットを作成したりしてはいけません。適用し、yes を入力します。

terraform apply

AWS View で application-server、User、ポート 8081 の選択を維持します。Private report を選び、リクエストを送信します。Connected · 200 と、次のレポートが表示されるはずです。

period,total
Q1,320

Unrelated private object を選択し、別のリクエストを送信します。Connected · 403 と Access denied が表示されるはずです。アプリケーションは自分のレポートを読み取れますが、private.csv は許可の範囲外に残ります。この 403 は、到達したアプリケーションが返す実際の S3 認可結果であり、ネットワーク接続の失敗ではありません。

Workload identity に戻り、再送信します。ARN は引き続きワークロードのロールを示す必要があります。収束を確認します。

terraform plan

No changes. と表示されるはずです。 結果の例:アプリケーションがロール ID で非公開レポートを読み取っています。リソース ID は環境ごとに異なります。

許可を取り消して実際の効果をテストする

このステップでは、読み取りポリシーだけを削除し、有効なロール ID を持つ稼働中のアプリケーションが、レポートにアクセスできなくなることを確かめます。

許可の削除は、プロファイルの切り離しやインスタンスの停止とは異なります。ロールとプロファイルをそのまま保持し、ポリシーリソースの設定を削除してから、削除内容を確認します。

rm permissions.tf
terraform plan

読み取りポリシーの削除が提案され、インスタンスの置き換えはないはずです。適用し、yes を入力します。

terraform apply

残ったインラインポリシー名を独立して読み取ります。

aws iam \
  list-role-policies \
  --role-name tf-report-reader \
  --query PolicyNames

[] と表示されるはずです。AWS View でアプリケーションの Workload identity を送信します。引き続き Connected · 200 と同じロール名が返ります。次に Private report と Unrelated private object のリクエストを送信します。両方とも Connected · 403 と Access denied を返さなければなりません。この読み取りの拒否は、アプリケーションとロールの関連付けが存在していても、許可の削除が実際のワークロードのリクエストに影響することを証明します。

terraform plan

No changes. と表示されるはずです。

管理対象リソースを削除して用意されたデータを保持する

このステップでは、外部のレポート用バケット、そのオブジェクト、参照用環境を保持しながら、管理対象のインスタンス、プロファイル、ロールを削除します。

ポリシーはすでに削除されています。残りの削除範囲を確認します。

terraform plan -destroy

管理対象の 3 つのリソース、アプリケーションインスタンス、インスタンスプロファイル、IAM ロールが表示されるはずです。次に削除し、yes を入力します。

terraform destroy

同じインスタンスが、サービス側で終了していなければなりません。

aws ec2 \
  describe-instances \
  --instance-ids "$INSTANCE_ID" \
  --query 'Reservations[].Instances[].{ID:InstanceId,State:State.Name}'
terraform state list

terminated が表示され、状態に管理対象リソースがないはずです。正確な名前を使って、ロールとプロファイルの一覧を照会します。正常に取得した空の一覧は、認証失敗を削除成功と見なすことなく、不在を確かめられます。

aws iam list-roles --query "Roles[?RoleName=='tf-report-reader'].RoleName"
aws iam list-instance-profiles --query "InstanceProfiles[?InstanceProfileName=='tf-report-reader'].InstanceProfileName"

両方とも [] を返します。バケットはこの状態の管理対象外として用意されたものです。Terminal の運用担当者で保持された両方のオブジェクトを読み、そのバイト列を確認します。この運用担当者による読み取りは保持の確認であり、アプリケーションの限定的なアクセスは、別途ロールを通じてテスト済みです。

aws s3api get-object --bucket "$REPORT_BUCKET" --key report.csv /tmp/retained-report.csv
cat /tmp/retained-report.csv
aws s3api get-object --bucket "$REPORT_BUCKET" --key private.csv /tmp/retained-private.csv
cat /tmp/retained-private.csv

レポートは period,total / Q1,320 のままで、無関係のオブジェクトは private,retained のままです。AWS View で reference-server、User、Application · 8081、Health を選択し、リクエストを送信します。Connected · 200 と Reference application が表示されるはずです。

Terraform ファイルは確認用に保持してください。後で通常のプランを実行すると、残った設定の再作成が提案されます。削除が成功したことを確認するために、その設定を削除する必要はありません。

まとめ

Terraform の参照でアプリケーションのインスタンスをロールに接続し、ロール ID とオブジェクトの許可を区別して、非公開レポートの読み取り成功と無関係のオブジェクトの拒否をテストしました。その後、許可を取り消して実際の認可失敗を観察し、用意されたデータと参照用アプリケーションを保持しながら、管理対象リソースを削除しました。