バケットポリシーでS3のアクセス制限

S3を静的サイトホスティングでWeb公開するが、特定のIPかVPC内のEC2からのみアクセスできるように許可したい場合、バケットポリシーで制限できる。特定のIPかVPCなので条件をORでつなげる必要があるが、バケットポリシーのStatementはリストになっていて複数入力でき、それらがORでつながる。

それ以外は以下のように記載する。

  1. Id, Sidには適当な名前をいれる
  2. Webアクセス制限なのでActionにはs3:GetObjectを指定する
  3. EffectにはAllowを設定する
  4. IpAddressで対象のIPを列挙する
  5. StringEqualsで対象のVPCのIDを設定する

3の「EffectにはAllowを設定する」は重要なポイント。Conditionの条件文を工夫したうえで全体のActionDenyを設定し、Allowのときと論理的には同じ制限をつくっても、うまくアクセス制限できない。S3はデフォルトで非公開設定になっているので、その非公開デフォルト設定に対して、特定のIPあるいはVPCを許可(Allow)するというバケットポリシーを書かなくてはいけないため。

5の「StringEqualsで対象のVPCのIDを設定する」については、あらかじめS3へのVPCエンドポイントを作成しておかなければならない。VPCエンドポイントを作成しないと、VPCのプライベートサブネットからS3バケットへアクセスできない。また、VPC内からのアクセスであることをS3バケットが判断できない。

以上の点を踏まえると、バケットポリシーは次のようになる。

{
    "Version": "2012-10-17",
    "Id": "my-private-bucket-policy",
    "Statement": [
        {
            "Sid": "Allow-from-specific-IP",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::mybucket/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [
                        "XXX.XXX.XXX.XXX/32",
                        "YYY.YYY.YYY.YYY/28"
                    ]
                }
            }
        },
        {
            "Sid": "Allow-from-specific-VPC",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::mybucket/*",
            "Condition": {
                "StringEquals": {
                    "aws:SourceVpc": "vpc-99999999"
                }
            }
        }
    ]
}

Terraformによる設定

TerraformのVersion情報は以下の通り。

$ terrafrom --version
Terraform v0.11.5
+ provider.aws v1.35.0

Terraformでバケットの作成からWeb公開、VPCエンドポイントの作成、バケットポリシーの設定まで書く。

resource "aws_s3_bucket" "mybucket" {
  bucket = "mybucket"

  tags {
    Name        = "mybucket"
    Environment = "dev"
  }

  website {
    index_document = "index.html"
    error_document = "error.html"
  }
}

resource "aws_vpc_endpoint" "s3" {
  vpc_id       = "vpc-99999999"
  service_name = "com.amazonaws.ap-northeast-1.s3"
  route_table_ids = [
                      "rtb-WWWWWWWW",
                      "rtb-XXXXXXXX",
                      "rtb-YYYYYYYY",
                      "rtb-ZZZZZZZZ"
                    ]
}

resource "aws_s3_bucket_policy" "my-private-bucket-policy" {
  bucket = "${aws_s3_bucket.mybucket.id}"
  policy =<<POLICY
{
  "Version": "2012-10-17",
  "Id": "private-bucket-policy",
  "Statement": [
    {
      "Sid": "Allow-from-specific-IP",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": ["arn:aws:s3:::mybucket/*"],
      "Condition": {
        "IpAddress": {
           "aws:SourceIp": [
             "XXX.XXX.XXX.XXX/32",
             "YYY.YYY.YYY.YYY/28"
           ]
        }
      }
    }
,
    {
      "Sid": "Allow-from-specific-VPC",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": ["arn:aws:s3:::mybucket/*"],
      "Condition": {
        "StringEquals": {
           "aws:SourceVpc": [
             "vpc-99999999"
           ]
        }
      }
    }
  ]
}
POLICY
}