AWS CDKでS3の拡張子を.zipに限定する方法
概要
AWS S3バケットで特定の拡張子のファイルのみアップロードを許可したい場合、標準機能では制限できませんが、バケットポリシーを使って実現できます。
この記事では、明示的拒否(Explicit Deny)とNotResourceを組み合わせて、.zipファイル以外のアップロードを事前にブロックする方法を解説します。
解決アプローチの比較
| 方法 | タイミング | コスト | 実装複雑度 | ファイル内容検証 |
|---|---|---|---|---|
| バケットポリシー | アップロード前 | 無料 | 低 | 不可(キー名のみ) |
| Lambda + S3イベント | アップロード後 | Lambda実行料金 | 中 | 可能 |
バケットポリシーによる事前制御は、コスト効率と即座性の観点で優れています。
アーキテクチャ
実装のポイント
バケットポリシーの動作原理
- 明示的拒否の優先: AWS IAMでは DENY > ALLOW の優先順位
- NotResource の活用: 「〜以外」の指定により、除外対象を定義
- マルチパートアップロード対応: 大容量ファイルの分割アップロードも制御
制御対象のS3アクション
actions: [
's3:PutObject', // 単一オブジェクトアップロード
's3:CreateMultipartUpload', // マルチパート開始
's3:UploadPart', // パーツアップロード
's3:CompleteMultipartUpload', // マルチパート完了
's3:AbortMultipartUpload', // マルチパート中断
]
CDK実装
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as iam from 'aws-cdk-lib/aws-iam';
export class ZipOnlyBucketStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const bucket = new s3.Bucket(this, 'ZipOnlyBucket', {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
enforceSSL: true,
});
// .zip以外のアップロードを明示的に拒否
const denyNonZipPolicy = new iam.PolicyStatement({
sid: 'DenyNonZipUploads',
effect: iam.Effect.DENY,
principals: [new iam.AnyPrincipal()],
actions: [
's3:PutObject',
's3:CreateMultipartUpload',
's3:UploadPart',
's3:CompleteMultipartUpload',
's3:AbortMultipartUpload',
],
// 重要: NotResourceで「*.zip以外」を対象にする
notResources: [bucket.arnForObjects('*.zip')],
});
bucket.addToResourcePolicy(denyNonZipPolicy);
}
}
動作確認
# 成功例
aws s3 cp archive.zip s3://bucket-name/archive.zip
# 拒否例(AccessDenied)
aws s3 cp document.txt s3://bucket-name/document.txt
aws s3 cp image.jpg s3://bucket-name/images/image.jpg
テスト結果
| ファイル名 | 結果 | 備考 |
|---|---|---|
data.zip |
許可 | 拡張子が.zip |
DATA.ZIP |
拒否 | 大文字小文字を区別 |
file.zip.txt |
拒否 | 最終拡張子が.txt |
path/to/file.zip |
許可 | パス内の.zipも有効 |
カスタマイズパターン
大文字小文字両対応
notResources: [
bucket.arnForObjects('*.zip'),
bucket.arnForObjects('*.ZIP'),
bucket.arnForObjects('*.Zip'),
],
複数拡張子の許可
notResources: [
bucket.arnForObjects('*.zip'),
bucket.arnForObjects('*.tar.gz'),
bucket.arnForObjects('*.7z'),
],
特定プレフィックス配下のみ制限
const denyNonZipInUploads = new iam.PolicyStatement({
sid: 'DenyNonZipInUploadsFolder',
effect: iam.Effect.DENY,
principals: [new iam.AnyPrincipal()],
actions: ['s3:PutObject'],
resources: [bucket.arnForObjects('uploads/*')],
notResources: [bucket.arnForObjects('uploads/*.zip')],
});
IAMプリンシパル限定
const denyNonZipForRole = new iam.PolicyStatement({
effect: iam.Effect.DENY,
principals: [
iam.Role.fromRoleArn(this, 'SpecificRole', roleArn)
],
actions: ['s3:PutObject'],
notResources: [bucket.arnForObjects('*.zip')],
});
高度な設定
条件キーを使った制限
const denyNonZipWithCondition = new iam.PolicyStatement({
effect: iam.Effect.DENY,
principals: [new iam.AnyPrincipal()],
actions: ['s3:PutObject'],
resources: [bucket.arnForObjects('*')],
conditions: {
StringNotLike: {
's3:object-key': '*.zip'
},
},
});
MFA必須でのアップロード
const requireMFAForUpload = new iam.PolicyStatement({
effect: iam.Effect.DENY,
principals: [new iam.AnyPrincipal()],
actions: ['s3:PutObject'],
resources: [bucket.arnForObjects('*.zip')],
conditions: {
BoolIfExists: {
'aws:MultiFactorAuthPresent': 'false'
},
},
});
トラブルシューティング
よくある問題
| 症状 | 原因 | 解決方法 |
|---|---|---|
| 期待通りに拒否されない | ポリシー評価順序の問題 | 他の許可ポリシーとの競合確認 |
| 正規表現が効かない | S3はワイルドカード(*)のみ |
StringLike条件を使用 |
| マルチパートが途中で失敗 | 一部アクションの制限漏れ | 全マルチパート関連アクション追加 |
デバッグ方法
// CloudTrailでポリシー評価ログ確認
const trail = new cloudtrail.Trail(this, 'S3AccessTrail', {
includeGlobalServiceEvents: false,
isMultiRegionTrail: false,
eventRuleActions: [
's3:PutObject',
's3:CreateMultipartUpload',
],
});
制限事項と考慮点
ファイル内容の検証なし
バケットポリシーはオブジェクトキーのみ評価するため、悪意のあるファイルが.zip拡張子で偽装される可能性があります。
大文字小文字の区別
デフォルトでは大文字小文字を厳密に区別します。必要に応じて複数パターンを定義してください。
ワイルドカードの制限
S3のリソース指定では、正規表現ではなくワイルドカード(*, ?)のみ使用可能です。
まとめ
AWS CDKとバケットポリシーを使用することで、S3バケットに特定拡張子のファイルのみをアップロードできるよう制限できます。
実装のキーポイント:
-
NotResourceを使った除外指定 -
明示的拒否による確実な制御 - マルチパートアップロードも含めた包括的な制御
この方法は追加コストなしで即座にブロックでき、セキュリティポリシーの実装に有効です。ただし、ファイル内容の検証が必要な場合は、Lambdaによる後処理との組み合わせを検討してください。