@@ -242,10 +242,11 @@ interface AssetPublishingProps {
242
242
*/
243
243
class AssetPublishing extends Construct {
244
244
private readonly publishers : Record < string , PublishAssetsAction > = { } ;
245
+ private readonly assetRoles : Record < string , iam . IRole > = { } ;
245
246
private readonly myCxAsmRoot : string ;
246
247
247
248
private readonly stage : codepipeline . IStage ;
248
- private assetRole ?: iam . Role ;
249
+ private readonly pipeline : codepipeline . Pipeline ;
249
250
private _fileAssetCtr = 1 ;
250
251
private _dockerAssetCtr = 1 ;
251
252
@@ -256,6 +257,7 @@ class AssetPublishing extends Construct {
256
257
// We MUST add the Stage immediately here, otherwise it will be in the wrong place
257
258
// in the pipeline!
258
259
this . stage = this . props . pipeline . addStage ( { stageName : 'Assets' } ) ;
260
+ this . pipeline = this . props . pipeline ;
259
261
}
260
262
261
263
/**
@@ -269,15 +271,9 @@ class AssetPublishing extends Construct {
269
271
// FIXME: this is silly, we need the relative path here but no easy way to get it
270
272
const relativePath = path . relative ( this . myCxAsmRoot , command . assetManifestPath ) ;
271
273
272
- // This role is used by both the CodePipeline build action and related CodeBuild project. Consolidating these two
273
- // roles into one, and re-using across all assets, saves significant size of the final synthesized output.
274
- // Modeled after the CodePipeline role and 'CodePipelineActionRole' roles.
275
- // Late-binding here to prevent creating the role in cases where no asset actions are created.
276
- if ( ! this . assetRole ) {
277
- this . assetRole = new iam . Role ( this , 'Role' , {
278
- roleName : PhysicalName . GENERATE_IF_NEEDED ,
279
- assumedBy : new iam . CompositePrincipal ( new iam . ServicePrincipal ( 'codebuild.amazonaws.com' ) , new iam . AccountPrincipal ( Stack . of ( this ) . account ) ) ,
280
- } ) ;
274
+ // Late-binding here (rather than in the constructor) to prevent creating the role in cases where no asset actions are created.
275
+ if ( ! this . assetRoles [ command . assetType ] ) {
276
+ this . generateAssetRole ( command . assetType ) ;
281
277
}
282
278
283
279
let action = this . publishers [ command . assetId ] ;
@@ -298,7 +294,7 @@ class AssetPublishing extends Construct {
298
294
cloudAssemblyInput : this . props . cloudAssemblyInput ,
299
295
cdkCliVersion : this . props . cdkCliVersion ,
300
296
assetType : command . assetType ,
301
- role : this . assetRole ,
297
+ role : this . assetRoles [ command . assetType ] ,
302
298
} ) ;
303
299
this . stage . addAction ( action ) ;
304
300
}
@@ -321,9 +317,78 @@ class AssetPublishing extends Construct {
321
317
}
322
318
}
323
319
}
320
+
321
+ /**
322
+ * This role is used by both the CodePipeline build action and related CodeBuild project. Consolidating these two
323
+ * roles into one, and re-using across all assets, saves significant size of the final synthesized output.
324
+ * Modeled after the CodePipeline role and 'CodePipelineActionRole' roles.
325
+ * Generates one role per asset type to separate file and Docker/image-based permissions.
326
+ */
327
+ private generateAssetRole ( assetType : AssetType ) {
328
+ if ( this . assetRoles [ assetType ] ) { return this . assetRoles [ assetType ] ; }
329
+
330
+ const rolePrefix = assetType === AssetType . DOCKER_IMAGE ? 'Docker' : 'File' ;
331
+ const assetRole = new iam . Role ( this , `${ rolePrefix } Role` , {
332
+ roleName : PhysicalName . GENERATE_IF_NEEDED ,
333
+ assumedBy : new iam . CompositePrincipal ( new iam . ServicePrincipal ( 'codebuild.amazonaws.com' ) , new iam . AccountPrincipal ( Stack . of ( this ) . account ) ) ,
334
+ } ) ;
335
+
336
+ // Logging permissions
337
+ const logGroupArn = Stack . of ( this ) . formatArn ( {
338
+ service : 'logs' ,
339
+ resource : 'log-group' ,
340
+ sep : ':' ,
341
+ resourceName : '/aws/codebuild/*' ,
342
+ } ) ;
343
+ assetRole . addToPolicy ( new iam . PolicyStatement ( {
344
+ resources : [ logGroupArn ] ,
345
+ actions : [ 'logs:CreateLogGroup' , 'logs:CreateLogStream' , 'logs:PutLogEvents' ] ,
346
+ } ) ) ;
347
+
348
+ // CodeBuild report groups
349
+ const codeBuildArn = Stack . of ( this ) . formatArn ( {
350
+ service : 'codebuild' ,
351
+ resource : 'report-group' ,
352
+ resourceName : '*' ,
353
+ } ) ;
354
+ assetRole . addToPolicy ( new iam . PolicyStatement ( {
355
+ actions : [
356
+ 'codebuild:CreateReportGroup' ,
357
+ 'codebuild:CreateReport' ,
358
+ 'codebuild:UpdateReport' ,
359
+ 'codebuild:BatchPutTestCases' ,
360
+ ] ,
361
+ resources : [ codeBuildArn ] ,
362
+ } ) ) ;
363
+
364
+ // CodeBuild start/stop
365
+ assetRole . addToPolicy ( new iam . PolicyStatement ( {
366
+ resources : [ '*' ] ,
367
+ actions : [
368
+ 'codebuild:BatchGetBuilds' ,
369
+ 'codebuild:StartBuild' ,
370
+ 'codebuild:StopBuild' ,
371
+ ] ,
372
+ } ) ) ;
373
+
374
+ // Publishing role access
375
+ const rolePattern = assetType === AssetType . DOCKER_IMAGE
376
+ ? 'arn:*:iam::*:role/*-image-publishing-role-*'
377
+ : 'arn:*:iam::*:role/*-file-publishing-role-*' ;
378
+ assetRole . addToPolicy ( new iam . PolicyStatement ( {
379
+ actions : [ 'sts:AssumeRole' ] ,
380
+ resources : [ rolePattern ] ,
381
+ } ) ) ;
382
+
383
+ // Artifact access
384
+ this . pipeline . artifactBucket . grantRead ( assetRole ) ;
385
+
386
+ this . assetRoles [ assetType ] = assetRole . withoutPolicyUpdates ( ) ;
387
+ return this . assetRoles [ assetType ] ;
388
+ }
324
389
}
325
390
326
391
function maybeSuffix ( x : string | undefined , suffix : string ) : string | undefined {
327
392
if ( x === undefined ) { return undefined ; }
328
393
return `${ x } ${ suffix } ` ;
329
- }
394
+ }
0 commit comments