読者です 読者をやめる 読者になる 読者になる

クラウドワークス エンジニアブログ

日本最大級のクラウドソーシング「クラウドワークス」の開発の裏側をお届けするエンジニアブログ

Amazon S3で限定したIPアドレスに公開する静的サイトをつくる

f:id:koichiroo:20160112045333p:plain Amazon S3のバケット(Bucket)に、アクセス元IPアドレスによるアクセスコントロールをかけられる、ってご存知でしたか?

この記事では、AWSやS3のリクエスト認証の概要と、それを踏まえた「アクセス元IPアドレスによるバケットへのアクセスコントロール」の方法、その応用例を紹介します。

バケットに入れるオブジェクトの機密性によっては、デフォルトの 「認証情報をつける」以外の方法でアクセスコントロールができると便利です。 例えば、機密でもなんでもないファイルや静的サイトを、ロケーション的に同じオフィスにいる人にゆるく共有したい、 くらいの用途であれば、アクセス元IPアドレスが使えることがあります。

実際、弊社でも、このエンジニアブログを公開前にテスト・レビューするために、 アクセス元IPによる読み取り制限をかけたバケットにデプロイしています。

なお、情報セキュリティに関わることなので、利用箇所には注意してください!!

S3リソースへのアクセス方法

Amazon S3で作成したバケットやオブジェクトにはHTTPでアクセスできます。

例えば、

  1. AWS SDKやREST APIでアクセスして、アプリのファイルストレージとして使う
  2. S3が払い出すオブジェクトのURLを共有して、ファイル共有に使う
  3. Static Website Hostingを有効化して、独自ドメインを割り当てて静的サイトとして公開、Webブラウザからアクセスする

いずれのケースでも、バケットやオブジェクトにアクセスするときはHTTPを話します。

AWSのリクエスト認証

S3を含むAWSリソースにアクセスするときは、基本的に、HTTPリクエストに認証情報を含めておかないとアクセス拒否されます。 初期状態では「AWSのルートアカウント」や「バケットを作成したIAMユーザ^1 に対してフルアクセスの権限がついているので、 そのどちらかの認証情報をつければ読み書き等の操作が行えます。

認証情報の内容は、2014年8月現在は「Authenticating Requests (AWS Signature Version 4)」の通りです。 具体的には、コンテンツとシークレットアクセスキーなどを元にした署名を含むAuthorizationヘッダをHTTPリクエストに含めたりします。

試しに、バケットを作成して、適当なファイルを設置して、curlやブラウザで該当するS3オブジェクトのURLにアクセスしてみると、403 FORBIDDENになりますね。

AWS SDK、S3に対応したファイル転送ソフト、ブラウザ拡張などは、アクセスキーID・シークレットキー等を元に認証情報を計算してくれています。 「認証情報」を計算できないブラウザ等から普通にアクセスすると、拒否されます。

この記事の趣旨は、特定のIPアドレスからは認証情報なしでアクセスできるようにすることです。 そうすると、「認証情報」を計算できないブラウザからも普通にアクセスできるようになります。

IPアドレスによるアクセス制限の利用イメージ

例として、「機密情報を含まない静的Webサイトを共有してフィードバックを受けたい」場合の利用イメージを考えてみましょう。

対象サイトは機密情報を含まないので、

  • 情報セキュリティ的には全公開してしまって問題ない ^2
  • しかし、今の段階ではお目汚しになるかもしれないのでユーザに見せたくない
  • 権限がある人だけは普段使っているブラウザで見られるようにしたい

と判断したとします。

こういった目的でアクセスコントロールをするなら、アクセス元IPアドレスによるアクセス制限が使えます。

IPアドレスによるアクセス制限は、AWS Consoleから簡単にかけられます。

BEFORE: アクセス拒否されることのテスト

まず、バケットに変更を加える前に、まずこの記事でするIPアドレスによるアクセスコントロールを行っていない状態ではどうなるのか、確認しておきましょう。 変更後に、また同じ手順でアクセスができることを確認します。

Amazon S3のバケットに保存されたオブジェクトには、以下のようなURLでアクセスできます。

$ curl https://s3-ap-northeast-1.amazonaws.com/<bucket-name>/<object-name>

認証情報を含まないHTTP GETリクエストを送ってみると、以下のように403 FORBIDDENです。

$ curl https://s3-ap-northeast-1.amazonaws.com/<bucket-name>/<object-name> -w "\n\nStatus: %{http_code}\n"
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>9BC0BE23385EBA9F</RequestId><HostId>wMa4ISfdmJkRtWepZUiPIuSH8A3NebIqfR0jjFAwhN1svtT4FF72/DdPKED+rdgm</HostId></Error>

Status: 403

前述のとおり、Amazon S3でバケットを作成すると、初期状態ではルートアカウントやバケットを作成したIAMユーザにフルアクセスの権限がついているはずです。 したがって、このように403 FORBIDDENとなるのが正しい挙動です。

セキュアでいいですね。 しかし、用途によってはもっと緩いアクセス制限にしたい、その一つの方法としてアクセス元IPアドレスを使う、というのがこの記事の趣旨です。

バケットポリシーの設定

アクセス元IPアドレスによるアクセスコントロールを行うためには、バケットポリシー(Bucket Policy)を変更します。

バケットポリシーは、S3リソースに誰がアクセスできるかというルールを、 Access Policy LanguageというJSONベースの言語で記述したものです。

バケットポリシーの詳細については S3のドキュメント "Using Bucket Policies and User Policies" を参照してください。

AWSコンソールで該当のBucketのPropertiesを開きます。 「Add bucket policy」をクリックします。

<%= image_tag 'aws-s3-bucket-policy.png' %>

開いたダイアログに以下のJSONを入力して、Saveをクリックします。 JSON中の<UNIQUE_ID><IP/CIDR>はそれぞれユニークなIDとIPアドレス/32のようなIPアドレス/CIDRブロックに変更してください。

このバケットポリシーに登場する要素はそれぞれ以下の意味があります。

要素名(JSONPathで) 説明
/Statement/ バケットポリシーの構成要素です。最低限、誰が(Principal)、どのAWSリソース(Resource)に対して、何をしようとしたとき(Action)、それを許可/拒否するか(Effect)という情報を含みます。
/Statement/Condition/ Statementが適用される条件です。Condition中では様々な条件分岐を書くことができます。例えば、S3の場合、「オブジェクトキー
/Statement/Condition/IpAddress/aws:SourceIp アクセス許可(ホワイトリスト)
/Statement/Condition/NotIpAddress/aws:SourceIp アクセス拒否(ブラックリスト)

<% content_for :scripts do %> <% end %>

Access Policy Languageの詳細については、 S3のドキュメント"Access Policy Language Overview"を参照してください。

AFTER: アクセス許可されることのテスト

以下のように認証情報なしでHTTP GETしたとき、アクセス許可されているIPアドレスからなら200 OK、それ以外なら403 FORBIDDENになれば成功です。 ブラウザからアクセスした場合も同様ですね。

$ curl https://s3-ap-northeast-1.amazonaws.com/<bucket-name>/<object-name>`

保守的なバケットポリシー

なお、紹介したバケットポリシーは念のため保守的に、 「デフォルトが拒否・許可にかかわらず、指定したIPアドレス/CIDRブロック内ならアクセス許可、それ以外なら拒否」となるようにしています。

わざわざ保守的にしている理由は、皆さんの環境でデフォルト許可・拒否のどちらになっていたとしても、 アクセス元IPアドレスでアクセスコントロールが効くようにするためです。

実は、今回変更するバケットポリシー以外にも、IAMポリシーやバケットのPermissionsの組み合わせ次第で、 かなり柔軟にS3へのアクセスコントロールを行うができます。

