We have a project that uses yaml templates to create a changeset and execute the changeset to deploy a frontend stack to two different AWS accounts. We're trying to create a codepipeline that builds and deploys a simple frontend module to the AWS account the codepipeline is in and a second account (test and prod accounts). The former we've accomplished, but when it comes to deploying across AWS accounts we're running into trouble with access and permission, and which roles to use where.
We have two service roles set up. The first role (TestCodepipelineBaseServiceRole) looks like this:
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"codepipeline:StartPipelineExecution"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"iam:*Role",
"iam:*Policy*",
"iam:*Group",
"iam:*InstanceProfile"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"ec2:Describe*",
"ec2:DescribeSecurityGroups",
"ec2:*Address",
"ec2:*SecurityGroup*",
"ec2:*Tags*",
"autoscaling:*"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"route53:GetHostedZone",
"route53:ChangeResourceRecordSets",
"route53:GetChange",
"route53:ListResourceRecordSets"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"codecommit:UploadArchive",
"codecommit:CancelUploadArchive",
"codecommit:GetBranch",
"codecommit:GetCommit",
"codecommit:GetUploadArchiveStatus"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"codebuild:BatchGetBuilds",
"codebuild:StartBuild"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"cloudfront:CreateInvalidation",
"cloudfront:GetInvalidation"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"kms:CreateKey",
"kms:*Key*",
"kms:*Alias*"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"kinesis:*",
"lambda:*",
"sqs:*",
"events:*",
"cognito-idp:*",
"dynamodb:*",
"cloudformation:*",
"s3:*",
"sns:*",
"apigateway:*",
"elasticbeanstalk:*",
"elasticloadbalancing:*",
"cloudwatch:*",
"codedeploy:*",
"logs:*",
"cloudfront:*",
"glue:*",
"ssm:*"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"sts:AssumeRole"
],
"Resource": "arn:aws:iam::{account2}:role/codepipeline-base-CodePipelineCrossAccountServiceRole",
"Effect": "Allow"
}
]
}
The second role (CodePipelineCrossAccountServiceRole) looks like this:
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"iam:*Role",
"iam:*Policy*",
"iam:*Group",
"iam:*InstanceProfile"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"ec2:Describe*",
"ec2:DescribeSecurityGroups",
"ec2:*Address",
"ec2:*SecurityGroup*",
"ec2:*Tags*",
"autoscaling:*"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"route53:GetHostedZone",
"route53:ChangeResourceRecordSets",
"route53:GetChange",
"route53:ListResourceRecordSets"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"kms:CreateKey",
"kms:*Key*",
"kms:*Alias*"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"cloudfront:CreateInvalidation",
"cloudfront:GetInvalidation"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"kinesis:*",
"lambda:*",
"sqs:*",
"events:*",
"cognito-idp:*",
"dynamodb:*",
"cloudformation:*",
"s3:*",
"sns:*",
"apigateway:*",
"elasticbeanstalk:*",
"elasticloadbalancing:*",
"cloudwatch:*",
"logs:*",
"cloudfront:*",
"glue:*",
"ecr:GetAuthorizationToken",
"ecs:*",
"states:*"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"kms:DescribeKey",
"kms:GenerateDataKey*",
"kms:Encrypt",
"kms:ReEncrypt*",
"kms:Decrypt"
],
"Resource": "arn:aws:kms:{region}:{account1}:key/{UUID}",
"Effect": "Allow"
},
{
"Action": [
"ecr:*"
],
"Resource": "arn:aws:ecr:*:{account2}:repository/myRepo/jobs",
"Effect": "Allow"
},
{
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
"Resource": "arn:aws:ecr:*:{account2}:repository/amazoncorretto11",
"Effect": "Allow"
},
{
"Action": [
"s3:GetObject*"
],
"Resource": "arn:aws:s3:::codepipeline-base-{region}-{account1}-artifacts/*",
"Effect": "Allow"
}
]
}
Trust relationships:
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::{account1}:root"
},
"Action": "sts:AssumeRole"
},
{
"Effect": "Allow",
"Principal": {
"Service": "cloudformation.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
We also have a role we're creating in the codepipeline that we are passing to each action that are CodeBuildAction.
This is the action we're adding to deploy the frontend module:
using Amazon.CDK.AWS.CodeBuild;
using Amazon.CDK.AWS.CodePipeline;
using Amazon.CDK.AWS.CodePipeline.Actions;
using Amazon.CDK.AWS.IAM;
using Common;
namespace CdkCodepipeline.StageDeployModules.Action.DeployActionsUiMainVue3;
public abstract class ActionDeployStackUiMainVue3
{
public static void Add(
AwsResourceUtil awsResourceUtil,
CdkCodepipelineStack stackCodepipeline,
Artifact_ artifactUiMainVue3Build,
IStage stageDeployModules,
IRole roleCodepipeline,
IRole roleCodeBuild,
CdkConfig config)
{
var customNameDeployStackUiMainVue3 = "deploy-stack-ui-main-vue3-" + (config.IsProd ? "prod" : "test");
// Creates an id with prod/test and branch name
var projectDeployStackUiMainVue3Id = awsResourceUtil
.NameBuilder("project", customNameDeployStackUiMainVue3)
.Build();
stageDeployModules.AddAction(new CodeBuildAction(new CodeBuildActionProps
{
ActionName = "DeployStackUiMainVue3Action",
Role = roleCodeBuild,
Input = artifactUiMainVue3Build,
RunOrder = 1,
Project = AwsResourceUtil.OverrideLogicalId(
projectDeployStackUiMainVue3Id,
new PipelineProject(
stackCodepipeline,
projectDeployStackUiMainVue3Id,
new PipelineProjectProps
{
ProjectName = projectDeployStackUiMainVue3Id,
Role = roleCodepipeline,
Environment = GlobalSettings.BuildEnvironment,
BuildSpec = BuildSpec.FromObject(new Dictionary<string, object>
{
{ "version", "0.2" },
{
"phases", new Dictionary<string, object>
{
{
"install", new Dictionary<string, object>
{
{
"runtime-versions", GlobalSettings.RuntimeVersions
},
{
"commands", new[]
{
"npm install -g aws-cdk"
}
}
}
},
{
"build", new Dictionary<string, object>
{
{
"commands", new[]
{
"cd src",
"cd Cdk",
$"cdk deploy stack-ui-main-vue3-{awsResourceUtil.BranchInfo.ShortName} --context branch={awsResourceUtil.BranchInfo.Name} --context accountId={config.AccountId} --context region={config.Region} --require-approval=never"
}
}
}
}
}
},
{
"artifacts", new Dictionary<string, object>
{
{ "base-directory", "./" },
{ "files", new[] { "**/*" } }
}
}
})
}))
}));
}
}
When we try to pass the test role as a property in the PipelineProject we get this error:
Could not assume role in target account using current credentials (which are for account {account1}) User: arn:aws:sts::{account1}:assumed-role/codepipeline-base-CodePipelineServiceRole/AWSCodeBuild{UUID} is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::{account2}:role/cdk-hnb659fds-file-publishing-role-{account2}-{region} . Please make sure that this role exists in the account. If it doesn't exist, (re)-bootstrap the environment with the right '--trust', using the latest version of the CDK CLI.
We have tried adding the following to TestCodepipelineBaseServiceRole policy which then gives the error below:
"Action": [
"sts:AssumeRole"
],
"Resource": [
"arn:aws:iam::{account2}:role/cdk-hnb659fds-deploy-role-{account2}-{region}",
"arn:aws:iam::{account2}:role/cdk-hnb659fds-file-publishing-role-{account2}-{region}"
]
"Effect": "Allow"
}
Error:
Could not assume role in target account using current credentials (which are for account {account1}) User: arn:aws:sts::{account1}:assumed-role/codepipeline-base-CodePipelineServiceRole/AWSCodeBuild-{UUID} is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::{account2}:role/cdk-hnb659fds-file-publishing-role-{account2}-{region}
When we try to pass the prod role as a property in the PipelineProject we get this error:
Failed to call UpdateProject, reason: Invalid service role: Service role account ID does not match caller's account