おぎろぐはてブロ

なんだかんだエンジニアになって10年以上

AWS CLIのS3接続先の選択動作とaddressing_styleオプション

AWS CLI がS3にアクセスする際にどのように Path-Style と Virtual Hosted-Style を使い分けるかをまとめます。

S3のアクセス形式2つ

S3 APIでアクセスするには以下2つの形式がある。(用語はドキュメントのまま)

  • パススタイル (Path-Style): http://s3-ap-northeast-1.amazonaws.com/mybucket/puppy.jpg
  • 仮想ホステッドスタイル (Virtual Hosted–Style): http://mybucket.s3.amazonaws.com
    • リージョンエンドポイントに、Virtual Hosted–Style もできる http://mybucket.s3-ap-northeast-1.amazonaws.com

CNAME Aliasは Virtual Hosted の拡張で、静的ウェブサイトホスティングは別の仕組みなのでここでは割愛。

それぞれのメリット、デメリットは以下のようなものが挙げられる

  • Path-Style
    • リージョンごとのエンドポイントにアクセスする必要がある
    • HTTP Keep-Alive 的には同一リージョンの複数のバケットに同時にアクセスするならメリットがあるかも
  • Virtual Hosted-Style
    • 名前解決でバケットが存在するリージョンに向くので、リージョンを気にしなくていい
    • 名前解決先がバケット作成から正しいリージョンに向くまで通常数時間ほどかかる。それまでは、us-east-1 に向く。

名前解決動作

正しく名前解決ができている場合は、バケットが作成したリージョンに名前解決される。

dig hmm.jp.s3.amazonaws.com

;; ANSWER SECTION:
hmm.jp.s3.amazonaws.com. 2400   IN  CNAME   s3-ap-northeast-1-w.amazonaws.com.
s3-ap-northeast-1-w.amazonaws.com. 1 IN A   52.219.4.97

未反映、もしくは存在しないバケット名の場合は、デフォルトのリージョン名無しのエンドポイントに向く。 これは、us-east-1 (旧 US Standard) リージョン。

dig justcreated.hmm.jp.s3.amazonaws.com

;; ANSWER SECTION:
justcreated.hmm.jp.s3.amazonaws.com. 7513 IN CNAME s3-directional-w.amazonaws.com.
s3-directional-w.amazonaws.com. 5891 IN CNAME   s3-1-w.amazonaws.com.
s3-1-w.amazonaws.com.   1   IN  A   54.231.115.3

バケット作成直後に Virtual Hosted–Style でアクセスした場合の問題

バケット作成直後に、Virtual Hosted Style でアクセスすると、us-east-1 リージョンを向いてしまう。 この場合、307 Temporary Redirect 応答が返ってくる。