「デフォルトで拒否」になっていれば、 バケットポリシーでは「許可するIPアドレス/CIDRブロックのリスト(=ホワイトリスト)」を設定するだけでOKです。

または「デフォルトで許可」になっていれば、 バケットポリシーでは「拒否するIPアドレス/CIDRブロックのリスト(=ブラックリスト)」を設定するだけでOKです。

例えば、以下のようにバケットに対してEveryoneユーザへのOpen/Download権限がついていると、 認証不要でだれでもHTTP GETでアクセスできてしまいます。つまり、「デフォルト許可」の状態です。 言われてみれば当たり前の話だとは思いますが、この状態でホワイトリストの設定をしても、認証不要でだれでもアクセスできることに変わりはありません。 保守的なバケットポリシーにしておけば、この状態でもアクセス元IPアドレスによるアクセスコントロールが有効になります。 個人的には、全公開にする予定がないなら、EveryoneへのPermissionは削除してしまいます。

<%= image_tag 's3-remove-everyones-permission.png' %>

皆さんの環境次第で、ホワイトリストやブラックリストのどちらかだけを利用していただいてもOKですし、 間違いがないようにこのまま保守的な設定にしておいても良いでしょう。

応用例: アクセス元IPで閲覧許可、ユーザ名で更新許可

このバケットポリシーの応用は色々考えられますが、例えば

  • 特定のIAMユーザが更新可能
  • あるアクセス元IPアドレスからは閲覧可能
  • その他はアクセス拒否

といった設定ができます。

実際、このブログのテスト環境では、アクセス元IPアドレスとIAMユーザによるアクセスコントロールを行っています。

このブログはMiddlemanでビルドされた静的サイトで、テスト環境はStatic Website Hostingを有効化したS3上のバケットです。 公開前の記事・デザインの修正などは、CIサービスであるWerckerによってバケットへデプロイされ、社内のメンバーによってレビューされます。

このフローに関わるWerckerと社内メンバーに対してのみ必要十分な権限を与えるために、バケットには以下のようなアクセスコントロールをかけています。

  • WerckerからS3へのデプロイを許可 (Wercker用のIAMユーザを作成して、そのユーザからS3へのファイルアップロード許可)
  • 社内からは認証情報なしで閲覧許可
  • 社外からのアクセスは拒否

これを実現するバケットポリシーは以下のとおりです。

このバケットポリシーのポイントは、Principal・NotPrincipalを活用しているところです。

前提として、Werckerがバケットにアクセスするときのアクセス元IPアドレスは弊社にとっては社外です。 また、バケットポリシーのStatementはAllowよりDenyが優先されます。

その結果としてどうなるかというと、以下のようにアクセス元IPアドレスが対象範囲外のユーザに対してすべてDenyしたとき、後続のStatementでWerckerからのアクセスをどのようにAllowしたとしても、結果はDenyになります。

"Effect": "Deny", "Principal": { "AWS": "*" },

そこで、以下のようにアクセス元IAMユーザがWerckerの場合以外の時だけDenyが適用されるようにしつつ、 WerckerからのアクセスをAllowしています。

"Effect": "Deny", "NotPrincipal": { "AWS": "arn:aws:iam::アカウントID:user/Wercker向けIAMユーザ名" },

NotPrincipal等の意味も含めて、Access Policy Languageの詳細については、 S3のドキュメント"Access Policy Language Overview"に詳しく書かれています。

まとめ

この記事では、AWSやS3のリクエスト認証の概要と、Amazon S3のBucketにアクセス元IPアドレスによるアクセスコントロールを行う方法、そして応用例としてこのブログでの利用方法を紹介しました。 機密性がない情報を、ロケーション的に近い人に共有する、というようなゆるいアクセスコントロールが必要な場合に検討してみてください!

© 2016 CrowdWorks, Inc., All rights reserved.