akiyoko blog

akiyoko の IT技術系ブログです

Boto を使って S3 にアップロードしたファイルを取得する

1. はじめに

Python のプログラムコードから(S3 や CloudFront などの)AWS のサービスを利用する場合には、Boto(あるいは次期バージョンの Boto3)を使います。


Boto
boto: A Python interface to Amazon Web Services — boto v2.38.0

Boto3
AWS SDK for Python | アマゾン ウェブ サービス(AWS 日本語)



今回は、Boto を使って、S3上に格納されたオブジェクト(ファイル)を取得する方法について書いておきたいと思います。

なお、S3 へのアクセスは、特定の IAMユーザだけに GetObject/ListBucket を許可するように Bucket Policy でアクセス制限を行います。



ところでいつも悩むのですが、「Boto」の発音って 「ボトゥー」 でいいのでしょうか?? あるいは「ブートゥー」?「ボトー」?「ボートー」? いろいろググったのですが、結局分かりませんでした。どなたか詳しい方、教えてくださいまし。




2. IAMユーザを作成

アクセスキー(アクセスキーID およびシークレットアクセスキー)は、AWSルートアカウントで作成してしまうと漏洩してしまったときのインパクトが大きすぎるので、ルートアカウントは極力利用しないという方針のもと、「Admin」という名前の管理者用ユーザを作成して、そのユーザに対してアクセスキーを発行することとします。


以降、ルートアカウントでの操作です。


IAM のダッシュボードから、[Users] -> [Create New Users] を選択し、ユーザ名「Admin」を入力して「Create」ボタンをクリックし、新規の IAMユーザーを作成します。

f:id:akiyoko:20150812021905p:plain


Users 一覧からユーザ名「Admin」を選択して、ユーザの詳細情報ページに移動します。
まずは、「User ARN」を確認しておきます。この User ARN (Amazon Resource Name) は、S3 の Bucket Policy でアクセスを許可する IAMユーザを特定するために使用します。

f:id:akiyoko:20150813005145p:plain

ちなみに、IAM の User ARN の形式は以下の通りです。

arn:aws:{service}:{region}:{account}:{resource}

{service} : IAMリソースの場合は「iam」
{region} : IAMリソースの場合は空白
{account} : ハイフンなしの AWS Account ID(AWSアカウントに対して1つ割り当てられる12桁の数字)
{resouce} : IAMユーザやロールなどで識別する部分(今回の場合は「user/Admin」)

参考
IAM ID - AWS Identity and Access Management



次に、
[Security Credentials] -> [Access Keys] -> [Create Access Key] をクリックして、アクセスキーを作成します。このアクセスキーは、Boto のプログラム内で S3 への認証・認可を行うのに使用します。

f:id:akiyoko:20150812022148p:plain


ここで、Adminユーザに AWS 管理ポリシーをアタッチして、完全な管理者アクセス権限(AdministratorAccess)を与えます。*1
f:id:akiyoko:20150922195029p:plain

f:id:akiyoko:20150923024916p:plain

実際のポリシーはこのようになります。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    }
  ]
}


最後に、
[Security Credentials] -> [Sign-In Credentials] -> [Manage Password] をクリックして、IAMユーザにパスワードを付けます。

f:id:akiyoko:20150812022317p:plain
f:id:akiyoko:20150812022327p:plain


一旦ログアウトし、
https://{AWS Account ID}.signin.aws.amazon.com/console
から、Adminユーザの新パスワードでログインできるかを確認しておきます。


なお、「AWS Account ID」は、AWSアカウントに紐づく 12桁の数字で、My Account ページの [Accout Settings] からも確認できます。
f:id:akiyoko:20150813011624p:plain





3. Bucket Policy を設定

S3 の Bucket Policy に、Adminユーザに対して ListBucket および GetObject を許可するポリシーを追加します。


Bucket Policy の構成要素については、以下のサイトで非常に分かりやすく説明されています。中でも、「Principal」「Resource」「Action」の書き方がポイントになります。

構成要素

誰(Principal)が、とある条件下(Condition)で、リソースに対し(Resource)、操作(Action)を行うことを制限(Effect)可能。

要素 説明
Version アクセスポリシーのバージョン。現在は唯一 ”2008-10-17” のみ有効
Id ポリシーオプションの識別子。UUID を使用、または組み込むことを推奨
Statement 下記の個々の要素を配列で持つ。JSON のブロックで構成
Sid Statement 内で一意に決まるサブID
Effect statement が許可、または拒否かどうかを指定する必須の要素。有効値:"Allow", “Deny”
Principal 設定したポリシーを受ける対象
Action 操作を許可する S3 の API について指定(例:s3:GetObject )
NotAction 唯一の操作を許可する S3 の API について指定。指定した API 以外は拒否
Resource ポリシーを指定するオブジェクトを指定
Condition 要素の内部について指定できる詳細な条件


