【小ネタ】管理者権限のEC2から同アカウントのバケットポリシーに制限をかけていないS3へのcurlリクエストに失敗するのはなぜか

    【小ネタ】管理者権限のEC2から同アカウントのバケットポリシーに制限をかけていないS3へのcurlリクエストに失敗するのはなぜか

    目次

      はじめに

      クラウドインテグレーション部の渡邊です。
      今回は、S3へのcurlリクエストについて考えます。
      EC2からS3へリクエストをすることを想定すると、アクセス制限のまったくないS3バケットは外部からcurlなどを利用してオブジェクト情報を取得できます。
      しかし、アクセス制限がある場合はIAMロールの認証情報などを含める必要があります。

      まとめ

      • アクセス制限のあるS3へEC2からリクエストを送るためには以下に注意する
        • AWSのSignature Version 4 (SIGv4) をサポートしているcurlのパッケージを利用し、リクエストにSIGv4の署名を付与する
        • x-amz-content-sha256を含める(空文字のハッシュ値)

      環境情報

      • Amazon Linux 2
      • curl 8.0.1 (EC2)
        curlコマンドで--aws-sigv4フラグを使えるようにするために、7.75以上のバージョンが必要です。

      実際にやってみる

      署名情報や必要なヘッダが含まれていないcurlのリクエスト

      • 前提
        • EC2にアタッチしたIAMロールには管理者権限を付与
        • 同アカウントに配置しているリクエスト先のS3へバケットポリシーによる制限をかけていない

      アクセス先のS3へリクエストを送ると、オブジェクトの一覧を取得できます。

      $ aws s3 ls watanabe-object-wp --recursive | grep -v '/$' | awk '{print $4}'

      test.txt

      中身を見るためにcurlによるオブジェクト情報の読み取りを試みます。
      しかし、curlによるファイルの読み取りに失敗します。

      $ curl -k -XGET "https://watanabe-object-wp.s3.amazonaws.com/test.txt"
      <?xml version="1.0" encoding="UTF-8"?>

      <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>SH31WFKGCWH91G00</RequestId><HostId>PeW/1mkgfDLHMFQo/7EAFRk5u7xN+R3AwKygfIDrZfwFOVRxm90VjwUTwP7TZoe8Uq/DgjCcB+Q=</HostId></Error>

      なぜなら、リクエスト時にAWSの署名付与していないためです。
      これでは、任意の外部のサーバからAWS S3にリクエストしているのと変わりません。
      バケットポリシーやブロックパブリックアクセスの設定を変更して、外部からアクセスできるようにしない限り、リクエストは失敗します。

      署名情報を含めたcurlのリクエスト

      では次に署名情報を付与したリクエストをします。
      まずは以下の環境情報を用意してください(リージョン情報などは適宜変更してください)

      TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
      ROLE_NAME=`curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/iam/security-credentials/`
      CRED=`curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/${ROLE_NAME}/`
      AWS_ACCESS_KEY_ID=`echo $CRED | jq -r ".AccessKeyId"`
      AWS_SECRET_ACCESS_KEY=`echo $CRED | jq -r ".SecretAccessKey"`
      AWS_SESSION_TOKEN=`echo $CRED | jq -r ".Token"`
      REGION="us-east-1"
      SERVICE="s3"

      では、リクエストを実行します。
      リクエストに含むオブジェクトのURLはAWSのコンソール画面などから取得が可能です。

      $ curl -k -XGET "https://watanabe-object-wp.s3.amazonaws.com/test.txt" -H "X-Amz-Security-Token: ${AWS_SESSION_TOKEN}" --aws-sigv4 "aws:amz:${REGION}:${SERVICE}" --user "${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}"

      <?xml version="1.0" encoding="UTF-8"?>
      <Error><Code>InvalidRequest</Code><Message>Missing required header for this request: x-amz-content-sha256</Message><RequestId>RFK35PPH28X1NEVV</RequestId><HostId>Ni7j03DDfJVDhYZ8ctSedjs/YG205LucpRfNhMNm2YZJUMe+FQJRw1KRszaAOUka+Slut57e7sM=</HostId></Error>

      必要なヘッダがないことが原因で、リクエストに失敗しています。

      署名情報や必要なヘッダを含めたcurlのリクエスト

      前項のリクエストにx-amz-content-sha256ヘッダを含める必要があります。
      値として、空文字のハッシュ値を表すe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855を指定すると、リクエストに成功しました。

      $ curl -k -XGET "https://watanabe-object-wp.s3.amazonaws.com/test.txt" -H "X-Amz-Security-Token: ${AWS_SESSION_TOKEN}" -H "x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" --aws-sigv4 "aws:amz:${REGION}:${SERVICE}" --user "${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}"

      test


      余談

      ブロックパブリックアクセスをオフにして、バケットポリシーを以下のようにすると外部からもアクセスできるようになり、署名情報や必要なヘッダが含まれていないcurlのリクエストに成功します。

      バケットポリシー

      {
      "Version": "2012-10-17",
      "Statement": [
      {
      "Sid": "Statement1",
      "Effect": "Allow",
      "Principal": {
      "AWS": "*"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::watanabe-object-wp/*"
      }
      ]
      }

      リクエスト

      $ curl -k -XGET "https://watanabe-object-wp.s3.amazonaws.com/test.txt"

      test


      【参考】
       S3 オブジェクトの一覧をあまり手間をかけずに AWS CLI で取得する