OpenPolicyAgentでS3のバケットを作成するときのポリシーチェックをしてみた
今回はS3のバケットへのアクセス制限の設定についてポリシーチェックをしていこうと思います。
環境
- OS: CentOS 7
- Terraform : 0.12.26
- opa: Version: 0.21.0
S3のポリシーチェックで必要なもの
最終的に出来上がるものです。
EC2からVPC Endpointを通り、Access PointからS3へアクセスするようにします。 S3へのアクセス制限をする際に設定する内容は下記のものを想定しています。
ポリシーという単語をOpen Policy Agent のRegoで設定するポリシーとIAMポリシー、バケットポリシーいろんな意味で出てきてしまうため、単に「ポリシー」とあるものはOpen Policy Agentでチェックするポリシーとします。
また、今回はTerraformのtfファイルからjsonファイルへ返還する方法は特に記述せずに進めていきます。変換方法や返還後のjsonフォーマットが気になる方は下記を参考にしてみてください。
では、それぞれのポリシーの実装についてみていきましょう。
EC2に割り当てるIAM Role
EC2に割り当てられているIAMロールのIAMポリシーがS3へのアクセスを許可しているかチェックをしていきます。 まず、TerraformでEC2にIAMプロファイルを指定します。
resource "aws_instance" "sample" { ・・・ iam_instance_profile = aws_iam_instance_profile.sample_profile.name ・・・ }
そしてIAMプロファイルとIAMロール、IAMポリシーを作成します。
resource "aws_iam_instance_profile" "sample_profile" { name = "SampleEC2Role" role = aws_iam_role.sample_role.name } resource "aws_iam_role" "sample_role" { name = "SampleEC2Role" assume_role_policy = data.aws_iam_policy_document.ec2_role.json } resource "aws_iam_role_policy" "ec2_role_policy" { name = "SampleEC2RolePolicy" role = aws_iam_role.sample_role.id policy = data.aws_iam_policy_document.ec2_role_policy.json } data "aws_iam_policy_document" "ec2_role_policy" { statement { effect = "Allow" actions = [ "s3:*" ] resources = [ "*" ] } }
IAMロールに割り当てるIAMポリシーはすべてのS3に対して、すべてのアクションを許可するようにしています。 IAMポリシーの設定方法はjsonファイルとして記述することもできますが、Open Policy Agentでチェックする場合はjsonではなくHCLで記述したほうが都合がいいためHCLで記述します。
では、このIAMポリシーの設定をチェックするポリシーを見ていきましょう。
valid_ec2_iam_role { resources := ec2_role_policy_data count({resource| resources[resource]; ec2_iam_role_policy_s3_filter(resource) }) == count(resources) } ec2_iam_role_policy_s3_filter(resource) { statement := resource.expressions.statement[_] value := statement.actions.constant_value[_] re_match("s3:.*", value) statement.effect.constant_value == "Allow" }
valid_ec2_iam_role
のRuleでEC2に割り当てるIAMロールのIAMポリシーをチェックをしています。
まず、 ec2_role_policy_data
でEC2に割り当てられているIAMロールのIAMポリシーを取得しています。そして ec2_iam_role_policy_s3_filter
関数でS3へのアクセスが許可されているIAMポリシーをフィルターしています。
statement := resource.expressions.statement[_]
でIAMポリシーのステートメントをとりだし value := statement.actions.constant_value[_]
でactions の値を取得しています。
re_match("s3:.*", value)
では正規表現で "s3:.*" の正規表現に当てはまっているのかをチェックしてし、 statement.effect.constant_value == "Allow"
でステートメントが許可する設定となっているかをチェックしています。
フィルターし、得られたIAMポリシーの数とEC2に割り当てられているIAMロールのIAMポリシーの数が等しい場合、すべてのIAMポリシーが条件に当てはまっている状態となります。
ec2_role_policy_data
の実装方法は下記のようになっています。
ec2_iam_policy_data[resource] { policy := ec2_iam_role_policies[_] policy_data := policy.expressions.policy.references[_] resource := fetch_address_resource[policy_data] } ec2_iam_role_policies[resource] { role_address := ec2_role_addresses resource := data.configuration.root_module.resources[_] resource.expressions.role.references[_] == role_address[_] resource.type == "aws_iam_role_policy" } ec2_role_addresses[resource] { profiles := instances[_].expressions.iam_instance_profile.references[_] profile_resource := fetch_address_resource[profiles] resource := profile_resource.expressions.role.references[_] } fetch_address_resource[address] = resource { resource := data.configuration.root_module.resources[_] address := resource.address } instances[resource] { resource := data.configuration.root_module.resources[_] resource.type == "aws_instance" }
長くなってしまうため、細かい説明は省略します。 処理としては、EC2インスタンスの一覧を取得し、EC2に割り当てられているIAMプロファイルを取得。IAMプロファイルからIAMロールを取得し、IAMポリシーを取得する流れとなっています。
作成するS3のAccess Point
それでは作成するS3のAccess Pointに対するポリシーチェックをしていきます。 作成するS3とAccess Pointは下記のものとなります。
resource "aws_s3_bucket" "sample" { bucket = "sample-bucket" acl = "private" } resource "aws_s3_access_point" "sample-access-point" { bucket = aws_s3_bucket.sample.id name = "sample-access-point" vpc_configuration { vpc_id = aws_vpc.main.id } }
sample-bucket
バケットと Access Pointを作成するTerraformになります。Access Point には vpc_configuration
を指定し vpc_id
でVPCに対する作成を行います。
では、実際にS3のAccess PointがすべてVPCのみとなっているかチェックをしていきます。
ポリシーチェックの実装は下記のようになっています。
valid_s3_vpc_access_point { s3_access_points := s3_access_point count({resource| s3_access_points[resource]; count(resource.expressions.vpc_configuration) > 0 }) == count(s3_access_points) } s3_access_point[resource] { s3_address := create_s3_addresses[_] resource := reference_s3_resources[s3_address] resource.type == "aws_s3_access_point" } create_s3_addresses[address] { some i data.resource_changes[i].type == "aws_s3_bucket" data.resource_changes[i].change.actions[_] == "create" address := data.resource_changes[i].address } reference_s3_resources[s3_address] = resource { some i s3_address := data.configuration.root_module.resources[i].expressions.bucket.references[_] resource := data.configuration.root_module.resources[i] }
順番に見ていきましょう。
valid_s3_vpc_access_point
のRuleでS3のAccess Pointのチェックをしています。チェックするAccess Pointは s3_access_point
で取得しています。
s3_access_point内では create_s3_addresses
でTerraformで作成するS3のaddressを取得します。
取得したS3のaddressをもとに reference_s3_resources
で S3のaddressが関連付けられたリソースを取得します。
そして、S3に関連付けられたリソースで aws_s3_access_point
のものを返すようにすることでS3のAccess Pointを取得することができます。
取得したS3のAccess PointですべてのAccess Pointがvpc_configurationを所有しているかどうかをチェックすることですべてのAccess PointがVPCに制限されたものかどうかを判定しています。
S3へのアクセス制限
最後に、S3へのアクセスをAccess Pointのみ許可するようにします。
まず、Terraformで指定するバケットポリシーの内容を見ていきましょう。
resource "aws_s3_bucket" "sample" { bucket = "sample-bucket" acl = "private" } resource "aws_s3_access_point" "sample-access-point" { bucket = aws_s3_bucket.sample.id name = "main-access-point" vpc_configuration { vpc_id = aws_vpc.main.id } } data "aws_iam_policy_document" "s3-bucket-policy" { statement { effect = "Deny" actions = [ "s3:*Object" ] resources = [ "${aws_s3_bucket.sample.arn}/*", aws_s3_bucket.sample.arn ] principals { type = "AWS" identifiers = ["*"] } condition { test = "StringNotEquals" variable = "s3:DataAccessPointArn" values = [ aws_s3_access_point.sample-access-point.arn ] } } } resource "aws_s3_bucket_policy" "s3-main-policy" { bucket = aws_s3_bucket.sample.id policy = data.aws_iam_policy_document.s3-bucket-policy.json }
バケットポリシーもHCLの表記で記述していきます。
conditionの設定で sample-access-point
Access Point 以外からのアクセスに対して sample-bucket
バケットのオブジェクトへのアクションは拒否する設定となっています。
これで、S3のオブジェクトへのアクセスはAccess Pointを通してのアクセスのみに制限されました。
ではこの設定がされているのかをチェックするポリシーを見ていきます。
valid_s3_bucket_policy { policy := s3_bucket_policies[_] access_point := s3_access_point statement := policy.expressions.statement[_] statement.effect.constant_value == "Deny" re_match("s3:.*", statement.actions.constant_value[_]) statement.condition[_].test.constant_value == "StringNotEquals" statement.condition[_].variable.constant_value == "s3:DataAccessPointArn" statement.condition[_].values.references[_] == access_point[_].address } s3_bucket_policies[policy] { s3_address := create_s3_addresses resource := data.configuration.root_module.resources[_] resource.type == "aws_s3_bucket_policy" resource.expressions.bucket.references[_] == s3_address[_] policy := fetch_address_resource[resource.expressions.policy.references[_]] }
s3_access_point
と create_s3_addresses
は 作成するS3のAccess Point のところで出てきたものを使っています。
valid_s3_bucket_policy
でバケットポリシーが想定されているものかどうかをチェックしています。
対象となる、バケットポリシーは s3_bucket_policies
で取得しています。
valid_s3_bucket_policy
の中を細かく見ていきましょう。
まず、 s3_bucket_policies
と s3_access_point
でバケットポリシーとAccess Pointを取得します。
そしてバケットポリシーのステートメントを取得し、条件に当てはまるかをチェックしていきます。
これで S3のバケットポリシーのチェックができました。
まとめ
EC2に割り当てられてるIAMロールからIAMポリシーのチェックとS3のバケットポリシーのチェックをしていきました。
IAMポリシーとバケットポリシーのチェックをする際に、jsonファイルとして別ファイルなどにせずにHCLの表記にすることでOpen Policy Agent でチェックすることができます。aws_iam_policy_document
についても今回初めて知り使ってみましたが個人的な意見としてはjsonよりも使いやすかったので今後は aws_iam_policy_document
で書いていこうかと思います。
実装内容としては今回の実装ですとS3のバケットする際にすべてのバケットに対してポリシーチェックをするため、グローバルアクセスするものがある場合は処理を分ける必要があります。用途の異なるS3のバケットを構築する際のポリシーチェックについては別の記事で書いていこうかと思います。
Access Pointへアクセスできるものを制御したいとかある場合はアクセスポイントポリシーも設定することができるのでより厳密に制御することが可能となります。気になる方はぜひやってみてください。