AWS CDK(Python)で特定のIPからのみアクセスできるS3バケットを作成してみた
データアナリティクス事業本部の鈴木です。
AWS CDKで特定のIPからのみアクセスできるS3バケットを作成してみました。
やりたいこと
以前バケットポリシーを使って、特定の条件からのみアクセスできるS3バケットを作った際に、全てのアクセス拒否から除外する、ただしこれらの条件に該当する場合は除外
という設定をしました。このように、除外条件が複数あるバケットポリシーを作る場合に、CDKではどのように記述したらいいんだろうと思い、試してみました。
ちなみに、CloudFormationテンプレートを直接作る方法は、こちらの記事で紹介しました(以降、CloudFormationはCFnと記載します)。
この設定では、DENYポリシーのCondition
キーのバリューに複数の条件を指定することで、該当する複数条件からのアクセスを許可していました。これってCDKだとどうやって書くのだろう?ということに相当します。
検証の前提
作成するS3バケットの設定
以下の2つの条件のどちらかを満たすときにアクセスを許可するように設定しました。
- 特定のIPアドレスからのアクセス
- CFnからのアクセス(忘れるとCFnからバケットが削除できなくなるので注意!)
環境
- Python 3.9.0
- AWS CDK 1.109.0
やってみた
プロジェクトの作成・準備
プロジェクトを以下のドキュメントを参考に作成しました。
Working with the AWS CDK in Python - AWS Cloud Development Kit (CDK)
まず、ディレクトリと雛形を作成します。
$ mkdir s3_iprestricted $ cd s3_iprestricted $ cdk init app --language python
続いて、仮装環境を有効化して、必要なパッケージをインストールしました。
# 仮装環境を有効化 $ source .venv/bin/activate # パッケージをインストール $ python -m pip install aws-cdk.aws-s3 aws-cdk.aws-iam
app.pyの編集
app.pyを以下のように編集しました。
※アクセス制限するIPは適当なものに置き換えてあります。
#!/usr/bin/env python3 from aws_cdk import ( core as cdk, aws_iam as iam, aws_s3 as s3, ) class IPRestrictedS3Stack(cdk.Stack): def __init__(self, scope: cdk.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) # Public access block public_access_blockc_config = s3.CfnBucket.PublicAccessBlockConfigurationProperty( block_public_acls=True, block_public_policy=True, ignore_public_acls=True, restrict_public_buckets=True ) # S3 bucket s3_bucket = s3.CfnBucket(self, id = "CMNayutaSampleBucket", bucket_name = "cm-nayuta-sample-bucket", public_access_block_configuration = public_access_blockc_config ) # Policy document policy_statement = iam.PolicyStatement(effect=iam.Effect.DENY) policy_statement.add_any_principal() policy_statement.add_actions("s3:*") policy_statement.add_resources(f'arn:aws:s3:::{s3_bucket.bucket_name}') policy_statement.add_resources(f'arn:aws:s3:::{s3_bucket.bucket_name}/*') policy_statement.add_condition("NotIpAddress", {'aws:SourceIp':"111.222.333.444"}) policy_statement.add_condition("StringNotEquals", {'aws:CalledVia':"cloudformation.amazonaws.com"}) policy_document = iam.PolicyDocument(statements=[policy_statement]) # Bucket policy s3.CfnBucketPolicy( self, "CMNayutaSampleBucketPolicy", bucket=s3_bucket.ref, policy_document=policy_document.to_json() ) app = cdk.App() IPRestrictedS3Stack(app, "cm-nayuta-cdk-stack") app.synth()
特にポイントはハイライトした29-37行目の、バケットにアタッチするポリシードキュメントを定義する箇所です。一つのDENYポリシーに複数の条件を指定するには、そのポリシーのインスタンスのadd_conditionを2回呼べば良いことが分かりました。
スタックを作成する
cdk ls
でスタック一覧を確認します。
$ cdk ls cm-nayuta-cdk-stack
エラーが出なければ、cdk deploy
でスタックを作成します。
$ cdk deploy
ターミナルでコマンドが正常に終了していれば、スタックが作成されているはずです。許可されたIPからバケットを見に行くと、無事作成されていることが確認できました。
バケットポリシーは以下のようになっており、期待通りにできていました。
※アクセス制限するIPは適当なものに置き換えてあります。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": [ "arn:aws:s3:::cm-nayuta-sample-bucket", "arn:aws:s3:::cm-nayuta-sample-bucket/*" ], "Condition": { "NotIpAddress": { "aws:SourceIp": "111.222.333.444" }, "StringNotEquals": { "aws:CalledVia": "cloudformation.amazonaws.com" } } } ] }
許可されていないIPからアクセスしてみると、アクセス制限されていることも確認できました。
後片付け
以下のコマンドでスタックを削除しておきます。
$ cdk destroy
感想
初めは少し難しい印象を持っていましたが、PythonのAPIリファレンス(参考に挙げたもの)を見つつ、ターミナルからコマンドでスタックを作ったり消したりして次第に理解することができました。
リファレンスは対応するサービスごとに整理されています。どのサービスにはどんなAPIがあって、CFnテンプレートとどういう対応関係なのかな〜と見ていくと、理解が深まりました。
また、スタック内でほかのリソースを参照するときに、refで簡単にできるのがとても使いやすいなと思いました。CFnテンプレートだと、Ref関数で参照しますが、CDKの方がより直感的に使えるなと感じました。
今回はバケットポリシーを付けたS3バケットを作成しましたが、ほかのリソースにも挑戦していきたいです。