AWS CDK を使用して作成されたリソースに対してタグを付与し、そのタグを条件として IAM ポリシーでアクセス制御を行う
こんにちは、製造ビジネステクノロジー部の若槻です。
AWS には タグ 機能があり、リソースにキーとバリューのペアから成るラベルを付与することができます。これにより、リソースに関するメタデータをユーザーが自由に定義でき、名前空間ごとにリソースを一括して管理することが可能になります。
タグの主な用途としては、コスト管理やアクセス制御などがあります。アクセス制御においては、IAM ポリシーで conditions: { StringEquals: { "aws:ResourceTag/Protection": "Enabled" }}
のようにタグを条件として使用することで、特定のタグが付与されたリソースへのアクセスを制限することができます。
今回は、AWS CDK を使用して作成されたリソースに対してタグを付与し、そのタグを条件として IAM ポリシーでアクセス制御を行ってみます。
やってみた
CDK で既定でタグが付与されるの対象は明示的に作成されたリソースのみ
そもそも CloudFormation で作成されたリソースには既定で aws:cloudformation:stack-name
という共通のタグが付与されます。当初このタグを用いれば良いと考えましたが、ここで述べる通り CDK においてはその対象は明示的に作成されたリソースのみとなるようです。
次のように Lambda関数、SNS トピックを明示的に作成し、さらにトピックへのパブリッシュ権限を Lambda 関数に付与する CDK コードを記述します。
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as sns from "aws-cdk-lib/aws-sns";
import { Construct } from "constructs";
export class MainStack extends cdk.Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
// Lambda 関数および SNS トピックの作成
const lambdaFunction = new lambda.Function(this, "LambdaFunction", {
runtime: lambda.Runtime.NODEJS_LATEST,
handler: "index.handler",
code: lambda.Code.fromInline(`
exports.handler = async (event) => {
console.log("Hello from inline Lambda!");
return {
statusCode: 200,
body: JSON.stringify({ message: "Hello, World!" }),
};
}
`),
});
const topic = new sns.Topic(this, "MyTopic");
// トピックへのパブリッシュ権限を Lambda 関数に付与
topic.grantPublish(lambdaFunction);
}
}
上記をデプロイすると、Lambda 関数、SNS トピックおよび IAM ロールが作成されます。
明示的に作成された Lambda 関数と SNS トピックのタグを確認すると、aws:cloudformation:stack-name
タグが付与されていることがわかります。
一方で、grant メソッドにより暗黙的に作成される IAM ロールには aws:cloudformation:stack-name
タグが付与されていません。
このように、grant メソッドなどで暗黙的に作成されるリソースには aws:cloudformation:stack-name
タグが付与されないため、CDK で作成されたリソースをタグで一括管理したい場合は、明示的にタグを付与する必要があります。
CDK で全てのリソースに明示的にタグを付与する
AWS CDK で特定のスコープの全てのリソースに明示的にタグを付与するには、Tags.of(scope).add(key, value)
メソッドを使用します。これにより、スタックやコンストラクト内の全てのリソースに対して指定したタグを付与できます。
下記は先ほどの CDK コードに cdk.Tags.of(this).add("Protection", "Enabled");
を追加し、スタック内の全てのリソースに Protection: Enabled
タグを付与する例です。
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as sns from "aws-cdk-lib/aws-sns";
import { Construct } from "constructs";
export class MainStack extends cdk.Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
// Lambda 関数および SNS トピックの作成
const lambdaFunction = new lambda.Function(this, "LambdaFunction", {
runtime: lambda.Runtime.NODEJS_LATEST,
handler: "index.handler",
code: lambda.Code.fromInline(`
exports.handler = async (event) => {
console.log("Hello from inline Lambda!");
return {
statusCode: 200,
body: JSON.stringify({ message: "Hello, World!" }),
};
}
`),
});
const topic = new sns.Topic(this, "MyTopic");
// トピックへのパブリッシュ権限を Lambda 関数に付与
topic.grantPublish(lambdaFunction);
// スコープ内の全てのリソースにタグを付ける
cdk.Tags.of(this).add("Protection", "Enabled");
}
}
CDK Diff を実行すると、スタック内の全てのリソースに Protection: Enabled
タグが付与されることが確認できます。
$ npx cdk diff Main
start: Building Main Template
success: Built Main Template
start: Publishing Main Template (current_account-current_region)
success: Published Main Template (current_account-current_region)
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)
Stack Main
Resources
[~] AWS::IAM::Role LambdaFunction/ServiceRole LambdaFunctionServiceRoleC555A460
└─ [+] Tags
└─ [{"Key":"Protection","Value":"Enabled"}]
[~] AWS::Lambda::Function LambdaFunction LambdaFunctionBF21E41F
└─ [+] Tags
└─ [{"Key":"Protection","Value":"Enabled"}]
[~] AWS::SNS::Topic MyTopic MyTopic86869434
└─ [+] Tags
└─ [{"Key":"Protection","Value":"Enabled"}]
✨ Number of stacks with differences: 1
上記実装をデプロイすると、Lambda 関数、SNS トピック、IAM ロールに Protection: Enabled
タグが付与されます。
動作確認
動作確認のために、別の CDK スタックを作成し、Protection: Enabled
タグが付与されたリソースに対する操作を拒否する IAM グループと、それに所属させる IAM ユーザーを作成します。
import * as cdk from "aws-cdk-lib";
import * as iam from "aws-cdk-lib/aws-iam";
import { Construct } from "constructs";
export class Main2Stack extends cdk.Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
// リソースタグ "Protection": "Enabled" が付与されたリソースへの操作を拒否する IAM グループの作成
const group = new iam.Group(this, "Group", {
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess"),
],
});
group.addToPolicy(
new iam.PolicyStatement({
actions: ["*"],
resources: ["*"],
conditions: {
StringEquals: {
"aws:ResourceTag/Protection": "Enabled",
},
},
effect: cdk.aws_iam.Effect.DENY,
})
);
// IAM ユーザーおよびグループに対する操作を拒否するポリシーを追加
group.addToPolicy(
new iam.PolicyStatement({
actions: ["*"],
resources: ["arn:aws:iam::*:user/*", "arn:aws:iam::*:group/*"],
effect: cdk.aws_iam.Effect.DENY,
})
);
// IAM ユーザーの作成
new iam.User(this, "User", {
userName: "User",
groups: [group],
});
}
}
上記をデプロイしたら、タグが付与された Lambda 関数に対する操作を IAM ユーザーで試してみます。
Lambda 関数一覧で参照することは可能です。
Lambda 関数の詳細を参照しようとすると、次のように AccessDenied
が発生しアクセス権限不足により拒否されます。
一方でタグが付与されていないので当然ですが、Lambda 関数を含めた新規リソースの作成は行えます。
このように、CDK で作成されたリソースに対してタグを付与し、そのタグを条件として IAM ポリシーでアクセス制御を行うことができました。
注意点
カスタムリソースには CDK で明示的にタグ付与ができない
下記のように S3 バケットで autoDeleteObjects: true
を指定すると、CDK は自動的にカスタムリソースの Lambda 関数およびその実行ロールを作成し、バケット削除時に S3 バケットのオブジェクトの自動削除が行われるようになります。
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
export class MainStack extends cdk.Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
new cdk.aws_s3.Bucket(this, "MyBucket", {
autoDeleteObjects: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
}
}
それでは、スコープ内にカスタムリソースがある場合に Tags.of(scope).add(key, value)
を使うとどうなるでしょうか。
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
export class MainStack extends cdk.Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
new cdk.aws_s3.Bucket(this, "MyBucket", {
autoDeleteObjects: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
cdk.Tags.of(this).add("Protection", "Enabled");
}
}
CDK Diff を試してみると、カスタムリソースの Lambda 関数およびその実行ロールには Protection: Enabled
タグが付与されていないことがわかります。
$ npx cdk diff Main
start: Building Main Template
success: Built Main Template
start: Publishing Main Template (current_account-current_region)
success: Published Main Template (current_account-current_region)
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)
Stack Main
Resources
[~] AWS::S3::Bucket MyBucket MyBucketF68F3FF0
└─ [~] Tags
└─ @@ -2,5 +2,9 @@
[ ] {
[ ] "Key": "aws-cdk:auto-delete-objects",
[ ] "Value": "true"
[+] },
[+] {
[+] "Key": "Protection",
[+] "Value": "Enabled"
[ ] }
[ ] ]
✨ Number of stacks with differences: 1
このように、カスタムリソースは CDK の標準的なタグ付けメカニズムをサポートしていないようです。もしカスタムリソースにもタグを付与したい場合は手動でタグを付与するなどの対応が必要なので注意しましょう。
おわりに
AWS CDK を使用して作成されたリソースに対してタグを付与し、そのタグを条件として IAM ポリシーでアクセス制御を行ってみました。
本記事がどなたかの参考になれば幸いです。
参考
以上