はじめに
AWS上に構築したシステムに、システム外の外部ホストからファイルをアップロードするような業務はよくあると思います。やっていることは単純なのですが、ちょっとした手順になりますのでまとめておきます。なお、今回は AWS SDK for Python (boto3) を使ったプログラムを紹介します。
バケットの準備
外部ホストからのアップロードファイルを受け取るバケットを作成します。
アップロードユーザーの作成
今回、s3botosampleuser というユーザーを作成して、これを外部ホスト向けに割り当てることにしましょう。AWS マネジメントコンソールのホームで「Identify & Access Management」(IAM) を探してクリックします。

左側メニューで「ユーザー」をクリックして、ボタン「新規ユーザーの追加」をクリックします。

ユーザー名として「s3botosampleuser」を入力して、ボタン「作成」をクリックします。

ユーザーが作成されると、このユーザー向けの「アクセスキーID」と「シークレットアクセスキー」が発行されます。「ユーザーのセキュリティ認証情報を表示」をクリックすると表示されますので、控えておきます。
続いて、バケットにアクセス権限を追加するときに、ユーザー「s3botosampleuser」のARN (Amazon Resource Name) も必要になりますのでここで確認しておきます。この画面を閉じて、ユーザー一覧の画面に戻ります。

ユーザー「s3botosampleuser」をクリックします。

画面右上に表示される「概要」から、「ユーザーのARN」を控えておきます。

これで、ユーザーの作成は完了です。
バケットの作成とアクセス許可設定
s3botosample というバケットを用意して、ここにアップロードしてもらうようにしましょう。AWS マネジメントコンソールのホームで「S3」を探してクリックします。S3 のバケット一覧画面が表示されますので、ボタン「バケットを作成」をクリックします。

バケット名に「s3botosample」を、リージョンは適当なものをそれぞれ入力して、ボタン「作成」をクリックします。

バケット一覧に「s3botosample」が追加されます。引き続き、アクセス許可設定を進めます。「s3botosample」をクリックしてください。

画面右上のボタン「プロパティ」をクリックしてください。バケット「s3botosample」についての情報が表示されます。セクション「アクセス許可」にある「バケットポリシーの追加」をクリックしてください。

今回はポリシージェネレーターを使って、GUIでポリシー情報を生成してみます。「AWS Policy Generator」をクリックしてください。

別ウィンドウでポリシージェネレーターが表示されます。まず、バケットの中身の一覧を表示できるように、アクション「ListBucket」を許可します。下表のとおり入力して、ボタン「Add Statement」をクリックしてください。
項目 | 値 |
Select Type of Policy | S3 BucketPolicy |
Effect | Allow |
Principal | 先ほど控えたユーザーのARN |
AWS Service | Amazon S3 |
Actions | ListBucket |
Amazon Resource Name (ARN) | arn:aws:s3:::s3botosample |

一度画面が下までスクロールして、生成されるポリシー情報が1件分表形式で表示されます。引き続き、バケットにファイルをアップロードできるようにアクション「PutObject」を許可したいので、画面を上までスクロールして2つ目のポリシー情報を入力します。下表のとおり入力して、ボタン「Add Statement」をクリックしてください。
項目 | 値 |
Select Type of Policy | S3 BucketPolicy |
Effect | Allow |
Principal | 先ほど控えたユーザーのARN |
AWS Service | Amazon S3 |
Actions | PutObject |
Amazon Resource Name (ARN) | arn:aws:s3:::s3botosample/* |
最後の項目「Amazon Resource Name (ARN)」の末尾に注意してください。
アクション「ListBucket」はバケット自身に対するポリシーなので末尾に「/」が付きませんが、アクション「PutObject」はパケットの中身であるObject(ファイルやディレクトリ)に対するポリシーとなりますので、末尾に「/*」が必要になります。

2つのアクションについてポリシー情報を作成できたら、ボタン「Fenerate Policy」をクリックしてください。

先ほど入力した2つのポリシー情報がJSONベースのテキストに変換されて表示されますので、これをコピーします。S3 バケットの詳細情報の画面(ポリシージェネレーターを表示する前の画面)に戻り、この内容を貼り付けて保存してください。

下記は生成されたJSONベースのテキストのサンプルです。
{
"Version": "2012-10-17",
"Id": "PolicyXXXXXXXXXXXXX",
"Statement": [
{
"Sid": "StmtXXXXXXXXXXXXX",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::XXXXXXXXXXXX:user/s3botosampleuser"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::s3botosample"
},
{
"Sid": "StmtXXXXXXXXXXXXX",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::XXXXXXXXXXXX:user/s3botosampleuser"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::s3botosample/*"
}
]
}
これでバケットの作成と、アクセス許可設定が完了しました。
アップロード元の環境整備
今回、ファイルのアップロードには AWS SDK for Python (boto3) を使います。botoCentOS7 + Python3.5 の環境でも動作しました。
(venv35)$ pip install boto3
アクセスキーのインストール
控えておいた、ユーザー s3botosample 向けの「アクセスキーID」と「シークレットアクセスキー」をホストにインストールします。boto3 をインポートすると自動的に適用されるため、Pythonプログラム中に「アクセスキーID」と「シークレットアクセスキー」を記述する必要がなくなります。
(venv35)$ mkdir -p ~/.aws
~/.aws/credentials
[default]
aws_access_key_id = <アクセスキーID>
aws_secret_access_key = <シークレットアクセスキー>
「アクセスキーID」と「シークレットアクセスキー」はダブルクオートなどで囲まず、そのまま記述します。
アップロードプログラムの作成と実行
単純にするため、カレントディレクトリにプログラムファイルを作成します。同じくカレントディレクトリにファイル「sample.txt」があり、これをアップロードするようなプログラムを作成しました。
今回、ファイルの一覧取得(ListBucket)とファイルのアップロード(PutObject)のみ許可するポリシーを適用しています。ファイルをダウンロードできないこと、ファイルを削除できないことも確認しました。
【リファレンス】S3 — Boto 3 Docs 1.3.1 documentation
s3botosample.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import boto3
s3 = boto3.resource('s3')
client = s3.Bucket('s3botosample')
print('01. show list objects in bucket.')
for obj in client.objects.limit(10):
print(obj.Object)
print('02. upload file.')
#client.upload_file('sample.txt', 'sample/sample-uploaded.txt')
with open('sample.txt', 'rb') as f:
result = client.put_object(Key='sample/sample-uploaded.txt', Body=f)
print(result)
print('03. show list objects in bucket again.')
for obj in client.objects.limit(10):
print(obj.Object)
print('04. try to download file.')
try:
client.download_file('sample/sample-uploaded.txt', 'sample-downloaded.txt')
except Exception as e:
print(e)
print('05. try to delete file.')
delete_query = {
'Objects': [{
'Key': 'sample/sample-uploaded.txt'
}]
}
response = client.delete_objects(Delete=delete_query)
print(response)
このプログラムではS3向けの「ハイレベルクライアント」を使用しています。より細かい操作が可能な「ローレベルクライアント」は client = boto3.client(‘s3’) とすると利用できるようになります。
ファイルのアップロードには client.upload_file(‘sample.txt’, ‘sample/sample-uploaded.txt’) と記述しても構わないのですが、成功時にレスポンスがないため、client.put_object(Key=’sample/sample-uploaded.txt’, Body=f) を使用しています。
S3のバケットでは、実はファイルやディレクトリという概念はなく、すべてオブジェクトとして扱われます。このプログラムではバケット直下にsampleというディレクトリのようなものをmkdirなどせずに指定してアップロードしていますが、S3のバケットからしてみればオブジェクト「sample/sample-uploaded.txt」が来るんだな、という感じです。
アップロード実行
作成したプログラムを実行すると、下記のような結果になりました。
(venv35)$ python s3botosample.py
01. show list objects in bucket.
02. upload file.
s3.Object(bucket_name='s3botosample', key='sample/sample-uploaded.txt')
03. show list objects in bucket again.
.create_resource of s3.ObjectSummary(bucket_name='s3botosample', key='sample/sample-uploaded.txt')>
04. try to download file.
An error occurred (403) when calling the HeadObject operation: Forbidden
05. try to delete file.
{'Errors': [{'Key': 'sample/sample-uploaded.txt', 'Message': 'Access Denied', 'Code': 'AccessDenied'}], 'ResponseMetadata': {'RequestId': 'F09B8B2FC7FE5F8C', 'HostId': '5cI109DInfkOSCm+R6ewEF2IfHZp7m/+FMGKOKDlASI3mzza25tJIuQLJLOqXXJzXdXpAOPPKXw=', 'HTTPStatusCode': 200}}
01. ではバケットにはまだ何もないので出力はありません。
02. でオブジェクトが帰ってきており、ファイルのアップロードに成功しています。
03. ではいまアップロードしたファイルがオブジェクトとして取得できています。
04. ダウンロードを許可していないため、403エラー(権限なしエラー)になっています。
05. 削除も許可していないため、こちらはエラーレスポンスがオブジェクトとして帰ってきています。
おわりに
AWS SDKを使うと、AWSの外にあるホストでも簡単にファイルを転送できます。バッチ処理結果の共有場所、バックアップアーカイブの保存場所、あるいはCMSのパブリッシュ結果のデプロイ先にしたWebサイトの運用など、いろいろな用途に使えそうですね!