アクセスキーなしでEC2からS3を使ってみる

S3を利用する方法として、クレデンシャルのアクセスキーとシークレットアクセスキーをサーバーに保存して使用する方法もありますが、万が一キーが漏洩してしまうと大変です。そのため、AWSではIAMロールを利用する方法を推奨しています。ここではその利用手順をまとめました。

動作環境

ホストOSWindows 11 Home 22H2(22621.1105) 64bit
ブラウザChrome 109.0.5414.121
GoLang1.18.1
aws-sdk-go-v2config  :v1.18.12
service/s3:v1.30.2

手順

EC2に仮想サーバーを構築

ここを参考に進めてみてください。パブリックサブネットだけあれば十分です。SSH接続で操作するため、忘れずに設定して下さい。

S3バケットを作成

ここを参考に進めてみてください。
下記ファイルをアップロードしておきました。

#download.txt

download.txtの内容

S3操作用のIAMロールを作成

S3に対して下記ができるポリシーを作成します。
 ・バケットの一覧表示(ListAllMyBuckets)
 ・バケット内のオブジェクト一覧表示(ListBucket)
 ・オブジェクトのアップロード(Putobject)
 ・オブジェクトのダウンロード(GetObject)

前述の手順で作成したバケット名を入力します。

ポリシーを紐づけるロールを作成します。

文字が小さいですが、先ほど作成したポリシーを選択しています。

EC2にIAMロールをセットする

最初に構築したEC2インスタンスにIAMロールを紐づけます。

ここで少し細かい話をすると、実際に紐づけるのはロールではなくインスタンスプロファイルというものです。ロールの作成と同時に同名で作成されています。選択肢のパスを見ると、『Instance-profile』と書いてあるのが分かります。

EC2からS3を操作する

EC2インスタンスにSSH接続して実際に操作してみます。ここではプログラム言語としてGoLangを使って進めていきます。GoLangとライブラリの準備を進めます。

# /home/ubuntu

sudo apt update
sudo wget https://go.dev/dl/go1.20.linux-amd64.tar.gz
sudo tar -C /usr/local -xvf go1.20.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
source .profile
mkdir app
cd app
go mod init sample
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/s3
go get golang.org/x/text/encoding/japanese
go get golang.org/x/text/transform
touch upload.txt #ファイルのアップロードテスト用
touch main.go
package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/transform"
)

func main() {
	// リージョンを環境変数に設定
	region, err := exec.Command("sh", "-c", "curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//g'").Output()
	if err != nil {
		fmt.Printf("command error:%v", err)
	}
	os.Setenv("AWS_DEFAULT_REGION", string(region))
	// AWSのコンフィグ情報取得
	cfg, err := config.LoadDefaultConfig(context.TODO())
	if err != nil {
		fmt.Printf("config get error:%v", err)
		return
	}
	fmt.Printf("region:%v\n", cfg.Region)
	// s3接続
	s3Client := s3.NewFromConfig(cfg)
	// バケットを一覧表示
	buckets, err := s3Client.ListBuckets(context.TODO(), &s3.ListBucketsInput{})
	if err != nil {
		fmt.Printf("backet display error:%v", err)
		return
	}
	bucketName := ""
	for _, bucket := range (*buckets).Buckets {
		fmt.Printf("Backetname:%s\n", *bucket.Name)
		bucketName = *bucket.Name
	}
	// バケットにファイルをアップロード
	f, err := os.Open("./upload.txt")
	if err != nil {
		fmt.Printf("file open error:%v", err)
		return
	}
	defer f.Close()
	_, err = s3Client.PutObject(context.TODO(), &s3.PutObjectInput{
		Body:   f,
		Bucket: aws.String(bucketName),
		Key:    aws.String("upload-key.txt"),
	})
	if err != nil {
		fmt.Printf("object upload error:%v", err)
		return
	}
	// バケット内のオブジェクトを一覧表示
	objects, err := s3Client.ListObjectsV2(context.TODO(), &s3.ListObjectsV2Input{
		Bucket: aws.String(bucketName),
	})
	if err != nil {
		fmt.Printf("object display error:%v", err)
		return
	}
	for _, object := range objects.Contents {
		fmt.Printf("ObjectName:%v \n", *object.Key)
	}
	// バケットからオブジェクトをダウンロード
	object, err := s3Client.GetObject(context.TODO(), &s3.GetObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String("download.txt"),
	})
	defer object.Body.Close()
	if err != nil {
		fmt.Printf("object download error:%v", err)
		return
	}
	body, err := ioutil.ReadAll(object.Body)
	if err != nil {
		fmt.Printf("object read error:%v", err)
		return
	}
	s, _, _ := transform.String(japanese.ShiftJIS.NewDecoder(), string(body))
	fmt.Printf("downloadText:%v\n", s)
}

実行すると、問題なければ下記のような結果が出ます。

go run *.go
----------------------------------------
region:ap-northeast-1
Backetname:s3-bucket-sample-2023-01
ObjectName:download.txt 
ObjectName:upload-key.txt 
downloadText:download.txtの内容
----------------------------------------

ただ、SSH接続だと実行結果を待たずに接続が切断されてしまうことがあるようでしたので、もしそのような事象が発生するようであれば、『EC2 Instance Connect』で接続して実行してみてください。一度実行が成功すれば、あとはSSH接続でも成功するようになります。