Policies · hamajyotan/castoro-s3-adapter Wiki · GitHub」より引用


その他の参考サイト



Bucket Policy をいちいち手で書くのは面倒なので、AWS Policy Generator を使って作成します。

http://awspolicygen.s3.amazonaws.com/policygen.html
f:id:akiyoko:20150813015219p:plain


なお、S3 の Resource は、「arn:aws:s3:::{S3バケット名}」という形式で指定可能です。


ListBucket の Policy
{
  "Id": "Policy1439041728317",
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1439041726404",
      "Action": [
        "s3:ListBucket"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::app1-transcoder-out",
      "Principal": {
        "AWS": [
          "arn:aws:iam::xxxxxxxxxxxx:user/Admin"
        ]
      }
    }
  ]
}
GetObject の Policy
{
  "Id": "Policy1439040499430",
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1439040494019",
      "Action": [
        "s3:GetObject"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::app1-transcoder-out/*",
      "Principal": {
        "AWS": [
          "arn:aws:iam::xxxxxxxxxxxx:user/Admin"
        ]
      }
    }
  ]
}


 
Statement の中のブロックをそれぞれコピペします。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1439041726404",
      "Action": [
        "s3:ListBucket"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::app1-transcoder-out",
      "Principal": {
        "AWS": [
          "arn:aws:iam::xxxxxxxxxxxx:user/Admin"
        ]
      }
    },
    {
      "Sid": "Stmt1439040494019",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::xxxxxxxxxxxx:user/Admin"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::app1-transcoder-out/*"
    }
  ]
}

f:id:akiyoko:20150813014535p:plain

 

4. Boto のインストール

pip でインストールします。

$ pip install boto
$ pip list | grep boto
boto (2.38.0)


 

5. Boto を使った Python コード

「app1-transcoder-out」バケットが、

app1-transcoder-out/
 └─HLS/
   └─1M/
     └─D0002021500_00000/
        └─sample.m3u8

のようなディレクトリ構造になっていると仮定し、ファイル「sample.m3u8」をインメモリのファイルオブジェクトとして取得するソースコードを書いてみます。



test.py

#!/usr/bin/python

from cStringIO import StringIO
from boto import connect_s3

ACCESS_KEY_ID = '<YOUR ACCESS KEY ID>'
SECRET_ACCESS_KEY = '<YOUR SECRET ACCESS KEY>'
BUCKET_NAME = 'app1-transcoder-out'
KEY_NAME = 'HLS/1M/D0002021500_00000/sample.m3u8'


def main():
    conn = connect_s3(ACCESS_KEY_ID, SECRET_ACCESS_KEY)
    bucket = conn.get_bucket(BUCKET_NAME)
    key = bucket.get_key(KEY_NAME)

    if key is None:
        raise Exception("No such key was found. key={}".format(key))

    fp = StringIO()
    key.get_contents_to_file(fp)
    fp.seek(0)

    print fp.getvalue()
    fp.close()


if __name__ == '__main__':
    main()
参考


実行結果
$ python test.py
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:13
#EXTINF:12.078744,
sample00000.ts
#EXTINF:9.009011,
sample00001.ts
#EXTINF:9.009011,
sample00002.ts
#EXTINF:12.012011,
sample00003.ts
#EXTINF:9.009011,
sample00004.ts
#EXTINF:2.068733,
sample00005.ts
#EXT-X-ENDLIST



ちなみに、Bucket Policy の設定ミスなどでパーミッションエラーが発生した場合は、以下のような実行結果になります。

$ python test.py 
Traceback (most recent call last):
  File "test.py", line 33, in <module>
    main()
  File "test.py", line 18, in test
    bucket = conn.get_bucket(BUCKET_NAME)
  File "/opt/webapps/venv/local/lib/python2.7/site-packages/boto/s3/connection.py", line 502, in get_bucket
    return self.head_bucket(bucket_name, headers=headers)
  File "/opt/webapps/venv/local/lib/python2.7/site-packages/boto/s3/connection.py", line 535, in head_bucket
    raise err
boto.exception.S3ResponseError: S3ResponseError: 403 Forbidden

 

まとめ

特定のIAMユーザにだけ Read Only でアクセスを許可した S3バケット内のオブジェクトを、Boto で取得する方法について書きました。Boto から S3への認証・認可は、アクセスを許可した IAMユーザのアクセスキー(アクセスキーID およびシークレットアクセスキー)を使います。

Bucket Policy の書き方には慣れが必要ですが、ACL で制限するよりも柔軟なアクセス制限を実現することができる上、ジェネレータやバリデータも充実しているため、より使いやすいという側面もあります。ただし、Bucket Policy で記述できる JSON のサイズに制限があるのが問題となるケースもレアながらあるようです。

*1: 今回の手順としては必須の設定ではありませんが、Adminユーザのアクセスキーを使用して AWS CLI を利用する場合などで必要となります。