Skip to content

Instantly share code, notes, and snippets.

@hassaku63
Last active July 12, 2025 07:50
Show Gist options
  • Save hassaku63/79da57b244b3fddb48b416993376ce6c to your computer and use it in GitHub Desktop.
Save hassaku63/79da57b244b3fddb48b416993376ce6c to your computer and use it in GitHub Desktop.
(CDK) 自作 Construct に複合的なパーミッション付与を行う独自の grant メソッドを実装してみる
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
const app = new cdk.App();
const stack = new cdk.Stack(app, 'ExampleStack');
const role = new iam.Role(stack, 'ExampleRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});
class MyConstruct extends Construct {
public readonly bucket: s3.IBucket;
constructor(scope: Construct, id: string) {
super(scope, id);
this.bucket = new s3.Bucket(this, 'MyBucket');
}
// Note: CDK 内部実装なら第一引数は慣例的に IGrantable 型になる可能性が高いが、
// この例は IAM Role に権限付与する目的しか想定していないため、IGrantable 実装クラスの一つでもある IRole 型の宣言でも特に問題ない
public grantDefaultPermissions(role: iam.IRole): iam.Grant {
const grant1 = this.bucket.grantRead(role, {
prefix: 'shared/',
readOnly: true,
});
const grant2 = this.bucket.grantReadWrite(role, {
prefix: 'my-prefix/',
});
const result = grant1.combine(grant2);
return result;
}
}
const myConstruct = new MyConstruct(stack, 'MyConstruct');
const grant = myConstruct.grantDefaultPermissions(role);
// デバック出力。
// この例は IAM Role への権限付与、すなわちアイデンティティベースのポリシーを使う話なので、
// Grant オブジェクトの principalStatements プロパティを見る必要がある。
// バケットポリシーなどリソースベースの仕組みで用いた場合は、代えて resourceStatements プロパティを見る必要がある。
grant.principalStatements.forEach((statement, index) => {
console.log(`## Statement ${index + 1}:`);
console.log(statement.toJSON());
});
@hassaku63
Copy link
Author

hassaku63 commented Jul 11, 2025

実行例。

$ npx cdk synth -q -a 'npx cdk cdk-grant-example.ts'
## Statement 1:
{
  Action: [ 's3:GetObject*', 's3:GetBucket*', 's3:List*' ],
  Effect: 'Allow',
  Resource: [ '${Token[TOKEN.33]}', '${Token[TOKEN.33]}/[object Object]' ]
}
## Statement 2:
{
  Action: [
    's3:GetObject*',
    's3:GetBucket*',
    's3:List*',
    's3:DeleteObject*',
    's3:PutObject',
    's3:PutObjectLegalHold',
    's3:PutObjectRetention',
    's3:PutObjectTagging',
    's3:PutObjectVersionTagging',
    's3:Abort*'
  ],
  Effect: 'Allow',
  Resource: [ '${Token[TOKEN.33]}', '${Token[TOKEN.33]}/[object Object]' ]
}

grantDefaultPermissions によって付与された複合的な権限に対応する Policy Statement が、単一の grant オブジェクトの principalStatements プロパティから参照可能。

通常のテンプレート開発では具体的な付与した対象 (IAM Role) がわかっているので、付与した権限の正しさは IAM Role に対するアサーションで十分。Grant に対するアサーションは不要。

ただし、共通ライブラリ的な立ち位置で開発を行っていて、このような自作 grant メソッドを持つ Construct を実装している場合は、付与するロジックそのものに対する検証として Grant の中を評価するアサーションを書くのはアリ。

ひとまとまりの Stack や Construct を対象とする場合は他のリソース構築が含まれてしまい、その都合で今回使った自作の grantDefaultPermission メソッドに由来する以外の権限付与ロジックも動いてしまう仕様になっている・・・のような場合は、この機能によって発生した結果だけを抽出してアサーションする動機が生じる。このような場合は自作 grant メソッドの内部で適切に combine を使い、そのメソッドの権限付与の結果を正しく返すようにすることで機能単独のテストがしやすくなる。

ただし、上述の話は最終的なテンプレート出力を伴わないアサーションを行うアプローチなので、それによって発生する若干の不自由もある点に注意。具体的には、Grant のプロパティである principalStatements または resourceStatements に含まれる Policy Statement の Resource キーに Token が入りうる。ここについては、若干テストがしづらい。

@hassaku63
Copy link
Author

hassaku63 commented Jul 12, 2025

追記;

この例で追加した grantDefaultPermissions メソッドだが、CDK 内部実装の慣例的なデザインに(ある程度)従い、以下のようにしておくとよい。

  • MyConstruct に対応する IMyConstruct interface を作り、MyConstruct は IMyConstruct を実装する
  • interface 型の方にも、同様の grant メソッドを宣言する
  • interface 型の宣言に、fromName や fromArn 系統のインポートメソッドを適宜追加し、MyConstruct で実装(これは、今作る Construct が不特定から使われる汎用部品としての位置付けを持つ場合に)

こうすると、MyConstruct の汎用性が上がるし、CDK 本体のデザインに緩めに従うデザインになるため使い勝手がいいかんじになる(雑)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment