S3 バケットにアクセスする .NET 8 アプリを EC2 インスタンスロールで試してみた

IAM ユーザーのアクセスキーを使って S3 バケットへのアクセスに必要な IAM ポリシーを IAM ユーザー付与すれば、アプリケーションとしては動作します。ローカル PC 上での開発時などは、自分の IAM ユーザーの権限を利用して開発すると思います。作成したアプリケーションを EC2 インスタンスや Lambda 上で動かす時は、自分の IAM ユーザーのアクセスキーは使用せず、EC2 インスタンスロールなど AWS サービスが使用する IAM ロールで実行することになるため、実際に試してみました。

検証用の S3 バケットとオブジェクトを作成

$ export AWS_ACCESS_KEY_ID="your-access-key"

$ export AWS_SECRET_ACCESS_KEY="your-secret-key"

$ aws s3api create-bucket \
  --bucket mnrst-test-bucket \
  --region us-east-1

$ echo test > test.txt

$ aws s3api put-object \
  --bucket mnrst-test-bucket \
  --key test.txt \
  --body test.txt

$ aws s3api list-objects \
  --bucket mnrst-test-bucket \
  --query "Contents[].Key"

[
    "test.txt"
]

.NET 8 で検証用アプリケーションを作成

$ dotnet new console -o awss3test --use-program-main

$ cd awss3test

$ dotnet add package AWSSDK.S3

$ code Program.cs
using System;
using System.Threading.Tasks;
using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;

class Program
{
    static async Task Main(string[] args)
    {
        string bucketName = "mnrst-test-bucket";

        try
        {
            IAmazonS3 s3Client = new AmazonS3Client(RegionEndpoint.USEast1);
            ListObjectsResponse response = await s3Client.ListObjectsAsync(new ListObjectsRequest
            {
                BucketName = bucketName,
                MaxKeys = 10
            });
            foreach (S3Object entry in response.S3Objects)
            {
                Console.WriteLine($"Key: {entry.Key}");
            }
        }
        catch (Exception e)
        {
            Console.WriteLine($"Exception: {e.Message}");
        }
    }
}

検証用アプリケーションで動作確認

$ dotnet run

Key: test.txt

$ unset AWS_ACCESS_KEY_ID

$ unset AWS_SECRET_ACCESS_KEY

$ dotnet run

Exception: Unable to get IAM security credentials from EC2 Instance Metadata Service.

EC2 インスタンスロールを作成

$ export AWS_ACCESS_KEY_ID="your-access-key"

$ export AWS_SECRET_ACCESS_KEY="your-secret-key"

$ aws iam create-role \
  --role-name mnrst-test-ec2-role \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  }'

$ aws iam attach-role-policy \
  --role-name mnrst-test-ec2-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

$ aws iam create-instance-profile \
  --instance-profile-name mnrst-test-instance-profile

$ aws iam add-role-to-instance-profile \
  --instance-profile-name mnrst-test-instance-profile \
  --role-name mnrst-test-ec2-role

EC2 インスタンスを作成

$ aws ec2 run-instances \
  --image-id ami-023ff3d4ab11b2525 \
  --instance-type t2.micro \
  --iam-instance-profile Name=mnrst-test-instance-profile

別ターミナルで EC2 インスタンスにログイン

$ aws ssm start-session \
  --target i-038aff30e485e26a5 \
  --region ap-northeast-1

Amazon Linux に .NET 8 SDK をインストール

$ sudo su - ec2-user

$ sudo yum install -y dotnet-sdk-8.0

EC2 内に検証用アプリケーションを作成

$ dotnet new console -o awss3test --use-program-main

$ cd awss3test

$ dotnet add package AWSSDK.S3

$ cat <<EOF > Program.cs
using System;
using System.Threading.Tasks;
using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;

class Program
{
    static async Task Main(string[] args)
    {
        string bucketName = "mnrst-test-bucket";

        try
        {
            IAmazonS3 s3Client = new AmazonS3Client(RegionEndpoint.USEast1);
            ListObjectsResponse response = await s3Client.ListObjectsAsync(new ListObjectsRequest
            {
                BucketName = bucketName,
                MaxKeys = 10
            });
            foreach (S3Object entry in response.S3Objects)
            {
                Console.WriteLine($"Key: {entry.Key}");
            }
        }
        catch (Exception e)
        {
            Console.WriteLine($"Exception: {e.Message}");
        }
    }
}
EOF

EC2 内でアプリケーションの動作確認

$ dotnet run

Exception: User: arn:aws:sts::123456789012:assumed-role/mnrst-test-ec2-role/i-038aff30e485e26a5 is not authorized to perform: s3:ListBucket on resource: "arn:aws:s3:::mnrst-test-bucket" because no identity-based policy allows the s3:ListBucket action

EC2 インスタンスロールに S3 権限を付与

$ aws iam put-role-policy \
  --role-name mnrst-test-ec2-role \
  --policy-name ListBucketPolicy \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": "s3:ListBucket",
        "Resource": "arn:aws:s3:::mnrst-test-bucket"
      }
    ]
  }'

もう一度 EC2 内でアプリケーションの動作確認

$ dotnet run

Key: test.txt

(おまけ)Lambda の Python で動作確認

import json
import boto3

print('Loading function')


def lambda_handler(event, context):
    s3 = boto3.client('s3')

    bucket_name = 'mnrst-test-bucket'

    try:
        response = s3.list_objects_v2(Bucket=bucket_name)
        if 'Contents' in response:
            for obj in response['Contents']:
                print(f"Object Key: {obj['Key']}")
    except Exception as e:
        print(f"Error: {e}")

    return "Done"
Test Event Name
test

Response
"Done"

Function Logs
Loading function
START RequestId: 338b53b9-025b-40fd-b815-c9a1d28c1212 Version: $LATEST
Object Key: test.txt
END RequestId: 338b53b9-025b-40fd-b815-c9a1d28c1212
REPORT RequestId: 338b53b9-025b-40fd-b815-c9a1d28c1212	Duration: 2880.13 ms	Billed Duration: 2881 ms	Memory Size: 128 MB	Max Memory Used: 80 MB	Init Duration: 334.51 ms

Request ID
338b53b9-025b-40fd-b815-c9a1d28c1212
タグ: , ,