curl -H "Host: hmm.jp.s3.amazonaws.com" http://s3.amazonaws.com -v
* Rebuilt URL to: http://s3.amazonaws.com/
*   Trying 54.231.72.50...
* TCP_NODELAY set
* Connected to s3.amazonaws.com (54.231.72.50) port 80 (#0)
> GET / HTTP/1.1
> Host: hmm.jp.s3.amazonaws.com
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 307 Temporary Redirect
< x-amz-bucket-region: ap-northeast-1
< x-amz-request-id: 2FC77EF092211583
< x-amz-id-2: 8Fd1S8rc9L3gx62nQXCe2It1JdltL4YgB/ZpnNmOzvRq+QIed5fkdh7fVVhjLo4SQPZGGA7Hl94=
< Location: http://hmm.jp.s3-ap-northeast-1.amazonaws.com/
< Content-Type: application/xml
< Transfer-Encoding: chunked
< Date: Tue, 23 May 2017 05:26:14 GMT
< Server: AmazonS3
<
<?xml version="1.0" encoding="UTF-8"?>
* Curl_http_done: called premature == 0
* Connection #0 to host s3.amazonaws.com left intact
<Error><Code>TemporaryRedirect</Code><Message>Please re-send this request to the specified temporary endpoint. Continue to use the original request endpoint for future requests.</Message><Bucket>hmm.jp</Bucket><Endpoint>hmm.jp.s3-ap-northeast-1.amazonaws.com</Endpoint><RequestId>2FC77EF092211583</RequestId><HostId>8Fd1S8rc9L3gx62nQXCe2It1JdltL4YgB/ZpnNmOzvRq+QIed5fkdh7fVVhjLo4SQPZGGA7Hl94=</HostId></Error>

エラーが返ってくるのであればよいが、VPC Endpoint内からのリクエスト等、us-east-1 リージョンに到達できない環境では、名前解決のあとのHTTPリクエストが到達しないこととなる。

AWS CLI はどちらの形式でS3にアクセスするか?

では、AWS CLIは、どちらの形式でS3にアクセスするか? これは条件によって変わる。

デバッグログを出力すると、以下のように Checking for DNS compatible bucket というログの後に、以下のようにURIが更新される場合とされない場合がある。

# Not changing URI
2017-05-23 08:02:18,490 - MainThread - botocore.utils - DEBUG - Checking for DNS compatible bucket for: https://s3-ap-northeast-1.amazonaws.com/hmm.jp?delimiter=%2F&prefix=&encoding-type=url
2017-05-23 08:02:18,490 - MainThread - botocore.utils - DEBUG - Not changing URI, bucket is not DNS compatible: hmm.jp
# URI updated
2017-05-23 08:22:30,011 - MainThread - botocore.utils - DEBUG - Checking for DNS compatible bucket for: https://s3-ap-northeast-1.amazonaws.com/hmm-jp?delimiter=%2F&prefix=&encoding-type=url
2017-05-23 08:22:30,011 - MainThread - botocore.utils - DEBUG - URI updated to: https://hmm-jp.s3.amazonaws.com/?delimiter=%2F&prefix=&encoding-type=url

AWS CLIでは、この挙動は AWS CLI が利用する botocore によるものなので、boto3 も同様の動作。 デバッグログで botocore と表示される部分はbotocoreの処理なので、その点意識すると、AWS CLIを叩いたときの処理の大部分はbotocoreだということが把握できる。

DNS Compatible Check とは?

hmm.jp.s3.amazonaws.com 自体はDNS Compatibleである。なので、実際のところ、ハンドラの名前が適切ではない。もともとは、その名の通り、DNS Compatible「だけ」をチェックするハンドラであった。 現状、DNS incompatible なバケット名、及び . (dot) を含むバケット名をチェックし、そうでなければ DNS Compatible と判定する。

DNS Incompatible なバケット名

us-east-1 (formerly known as US Standard) リージョンでは、DNS Compatibleでないバケット名が利用できていた。 (ドキュメントの記載では、現時点では利用可能だが、まもなく使えなくなる模様)

以下の例が挙げられているが、これらは、仮想ホスト形式 (http://<bucket_name>.s3.amazonaws.com/) では、DNS Compatibleでないということがわかるとおもう。

Example Reason
.myawsbucket バケット名の先頭にはピリオド(.)を使用できません。
myawsbucket. バケット名の末尾にはピリオド(.)を使用できません。
my..examplebucket ラベルの間にはピリオドを 1 つだけ使用できます。

dotを含むバケット名の対応

バケット名に . (dot) を含む場合、仮想ホスト形式では、SSL証明書のホスト名の検証で問題が発生する。

# HTTPならOK
curl -I http://hmm.jp.s3.amazonaws.com/index.html
HTTP/1.1 200 OK

# HTTPSでは証明書の警告が出る
curl -Iv https://hmm.jp.s3.amazonaws.com/index.html
*   Trying 52.219.4.85...
* TCP_NODELAY set
* Connected to hmm.jp.s3.amazonaws.com (52.219.4.85) port 443 (#0)
* SSL certificate problem: Invalid certificate chain
* Curl_http_done: called premature == 1
* Closing connection 0
curl: (60) SSL certificate problem: Invalid certificate chain
More details here: https://curl.haxx.se/docs/sslcerts.html

これは、S3側で利用している証明書が " *.s3.amazonaws.com"ワイルドカード証明書であるため。ワイルドカードはシングルレベルしか許容しないため、仮に hmm.jp.s3.amazonaws.com を満たすワイルドカード証明書を用意するとなれば、 *.jp.s3.amazonaws.com が必要となる。

ということで、dotを含むバケット名についても、パス形式のURLを利用する動作に変更されたが、ログに表示されるメッセージは、 DNS compatible のままであるという歴史的経緯

Pull Request では、誤ったエンドポイントに接続し、 301 応答が返却された場合の動作の修正が示唆されているんだけど、

We need to do more work in awscli to fully integrate the 301 redirect error we will receive if the wrong endpoint is chosen into a reasonable error message for the end user but this PR can go into botocore now without causing any problems.

これが実現されたのは3年後であった。

s3.addressing_style オプション

AWS CLI では、S3に接続する際にどちらの形式で接続するか指定する s3.adressing_style オプションがある。

addressing_style Default - auto

There’s two styles of constructing an S3 endpoint. The first is with the bucket included as part of the hostname. This corresponds to the addressing style of virtual. The second is with the bucket included as part of the path of the URI, corresponding to the addressing style of path. The default value in the CLI is to use auto, which will attempt to use virtual where possible, but will fall back to path style if necessary. For example, if your bucket name is not DNS compatible, the bucket name cannot be part of the hostname and must be in the path. With auto, the CLI will detect this condition and automatically switch to path style for you. If you set the addressing style to path, you must ensure that the AWS region you configured in the AWS CLI matches the same region of your bucket.

設定可能なオプションは以下。

  • path: 常にPath Styleでリージョンエンドポイントに接続する
    • i.e.https://s3-us-west-2.amazonaws.com/<bucket-name>
  • virtual: 常にVirtual Hosted Styleでリージョンエンドポイントに接続する
    • i.e. <bucket-name>.s3-us-west-2.amazonaws.com
    • DNS compatibleチェックをせず処理をすすめるのだけど、DNS incompatibleの場合は結局 InvalidDNSNameError が出てエラーになる
  • auto: 通常動作と同様

例えば、S3にVPC Endpointでのみ接続するような環境 (= 1つのリージョンしか利用しない) であれば、利用リージョンの指定も合わせてPath Style でもよいと思う。

aws configure set region ap-northeast-1
aws configure set default.s3.addressing_style path

リージョン違いのフォールバック

指定したリージョンにアクセスを試み、結果、異なるリージョンのS3にリクエストしてしまった場合。301のエラーが発生する。

<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>PermanentRedirect</Code>
<Message>The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.</Message>
<Bucket>iogi-private</Bucket>
<Endpoint>iogi.s3-us-west-2.amazonaws.com</Endpoint>
<RequestId>0E95BB54CDAF49A4</RequestId>
<HostId>JxazNOdEWUeE7Ch8...=</HostId>
</Error>

この場合、適切なリージョンへ向かうように修正され、再度リクエストが発生する。

botocore.vendored.requests.packages.urllib3.connectionpool - INFO - Starting
 new HTTPS connection (1): s3-us-west-2.amazonaws.com
botocore.vendored.requests.packages.urllib3.connectionpool - DEBUG - "GET /i
ogi-private?delimiter=%2F&prefix=&encoding-type=url HTTP/1.1" 200 None
botocore.parsers - DEBUG - Response headers: {'x-amz-bucket-region': 'us-wes
t-2', 'x-amz-id-2': 'EXO+iAgSAF7uV4cmGV0FPAPVeoUEII7mnS6uJvQknyeIkgOONRmWvFRQibCP7gfQHveSjimiyfw=', 'server': 'Amaz
onS3', 'transfer-encoding': 'chunked', 'x-amz-request-id': '0D249E086D6EACCF', 'date': 'Sat, 27 May 2017 16:31:57 G
MT', 'content-type': 'application/xml'}

この動きで、リージョンが違っていても正しく接続することができるが、複数回リクエストを投げることにより無理に時間を要してしまうので、適切なリージョンを指定するように注意。

まとめ

  • DNS Compatible であれば、 <bucket_name>.s3.amazonaws.com にリクエストする
  • DNS Compatible でなければ、 <region>.s3.amazonaws.com/<bucket_name> の形式でリクエストする
  • この動作は s3.addressing_style オプションで変更できる。
  • 異なるリージョンへ接続してしまった場合は、301応答を受けて、正しいリージョンにフォールバックする