最新 追記

雑記帳


2014-07-11 (Fri) [長年日記]

[AWS] AWS SDK for Rubyで新標準となったCredentials管理方法を使ってみる

A New and Standardized Way to Manage Credentials in the AWS SDKsというblogの記事があったので、早速試してみた*1

credentialsファイルがない状態で、下記のコマンドを実行すと、エラーが発生する。

% ruby -raws-sdk -e 'puts AWS.config.credentials'
/Users/isobe/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/aws-sdk-1.46.0/lib/aws/core/credential_providers.rb:135:in `credentials':  (AWS::Errors::MissingCredentialsError)
Missing Credentials.

Unable to find AWS credentials.  You can configure your AWS credentials
a few different ways:

* Call AWS.config with :access_key_id and :secret_access_key

* Export AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to ENV

* On EC2 you can run instances with an IAM instance profile and credentials
  will be auto loaded from the instance metadata service on those
  instances.

* Call AWS.config with :credential_provider.  A credential provider should
  either include AWS::Core::CredentialProviders::Provider or respond to
  the same public methods.

= Ruby on Rails

In a Ruby on Rails application you may also specify your credentials in
the following ways:

* Via a config initializer script using any of the methods mentioned above
  (e.g. RAILS_ROOT/config/initializers/aws-sdk.rb).

* Via a yaml configuration file located at RAILS_ROOT/config/aws.yml.
  This file should be formated like the default RAILS_ROOT/config/database.yml
  file.

	from /Users/isobe/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/aws-sdk-1.46.0/lib/aws/core/credential_providers.rb:62:in `access_key_id'
	from /Users/isobe/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/aws-sdk-1.46.0/lib/aws/core/configuration.rb:263:in `block in credentials'
	from /Users/isobe/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/aws-sdk-1.46.0/lib/aws/core/configuration.rb:262:in `each'
	from /Users/isobe/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/aws-sdk-1.46.0/lib/aws/core/configuration.rb:262:in `credentials'
	from -e:1:in `<main>'

credentialsファイルを用意する

UN*Xの場合は、~/.aws/credentialsを作成し、下記のような内容を追記する。*2

$ cat ~/.aws/credentials
[default]
aws_access_key_id = AKxxxxxxxxxxxxxxxxxx
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

[account1]
aws_access_key_id = AKyyyyyyyyyyyyyyyyyy
aws_secret_access_key = yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

さっきと同じコマンドを実行するとどうなるか。

% ruby -raws-sdk -e 'puts AWS.config.credentials'
{:access_key_id=>"AKxxxxxxxxxxxxxxxxxx", :secret_access_key=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}

~/.aws/credentialsファイルの、defaultセクションに記載のアクセスキーIDと、シークレットアクセスキーが入るようになっている。

これは、AWS::Core::CredentialProviders::DefaultProviderの働きとなる。使用されているCredentialProviderは、AWS.config.credentials_providerで確認できる。

% ruby -raws-sdk -e 'puts AWS.config.credentials_provider'
#<AWS::Core::CredentialProviders::DefaultProvider:0x007fa1e298ec80>

AWS CLIでは、--profileオプションを使って、別のプロファイルに切り替えることができる。これを、AWS SDK for Rubyで実現するには、下記のようにすればよい。

% ruby -raws-sdk -e 'puts AWS.config(credential_provider: AWS::Core::CredentialProviders::SharedCredentialFileProvider.new(profile_name: "account1")).credentials'
{:access_key_id=>"AKyyyyyyyyyyyyyyyyyy", :secret_access_key=>"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"}

SharedCredentialFileProvider.newの引数に、:profile_nameをキーとしてプロファイル名を渡すと、~/.aws/credentialsからそのセクションを探して使ってくれるようになる。

AWS SDKを使ってるツールは、ぜひ対応して欲しい。

*1 AWS CLIでは、1.3.13から、AWS SDK for Rubyでは、1.40.0からサポートされている。

*2 ちなみに、AWS CLIでは、以前は~/.aws/configを参照していた。こちらに含まれている、認証情報以外の設定(デフォルトのregionなど)は、そのまま利用できる。


2014-07-15 (Tue) [長年日記]

[AWS] DB Parameter Groupの設定をコピーするワンライナー

2014-10-16追記
CopyDBParameterGroup APIが増えたので、そちらを使いましょう。

DB Parameter Groupでユーザによる設定を変更したものを、別のDB Parameter Groupにコピーしたかったのだが、AWS CLIのドキュメントを読んでいて、ワンライナーで簡単にコピー出来そうだと思って試行錯誤したら出来た。

