Amazon S3のREST APIを使ってみる
最近Amazon Web Services(AWS)でちょこちょこ遊んでます。今のところSimple Storage Service(S3)とGlacierぐらいですが。そこでPythonから使ってみようと思い、いろいろ試しているのでその記録。
この記事内のコード片は、パブリックドメインということにしておきますので、使用される方の責任でご自由にお使いください。
準備
AWSを使うに当たってはアカウント作成およびS3の使用開始登録、access key、secret access keyの取得が必要になります。その作業に関しては解説サイトも多数存在するようですのでそちらを参照して下さい。
概要
AWSへをPythonから利用するライブラリとしてはboto(http://docs.pythonboto.org/)がありますが、あらゆるサービスに対応している反面、かなり巨大なものになっています。私のようにS3ぐらいしか使わないライトユーザーはそこまでいらないので、基本を学ぶことも含めてアクセス用モジュールを作成してみました。
AWSに対するリクエスト
AWSに対するリクエストはAWS access key、シグネチャ、タイムスタンプの組み合わせにより認証されます。このHMACのシグネチャベースの認証はTwitterなどで用いられているOAuthに似ていますね。ともかく、HTTPメソッドやヘッダの組み合わせをsecret access keyを使ってシグネチャを作成することで認証を行う仕組みです。つまり、自分とAWSしかsecret access keyを知らないため、シグネチャの検証により認証されるようです。
実装はPHPでAmazon S3のREST APIを使用 #1とAmazon S3 Developer Guideを参考にしました。Amazon S3 Developer GuideとAPI Referenceは例が豊富に載っており非常にわかりやすいです。
Pythonによる実装
実例
前出のAmazon S3 Developer GuideのMaking Requests using the REST APIの例をここに示します。
DELETE /puppy.jpg HTTP/1.1 User-Agent: dotnet Host: mybucket.s3.amazonaws.com Date: Tue, 15 Jan 2008 21:20:27 +0000 x-amz-date: Tue, 15 Jan 2008 21:20:27 +0000 Authorization: AWS AKIAIOSFODNN7EXAMPLE:k3nL7gH3+PadhTEVn5EXAMPLE
このようなリクエストを発行します。各々はとりあえず無視して認証で使われるAuthorizationヘッダを中心に実装を解説してみます。
実装
前置きが長くなりましたが、まずはS3に対する簡単なリクエスト(Bucketの一覧取得)を実行してみます。このリクエストは使用しているリージョンに対して"/"をGETすることでXMLで書かれたBucketの一覧が取得するというものです。
シグネチャの基本と実装|
AuthorizationヘッダはAWS Access key:Signatureという組み合わせになっており、ここのSignatureを求める手順を示します。シグネチャは必要な内容をつなぎ合わせた文字列に対してsecret access keyを使って署名することで作成することができます。署名対象のヘッダは以下です。
名称 | 内容 | 例 |
HTTP-Verb | HTTPメソッド | GET |
---|---|---|
Content-MD5 | 送信データのMD5のBase64エンコード文字列 検証が不要なら空文字列でよい | |
Content-Type | Content-Typeヘッダ。データを送信する際に付ける GETの場合は空文字列 | |
Date | RFC1123形式のタイムスタンプ文字列(UTC) | Fri, 16 Nov 2012 21:20:27 +0000 |
CanonicalizedAmzHeaders | x-amz-で始まるヘッダ内容 | |
CanonicalizedResource | リソース文字列 | / |
これらを改行(\n)でつなぎ合わせてsecret access keyを使ってHMAC-SHA1による署名をします。コードは以下のようになります。
なお、ACCESS_KEYおよびSECRET_KEYはDeveloper Guide内の値を使っており、実際の認証には使えません。
#!/usr/bin/env python from datetime import datetime import hmac, hashlib, base64 ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE" SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" def get_authorization(method, resource): http_verb = method content_md5 = "" content_type = "" date = datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000") amz_headers = "" resource = resource str_to_sign = "\n".join([http_verb, content_md5, content_type, date, amz_headers, resource]) signature = base64.b64encode(hmac.new(SECRET_KEY, str_to_sign, hashlib.sha1).digest()) authorization = "AWS %s:%s" % (ACCESS_KEY, signature) return authorization authorization_str = get_authorization("GET", "/") print authorization_str
実行するとこんな感じ
% ./step1.py
AWS AKIAIOSFODNN7EXAMPLE:nNUgLAHZKX8fLjg7EiueU0Q71v4=
このAWSから始まる文字列をAuthorizationヘッダに使用します。
リクエストを実行する
S3に対するリクエストはVirual hosted-styleとpath-styleがあるようですが、ここではpath-styleを使用しています。また、東京リージョン(s3-ap-northeast-1)を接続先にしています。必要であれば変更して下さい。 実際のリクエストを作成してみます。リクエストヘッダなどをいじるのでurllib2モジュールを使います。
#!/usr/bin/env python from datetime import datetime import hmac, hashlib, base64, urllib2 ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE" SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" BASEURL = "https://s3-ap-northeast-1.amazonaws.com" def authorize(req, access_key, secret_key): http_verb = req.get_method() content_md5 = req.get_header("Content-md5", "") content_type = req.get_header("Content-type", "") date = datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000") amz_headers = "" resource = req.get_selector() str_to_sign = "\n".join([http_verb, content_md5, content_type, date, amz_headers + resource]) signature = base64.b64encode(hmac.new(secret_key, str_to_sign, hashlib.sha1).digest()) authorization = "AWS %s:%s" % (access_key, signature) req.add_header("Date", date) req.add_header("Authorization", authorization) req = urllib2.Request(BASEURL + "/") authorize(req, ACCESS_KEY, SECRET_KEY) opener = urllib2.build_opener() print opener.open(req).read()
ACCESS_KEYとSECRET_KEYは適切なものを指定して実行するとこんな感じです。わかりやすいように結果のXMLには改行を入れてあります。また値は適当にダミーにしてあります。
% ./step2.py <?xml version="1.0" encoding="UTF-8"?> <ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <Owner> <ID>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</ID> <DisplayName>YourDisplayName</DisplayName> </Owner> <Buckets> <Bucket><Name>mybucket1</Name><CreationDate>2012-11-12T05:38:29.000Z</CreationDate></Bucket> <Bucket><Name>mybucket2</Name><CreationDate>2012-11-14T09:15:36.000Z</CreationDate></Bucket> </Buckets> </ListAllMyBucketsResult>
これ以降
今回はCanonicalizedAmzHeadersの処理と複雑なCanonicalizedResourceの処理は実装していませんので、詳細はDeveloper Guidなどのドキュメントを参考にして下さい。また、urllib2.RequestではHTTPメソッドにGETかPOSTしか使えない(何という仕様!)のでGET以外のリクエスト(PUT、DELETEなど)を行う場合はRequestのサブクラスを作るなどの対応が必要になります。もうちょっとましなモジュールができたらGitHubあたりで公開したいなーとか思ってます。
Glacierも操作できるようなものができたら自分的には十分満足なんですが・・・案外遠いかな(^^;
参考サイト
- 林檎生活100 PHPでAmazon S3のREST APIを使用 #1
- Amazon S3 Developer Guide
- Signing and Authenticating REST Requests - 例が多く載っている
- Amazon S3 API Reference
コメント
コメントなどありましたらお願いします。