Skip to content

Instantly share code, notes, and snippets.

@a-h

a-h/cdk.go Secret

Created September 2, 2021 19:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save a-h/71c9cd7f35165b0ff07545c49ef6a65e to your computer and use it in GitHub Desktop.
Save a-h/71c9cd7f35165b0ff07545c49ef6a65e to your computer and use it in GitHub Desktop.
Go CDK CI User and Permissions Boundary
package main
import (
"fmt"
"os"
"github.com/aws/aws-cdk-go/awscdk"
"github.com/aws/aws-cdk-go/awscdk/awsiam"
"github.com/aws/constructs-go/constructs/v3"
"github.com/aws/jsii-runtime-go"
)
type CIStackProps struct {
awscdk.StackProps
ApplicationName string
}
func NewCIStack(scope constructs.Construct, id string, props *CIStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)
pb := addPermissionsBoundary(stack, props)
user := addCIUser(stack, props)
pb.AttachToUser(user)
return stack
}
func addCIUser(stack constructs.Construct, props *CIStackProps) (user awsiam.User) {
// Create a role creator role.
roleCreatorPolicy := awsiam.NewManagedPolicy(stack, jsii.String("CIRoleCreator"), &awsiam.ManagedPolicyProps{
ManagedPolicyName: jsii.String("CIRoleCreator"),
Description: jsii.String("Allows CI users to create roles for Lambda functions, should be used with the permission boundary."),
})
roleCreatorPolicy.AddStatements(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
Sid: jsii.String("passRole"),
Effect: awsiam.Effect_ALLOW,
Actions: jsii.Strings(
"iam:PassRole*",
"iam:GetRole",
"iam:CreateRole",
"iam:PutRolePolicy",
"iam:DeleteRolePolicy",
"iam:DeleteRole",
"iam:List*",
"iam:SimulatePrincipalPolicy",
"iam:AttachRolePolicy",
"iam:DetachRolePolicy",
"iam:PutRolePermissionsBoundary",
),
Resources: jsii.Strings("*"),
}))
// Create a group for CI users.
group := awsiam.NewGroup(stack, jsii.String("ci-users"), &awsiam.GroupProps{
GroupName: jsii.String("ci-users"),
ManagedPolicies: &[]awsiam.IManagedPolicy{
awsiam.ManagedPolicy_FromAwsManagedPolicyName(jsii.String("PowerUserAccess")),
roleCreatorPolicy,
},
})
// Create ci-user.
user = awsiam.NewUser(stack, jsii.String("ci-user"), &awsiam.UserProps{
UserName: jsii.String("ci-user"),
Groups: &[]awsiam.IGroup{
group,
},
})
// Give them an access key, and print it out.
accessKey := awsiam.NewCfnAccessKey(user, jsii.String("ci-user-accesskey"), &awsiam.CfnAccessKeyProps{
UserName: user.UserName(),
})
awscdk.NewCfnOutput(stack, jsii.String("accessKeyId"), &awscdk.CfnOutputProps{Value: accessKey.Ref()})
awscdk.NewCfnOutput(stack, jsii.String("secretAccessKey"), &awscdk.CfnOutputProps{Value: accessKey.AttrSecretAccessKey()})
return
}
func addPermissionsBoundary(stack constructs.Construct, props *CIStackProps) (pb awsiam.ManagedPolicy) {
resourceApplicationRoleWildcard := fmt.Sprintf("arn:aws:iam::%v:role/%s*", *props.Env.Account, props.ApplicationName)
// Create a permission boundary.
pb = awsiam.NewManagedPolicy(stack, jsii.String("PermissionsBoundary"), &awsiam.ManagedPolicyProps{
ManagedPolicyName: jsii.String("ci-permissions-boundary"),
Description: jsii.String("Permission boundary to limit permissions of roles created by CI/CD user."),
})
// Allow reading IAM information, and simulating policies.
pb.AddStatements(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
Sid: jsii.String("AllowIAMReadOnly"),
Effect: awsiam.Effect_ALLOW,
Actions: jsii.Strings(
"iam:Get*",
"iam:List*",
"iam:SimulatePrincipalPolicy",
),
Resources: jsii.Strings("*"),
}))
// Allow services that need a wildcard resource ID because the resource path is unknown in advance e.g. API Gateway.
pb.AddStatements(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
Sid: jsii.String("AllowServerlessServices"),
Effect: awsiam.Effect_ALLOW,
Actions: jsii.Strings(
"cognito-idp:*",
"dynamodb:*",
"ec2:CreateNetworkInterface",
"ec2:DeleteNetworkInterface",
"ec2:Describe*",
"events:*",
"kms:*",
"lambda:*",
"logs:*",
"s3:*",
"schemas:*",
"secretsmanager:GetSecretValue", // Only allow retrival of secrets.
"ses:*", // Unknown email at the moment
"sns:*",
"sqs:*",
"ecr:*",
"apprunner:*",
"ssm:*", // Alow SSM parameter store.
"states:*",
"synthetics:*",
"xray:*",
),
Resources: jsii.Strings("*"),
Conditions: conditionRestrictToRegions,
}))
// Allow CloudFormation deployment.
pb.AddStatements(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
Sid: jsii.String("AllowCloudFormationDeployment"),
Effect: awsiam.Effect_ALLOW,
Actions: jsii.Strings(
"cloudformation:CreateStack",
"cloudformation:DescribeStackEvents",
"cloudformation:DescribeStackResources",
"cloudformation:DescribeStackResource",
"cloudformation:DescribeStacks",
"cloudformation:GetTemplate",
"cloudformation:ListStackResources",
"cloudformation:UpdateStack",
"cloudformation:ValidateTemplate",
"cloudformation:DeleteStack",
),
Resources: jsii.Strings("*"),
Conditions: conditionRestrictToRegions,
}))
// Allow validation of any stack.
pb.AddStatements(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
Sid: jsii.String("AllowValidationOfAnyStack"),
Effect: awsiam.Effect_ALLOW,
Actions: jsii.Strings(
"cloudformation:ValidateTemplate",
),
Resources: jsii.Strings("*"),
Conditions: conditionRestrictToRegions,
}))
// Allow passing any roles that start with the application name to Lambda.
pb.AddStatements(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
Sid: jsii.String("AllowPassRoleToLambda"),
Effect: awsiam.Effect_ALLOW,
Actions: jsii.Strings(
"iam:PassRole",
),
Resources: jsii.Strings(resourceApplicationRoleWildcard),
Conditions: &map[string]interface{}{
"StringEquals": &map[string]interface{}{
"iam:PassedToService": jsii.String("lambda.amazonaws.com"),
},
},
}))
// Deny permissions boundary alteration.
arn := awscdk.Fn_Sub(jsii.String("arn:aws:iam::${AWS::AccountId}:policy/ci-permissions-boundary"), nil)
pb.AddStatements(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
Sid: jsii.String("DenyPermissionsBoundaryAlteration"),
Effect: awsiam.Effect_DENY,
Actions: jsii.Strings(
"iam:CreatePolicyVersion",
"iam:DeletePolicy",
"iam:DeletePolicyVersion",
"iam:SetDefaultPolicyVersion",
),
Resources: &[]*string{arn},
}))
// Deny removal of permissions boundary from any role.
pb.AddStatements(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
Sid: jsii.String("DenyPermissionsBoundaryRemoval"),
Effect: awsiam.Effect_DENY,
Actions: jsii.Strings(
"iam:DeleteRolePermissionsBoundary",
),
Resources: jsii.Strings("arn:aws:iam:::role/*"),
}))
// Allow permissions boundaries to be applied.
pb.AddStatements(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
Sid: jsii.String("AllowUpsertRoleIfPermBoundaryIsBeingApplied"),
Effect: awsiam.Effect_ALLOW,
Actions: jsii.Strings(
"iam:CreateRole",
"iam:PutRolePolicy",
"iam:PutRolePermissionsBoundary",
),
Resources: jsii.Strings("arn:aws:iam:::role/*", "arn:aws:iam:::policy/*"),
Conditions: &map[string]interface{}{
"StringEquals": &map[string]interface{}{
"iam:PermissionsBoundary": arn,
},
},
}))
// Allow roles to be deleted.
pb.AddStatements(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
Sid: jsii.String("AllowDeleteRole"),
Effect: awsiam.Effect_ALLOW,
Actions: jsii.Strings(
"iam:DetachRolePolicy",
"iam:DeleteRolePolicy",
"iam:DeleteRole",
),
Resources: jsii.Strings("arn:aws:iam:::role/*"),
}))
return pb
}
var conditionRestrictToRegions = &map[string]interface{}{
"StringEquals": &map[string]interface{}{
"aws:RequestedRegion": jsii.Strings(
"us-east-1", // Allow North Virginia for CloudFront.
"eu-west-1", // Europe.
),
},
}
func main() {
app := awscdk.NewApp(nil)
NewCIStack(app, "CIStack", &CIStackProps{
awscdk.StackProps{
Env: &awscdk.Environment{
Account: awscdk.Aws_ACCOUNT_ID(),
Region: awscdk.Aws_REGION(),
},
},
"ci",
})
app.Synth(nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment