ほのぼの Rake タスクで S3 のバケットポリシーをちょっと管理する（表示と export と diff と update するだけ）

[こちら](http://inokara.hateblo.jp/entry/2016/11/23/110251)からの転載です。

## 最近

ギョームで S3 バケットを更新する作業があったので、ほのぼの Rake タスクで汎用的に管理出来る感じにしてみました。

## タスク

### Rakefile

```ruby
require 'aws-sdk'
require 'diffy'
require 'json'

POLICY_DIR='policies/'

def s3
  Aws.config[:credentials] = Aws::SharedCredentials.new(profile_name: 'あなたのプロファイル')
  Aws::S3::Client.new(region: 'ap-northeast-1')
end

def get_bucket_policy(bucket)
  begin
    resp = s3.get_bucket_policy({
      bucket: bucket
    })
    JSON.parse(resp.policy.read)
  rescue JSON::ParserError => e
    puts e
  end
end

def open_policy_document(path)
  begin
    File.open(POLICY_DIR + path) do |file|
      JSON.load(file)
    end
  rescue JSON::ParserError => e
    puts e
  end
end

def valid_json?(json)
  begin
    JSON.parse(json)
    return true
  rescue JSON::ParserError => e
    return false
  end
end

namespace :s3 do

  namespace :policy do

    desc "S3 バケット policy を取得する"
    task :export, :bucket do |task, args|
      puts "#{args[:bucket]} の policy を取得します..."
      hash = get_bucket_policy(args[:bucket])
      puts JSON.pretty_generate(hash)
      File.open(POLICY_DIR + args[:bucket], "w") do |f| 
        f.puts(JSON.pretty_generate(hash))
      end
    end

    desc "S3 バケット policy を表示する"
    task :view, :bucket do |task, args|
      puts "#{args[:bucket]} の policy を表示します..."
      hash = get_bucket_policy(args[:bucket])
      puts JSON.pretty_generate(hash)
    end

    targets = []
    Dir.glob(POLICY_DIR + '*').each do |file|
      target = File.basename(file)
      target = "_#{target}" if target == "default"
      targets << target
    end

    targets.each do |target|
      namespace target.to_sym do
        desc "S3 バケット #{target} の policy を変更する"
        task :update do
          resp = s3.put_bucket_policy({
            bucket: "#{target}",
            policy: open_policy_document(target).to_json
          })
          puts resp
        end

        desc "S3 バケット #{target} に適用する policy と既存のポリシーを比較する"
        task :diff do
          hash = get_bucket_policy(target)
          old = JSON.pretty_generate(hash)
          new = JSON.pretty_generate(open_policy_document(target))
          puts Diffy::Diff.new(old, new).to_s(:color)
        end

      end
    end
  end
end
```

### 使い方

- 初期状態の tasks

```sh
$ bundle exec rake -T
rake s3:policy:export[bucket]          # S3 バケット policy を取得する
rake s3:policy:view[bucket]            # S3 バケット policy を表示する
```

- バケットポリシーを export しましょう

```sh
$ mkdir policies
$ bundle exec rake "s3:policy:export[hage.inokara.com]"
```

以下のように出力されます。

```sh
hage.inokara.com の policy を取得します...
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "AllowPublicRead",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::hage.inokara.com/*"
    }
  ]
}
```

デフォルトではカレントディレクトリの policies というディレクトリ以下にバケット名と同じ名前のポリシーファイルが作成されています。

```sh
bash-3.2$ ls -l policies/
total 8
-rw-r--r--  1 syorou  staff  253 Nov 23 11:58 hage.inokara.com
```

tasks も確認しましょう。

```sh
bash-3.2$ bundle exec rake -T
rake s3:policy:hage.inokara.com:diff    # S3 バケット hage.inokara.com に適用する policy と既存のポリシーを比較する
rake s3:policy:hage.inokara.com:update  # S3 バケット hage.inokara.com の policy を変更する
rake s3:policy:export[bucket]          # S3 バケット policy を取得する
rake s3:policy:view[bucket]            # S3 バケット policy を表示する
```

- バケットポリシーを修正しましょう

```sh
bash-3.2$ vim policies/hage.inokara.com
```

[ドキュメント](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/example-bucket-policies.html)を参考に IP アドレス制限をかけてみました。

```json
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "AllowPublicRead",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::hage.inokara.com/*",
      "Condition": {
         "IpAddress": {"aws:SourceIp": "54.240.143.0/24"},
         "NotIpAddress": {"aws:SourceIp": "54.240.143.188/32"}
      }
    }
  ]
}
```

- 差分を確認しましょう

```sh
bash-3.2$ bundle exec rake s3:policy:hage.inokara.com:diff
 {
   "Version": "2008-10-17",
   "Statement": [
     {
       "Sid": "AllowPublicRead",
       "Effect": "Allow",
       "Principal": {
         "AWS": "*"
       },
       "Action": "s3:GetObject",
-      "Resource": "arn:aws:s3:::hage.inokara.com/*"
+      "Resource": "arn:aws:s3:::hage.inokara.com/*",
+      "Condition": {
+        "IpAddress": {
+          "aws:SourceIp": "54.240.143.0/24"
+        },
+        "NotIpAddress": {
+          "aws:SourceIp": "54.240.143.188/32"
+        }
+      }
     }
   ]
 }
\ No newline at end of file
```

ちゃんと既存の設定との差分を表示してくれます。ありがたい。

- バケットポリシーをツッコミましょう

```sh
bash-3.2$ bundle exec rake s3:policy:hage.inokara.com:update
#<struct Aws::S3::Types::EmptyStructure>
```

- 適用したポリシーを見てみましょう

```sh
bash-3.2$ bundle exec rake s3:policy:view[hage.inokara.com]
hage.inokara.com の policy を表示します...
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "AllowPublicRead",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::hage.inokara.com/*",
      "Condition": {
        "NotIpAddress": {
          "aws:SourceIp": "54.240.143.188/32"
        },
        "IpAddress": {
          "aws:SourceIp": "54.240.143.0/24"
        }
      }
    }
  ]
}
```

## 最後に

### バケラッタ

この Rake タスクを書き終わった後に気づきましたが、既に Codenize.tools の Bukelatta というツールがありますので、今後はバケラッタを使っていきたいと思います。

[https://github.com/winebarrel/bukelatta:embed:cite]

### Rake って

楽しいですね。