% aws rds describe-db-parameters --db-parameter-group-name param1 | \
  jq '[.Parameters[]|select(.Source == "user") + {"ApplyMethod": "immediate"}]' | \
  tr -d '\n' | \
  xargs -I{} aws rds modify-db-parameter-group --db-parameter-group-name param2 --parameters '{}'

今回は行ごとに解説してみる。

1行目
コピー元となるDB Parameter Group (param1) の設定を取得している。
2行目
その結果からjqを使って、ユーザによって変更されたもののみをフィルタして取得し、その結果それぞれについて、"ApplyMethod": "immediate"という項目を追加している。
3行目
JSONに含まれる改行を全て削除している。
4行目
コピー先となるDB Parameter Group (param2) にmodify-db-parameter-groupを実行する。xargsを使って--parametersオプションに、jqで加工したJSONを渡している。

describe-db-parametresで、ユーザによって変更されたパラメータの一覧をJSONで取得し、その内容をまるごとmodify-db-parameter-groupに渡しているということになる。ただし、そのまま渡してもだめで、ApplyMethodの追加が必要となるので、それをjqで行なっている。

[Evernote] Evernote for Mac 5.6 Previewではノートのリンクがevernote://...からhttp://...に変更になった

Mac 5.5以前のEvernoteで「ノートリンクをコピー」を実行すると、evernote://で始まるリンクが取得できた。これはMacの中で開くと、ローカルにインストールしているEvernoteアプリでノートが開くので便利である。OmniFocusのメモによく貼って使っている。

これが、5.6からは、http://で始まるリンクとなり、ブラウザでwww.evernote.comが開くように変更されている。こっちのほうが汎用性が高いのだが、オフラインでアプリのノートが開けるほうが嬉しかったので、自分としては困ったことになってしまった。

これに対する対策はあって、ノートリンクをコピーした後に、Evernoteのノートにリンクをペーストすると、evernote://で始まるURIになるので、そのURIをコピーして別のアプリケーションに貼り付ける事ができる。

しかし、手順としては面倒なので、evernote://のリンクが一発で取得できるようにして欲しい。


2014-07-17 (Thu) [長年日記]

[AWS] AWS SDK for Ruby付属のaws-rbが--profileオプションに対応

aws-rbとは、AWS SDK for Rubyに付属しているコマンドで、irbまたはpryを使ってAWS SDK for Rubyをインタラクティブに簡単に扱えるツールである*1

このツールが、先日書いた記事「AWS SDK for Rubyで新標準となったCredentials管理方法を使ってみる」の仕組みをつかって、--profileオプションに対応しました。本日リリースされた1.48.0から利用できるようになっていた*2

ためしに実行してみる。

現在のcredentialsファイルが下記のようになっている。

$ cat ~/.aws/credentials
[default]
aws_access_key_id = AKxxxxxxxxxxxxxxxxxx
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

[account1]
aws_access_key_id = AKyyyyyyyyyyyyyyyyyy
aws_secret_access_key = yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

この状態で何も指定せずに実行すると、defaultが自動的に適用される。

% aws-rb
AWS> AWS.config.credentials
{
        :access_key_id => "AKxxxxxxxxxxxxxxxxxx",
    :secret_access_key => "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

--profileをつけると、下記のようになる。

% aws-rb --profile account1
AWS> AWS.config.credentials
{
        :access_key_id => "AKyyyyyyyyyyyyyyyyyy",
    :secret_access_key => "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
}

これまでは、起動してから一々アクセスキーIDとシークレットアクセスキーを指定してたので、とても楽になった。

*1 AWS Development blogの記事 http://ruby.awsblog.com/post/Tx37CB1ZX8AGBQ5/Using-the-AWS-SDK-for-Ruby-from-Your-REPL が参考になる

*2 すでに1.48.1がリリースされてた


2014-07-23 (Wed) [長年日記]

[AWS] Amazon RDSのログをダウンロードするスクリプトを作った

Amazon RDSは、ログを直接参照できないので、APIを使ってログをダウンロードすることができる。AWS Management Consoleでdownloadボタンをポチポチ押してダウンロードすることもできるが、大量にあるログを一気に取得したかったので、スクリプトを作った。

githubで公開してます。 https://github.com/muramasa64/rds-log-downloader

いくつかハマるポイントがあった。

download_db_log_file_portionを実行するときのmarkerの初期値は0を指定する

ログファイルのサイズが大きい場合は、一度のAPI呼び出しで全て取得できない。続きがある場合は、レスポンスにmarkerという値が返ってくるので、次の呼び出し時に渡してあげると、続きを取得することができる。

当初は特に値を渡さずに実行していたのだが、その場合は最初からではなくて途中から最後までが取得できるようで、最初が取得できなかった。最初から取得したい場合は、markerを0にすればよい。

describe_db_log_filesのfile_last_writtenには、ミリ秒単で指定する

ファイル一覧を取得する際に、フィルタの条件で、最後に更新された日時を指定するfile_last_writtenオプションがあるのだが、この値の単位はミリ秒である。Rubyで、Time.to_iを実行すると取得できる値は秒単位であるため、1000倍した値を渡すようにしている。


2014-07-28 (Mon) [長年日記]

[AWS] roadworker, piculet, kelbimが--profileオプションに対応

AWSを使う上で、超便利なツールである、roadworker(Route 53)、piculet(Security Group)、kelbim(ELB)が、--profileオプションに対応した*1

roadworkerとpiculetについては、自分がPull requestしたら、すぐに取り込んでいただけた。また、kelbimについては追って対応された様子。

この辺りのツールは、何れもかなり便利で実用的なのでオススメ。IAMに対応したツールが欲しい(自分で作るか…)。

*1 以前は、bitbucketにてリポジトリがホスティングされていたが、githubに変更になっていた。

[AWS] JAWS-UG CLI専門支部 #0に参加した

最近、AWS CLIをよく使っているということもあって、参加してきた。 主催者のtcshさんの作成したハンズオン資料を元に、参加者がもくもくするという、由緒正しい感じの勉強会で良かった。

初回は、AWS CLIをつかってS3の静的ウェブサイトホスティングまでやるというところまで実施した。WebSite化したS3のURLってリージョンとバケット名が決まっていれば一意に決まるからか、取得するAPIが無いっていうことに気がついた。自分は覚えてなかったので、ドキュメントで確認したけど、他の参加者はAWS Management Consoleとかで確認していたのかな。

ちなみにこのResions and Endpointsが書いてある、AWS General Referenceって、今のAWSドキュメントのトップから、直接的なリンクが無いのが残念。以前はあったんだけど、リニューアルされたときに無くなってしまった。しかし何故か、AWSの用語集へのリンクはあるのだが、それ自体はAWS General Reference内のコンテンツなので、そこから辿ることはできる。

このCLI専門支部は、二週間に一回くらいのペースで実施するとのことなので、スケジュールが合えば次回以降も参加したいと思う。


2014-07-31 (Thu) [長年日記]

[AWS] Amazon S3に別アカウントから書き込みを許可する場合の設定

あるバケットに、別のアカウントから読み書きできるようにバケットポリシーを定義したい。普通に考えると、下記のような感じになるだろう。

{
  "Version": "2012-10-17",
  "Id": "Policy1406782030997",
  "Statement": [
    {
      "Sid": "Stmt1406782027567",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:ListBucket",
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::s3-acl-test",
        "arn:aws:s3:::s3-acl-test/*"
      ],
      "Principal": {
        "AWS": [
          "024665792058"
        ]
      }
    }
  ]
}

このように設定されたバケットに、許可した別アカウントから書き込みをする。

% aws --profile s3test s3api put-object --bucket s3-acl-test --key file --body file
{
    "ETag": "\"8a9c538c7f848d97d9d45736c4f709f3\""
}

書き込んだオブジェクトを取得してみる。中身はどうでもいいので、/dev/nullに捨てる。

% aws --profile s3test s3api get-object --bucket s3-acl-test --key file /dev/null
{
    "AcceptRanges": "bytes",
    "ContentType": "binary/octet-stream",
    "LastModified": "Thu, 31 Jul 2014 04:56:08 GMT",
    "ContentLength": "14",
    "ETag": "\"8a9c538c7f848d97d9d45736c4f709f3\""
}

さて、これをバケットを所有しているアカウントで取得してみよう。

% aws --profile default s3api get-object --bucket s3-acl-test --key file /dev/null

A client error (AccessDenied) occurred when calling the GetObject operation: Access Denied

アクセス出来ない。これは、ACLが下記のように、書き込んだユーザのみ権限が付与されているからである。

% aws --profile s3test s3api get-object-acl --bucket s3-acl-test --key file
{
    "Owner": {
        "DisplayName": "s3test",
        "ID": "4682c00752c7f406f24e7dda04700a324b2179670ee5ab2a610352226a47dc73"
    },
    "Grants": [
        {
            "Grantee": {
                "DisplayName": "s3test",
                "ID": "4682c00752c7f406f24e7dda04700a324b2179670ee5ab2a610352226a47dc73"
            },
            "Permission": "FULL_CONTROL"
        }
    ]
}

バケットの所有者は、このオブジェクトを誰でも読み取り可能にしたい。バケットポリシーを下記のものに変更する。

{
  "Version":"2012-10-17",
  "Id": "Policy1406782030997",
  "Statement": [
    {
      "Sid": "Stmt1406782027567",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::s3-acl-test",
        "arn:aws:s3:::s3-acl-test/*"
      ],
      "Principal": {
        "AWS": [
          "024665792058"
        ]
      }
    },
    {
      "Sid": "Stmt1406782027568",
      "Action": [
        "s3:GetObject"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::s3-acl-test",
        "arn:aws:s3:::s3-acl-test/*"
      ],
      "Principal": {
        "AWS": [
          "*"
        ]
    }
  ]
}

自分でオブジェクトを作成して、テストしてみる。

% aws --profile default s3api put-object --bucket s3-acl-test --key file2 --body file
{
    "ETag": "\"8a9c538c7f848d97d9d45736c4f709f3\""
}

curlで取得してみる。

% curl http://s3-acl-test.s3.amazonaws.com/file2
Hello, World.
% curl --head http://s3-acl-test.s3.amazonaws.com/file2
HTTP/1.1 200 OK
x-amz-id-2: 8Bb1L6uwe497S7CEHzFTtx4qI2rye2c+ilZoALtDqJ9xzk+LXVvZmcbk7/HB+yy/
x-amz-request-id: 1696D56CCBF01BE5
Date: Thu, 31 Jul 2014 05:08:17 GMT
Last-Modified: Thu, 31 Jul 2014 05:06:04 GMT
ETag: "8a9c538c7f848d97d9d45736c4f709f3"
Accept-Ranges: bytes
Content-Type: binary/octet-stream
Content-Length: 14
Server: AmazonS3

問題なく取得できる。

他のアカウントが作成したオブジェクトも同様に取得してみる。

% curl http://s3-acl-test.s3.amazonaws.com/file
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>8A2F7FE7C668B21C</RequestId><HostId>ZMgwnn6fAvDCB3bP8pLIXu7Ar3zQ0jeMfTXP6Wn1fW9y9dKPsgkBaMc8l+esYiQ+</HostId></Error>
% curl --head http://s3-acl-test.s3.amazonaws.com/file
HTTP/1.1 403 Forbidden
x-amz-request-id: 7FF68FDDDC40A0A7
x-amz-id-2: 4RN6yGv2TUrEXSwvaTGsaOANf5rEW0j82eOyh4hPdOGuMbRLtZ4+Kuw45BtSzZOckPMVIGUbahBY+8MXv0YM1A==
Content-Type: application/xml
Transfer-Encoding: chunked
Date: Thu, 31 Jul 2014 05:12:23 GMT
Server: AmazonS3

エラーになってしまった。これはどういう事だろうか。バケットポリシーが適用されていないように見える。

実は、バケットポリシーは、バケットの所有者とオブジェクトの所有者が同じでないと、ポリシーが適用されないという仕様になっている*1

ということで、バケットの所有者で改めてオブジェクトを作成しなおしてみる。オブジェクトに権限がなくてもオブジェクトを作成・削除する権限があるので、普通にputすればよい。

% aws --profile default s3api put-object --bucket s3-acl-test --key file --body file
{
    "ETag": "\"8a9c538c7f848d97d9d45736c4f709f3\""
}

所有者が変更されている。

% aws --profile default s3api get-object-acl --bucket s3-acl-test --key file
{
    "Owner": {
        "DisplayName": "default",
        "ID": "2b70304c677e207198b076a1436d3f0fdf8c4cd34a1d6a77c95924268e52dc27"
    },
    "Grants": [
        {
            "Grantee": {
                "DisplayName": "default",
                "ID": "2b70304c677e207198b076a1436d3f0fdf8c4cd34a1d6a77c95924268e52dc27"
            },
            "Permission": "FULL_CONTROL"
        }
    ]
}

この状態であれば、バケットポリシーが適用されるので、匿名ユーザでもGETできるようになる。

% curl http://s3-acl-test.s3.amazonaws.com/file
Hello, World.
% curl --head http://s3-acl-test.s3.amazonaws.com/file
HTTP/1.1 200 OK
x-amz-id-2: /Hy86Fcmqf6t5ICqdycw4GvqI9DX12cPtTfE6OOXYJZ0JRMZPK6WTc2vOU/+Ot88
x-amz-request-id: 3388276F03F90125
Date: Thu, 31 Jul 2014 11:07:17 GMT
Last-Modified: Thu, 31 Jul 2014 10:56:18 GMT
ETag: "8a9c538c7f848d97d9d45736c4f709f3"
Accept-Ranges: bytes
Content-Type: binary/octet-stream
Content-Length: 14
Server: AmazonS3

問題が起こらないようにするには

ということで、他のアカウントのユーザに自分のバケットを開放する際には、気をつけなければいけないことがわかった。では、誰が書き込んだとしても、匿名ユーザからのアクセスを許可したい場合には、どうすればいいのか。

下記のようなバケットポリシーを設定すればよい。

{
  "Version": "2012-10-17",
  "Id": "Policy1406782030997",
  "Statement": [
    {
      "Sid": "Stmt1406782027567",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::s3-acl-test",
        "arn:aws:s3:::s3-acl-test/*"
      ],
      "Principal": {
        "AWS": [
          "024665792058"
        ]
      }
    },
    {
      "Sid": "Stmt1406782027569",
      "Effect": "Deny",
      "Principal": {
        "AWS": [
          "024665792058"
        ]
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::s3-acl-test/*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-grant-read": [
            "uri=http://acs.amazonaws.com/groups/global/AllUsers"
          ]
        }
      }
    },
    {
      "Sid": "Stmt1406782027568",
      "Action": [
        "s3:GetObject"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::s3-acl-test",
        "arn:aws:s3:::s3-acl-test/*"
      ],
      "Principal": {
        "AWS": [
          "*"
        ]
      }
    }
  ]
}

追加したポリシー(真ん中のブロック)は、PutObjectを実行する際には、AllUsersに対して読み込みを許可する権限が付与されていなければ拒否する、というものである*2。つまり、書き込むオブジェクトの権限を強制するもの、ということになる。

それでは、このバケットポリシーが設定されている状態で、先ほどと同じように、オブジェクトを作成してみる。

% aws --profile s3test s3api put-object --bucket s3-acl-test --key file3

A client error (AccessDenied) occurred when calling the PutObject operation: Access Denied

エラーとなった。すべてのユーザーがREADできるようにACLを付与するように変更してみる。

% aws --profile s3test s3api put-object --bucket s3-acl-test --key file3 --body file --grant-read "uri=http://acs.amazonaws.com/groups/global/AllUsers"
{
    "ETag": "\"8a9c538c7f848d97d9d45736c4f709f3\""
}

今度は書き込めたので、ACLを確認する。

% aws --profile s3test s3api get-object-acl --bucket s3-acl-test --key file3
{
    "Owner": {
        "DisplayName": "s3test",
        "ID": "4682c00752c7f406f24e7dda04700a324b2179670ee5ab2a610352226a47dc73"
    },
    "Grants": [
        {
            "Grantee": {
                "URI": "http://acs.amazonaws.com/groups/global/AllUsers"
            },
            "Permission": "READ"
        }
    ]
}

curlでGETしてみる。

% curl http://s3-acl-test.s3.amazonaws.com/file3
Hello, World.
% curl --head http://s3-acl-test.s3.amazonaws.com/file3
HTTP/1.1 200 OK
x-amz-id-2: sZY8vgy+haVfV8Rji1w8bePa8kjFkyxms6mMZvu18vHgIStEB4RUcS7Y+stiHrra
x-amz-request-id: C58EB280DFE46C5D
Date: Thu, 31 Jul 2014 11:45:07 GMT
Last-Modified: Thu, 31 Jul 2014 11:44:39 GMT
ETag: "8a9c538c7f848d97d9d45736c4f709f3"
Accept-Ranges: bytes
Content-Type: binary/octet-stream
Content-Length: 14
Server: AmazonS3

今度は取得できるようになった。

今回はACLにて権限を設定したが、実際はバケットポリシーで制限をしたい場合の方が多いのではないかと思う。その場合は、バケットとオブジェクトの所有者をあわせなければならない。どうやら、オブジェクトの所有者を変更するには、そのユーザでオブジェクトを作成するしか方法がないようである。従って、次のようにすれば良い。

  • バケットの所有者にフルコントロールを与える権限を付与しないとPUTできないようにする(前述のバケットポリシーの応用)
  • バケットの所有者が、書き込まれたオブジェクトを同じ場所にコピーする(コピー元とコピー先を同じにする)。

こうすることで、バケットの所有者とオブジェクトの所有者を同じにする事ができる。書き込まれたオブジェクト毎に実行しなければならないので、書き込まれたことの通知を受けて処理するようにする必要があるなど、実現するにはハードルが高いように思える。そもそも同じアカウントで書き込むのが手っ取り早いだろう。

*1 これを知らなかったせいで結構ハマったのであった…

*2 AWSのドキュメントを参考にした。http://docs.aws.amazon.com/AmazonS3/latest/dev/AccessPolicyLanguage_UseCases_s3_a.html