Skip to content

feat: rplt 828 bucket policy update #11553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/deployment-service/cdk/lib/cdk-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { ResolveProductionS3BucketPermissionsCustomResource } from './resolve-pr
import { ResolveProductionOACCustomResource } from './resolve-production-OAC-custom-resource'
import { ResolveProductionS3BucketPoliciesCustomResource } from './resolve-production-S3-bucket-policies-custom-resource'
import { DnsCertificateUpdate } from './dns-certificate-update'
import { ResolveS3BucketPolicyConditionsCustomResource } from './resolve-s3-bucket-policy-conditions-custom-resource'

export const databaseName = 'deployment_service'

Expand Down Expand Up @@ -310,4 +311,10 @@ export const createStack = async () => {
buckets,
iaasAccountId: usercodeStack.account,
})

new ResolveS3BucketPolicyConditionsCustomResource(usercodeStack, 'resolve-s3-bucket-policy-conditions', {
buckets,
iaasAccountId: usercodeStack.account,
paasAcountId: stack.account,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Construct } from 'constructs'
import { Stack, aws_s3, aws_lambda, aws_iam, CustomResource, custom_resources, aws_logs, Duration } from 'aws-cdk-lib'
import { BucketNames } from './create-S3-bucket'

export class ResolveS3BucketPolicyConditionsCustomResource extends Construct {
constructor(
scope: Stack,
id: string,
{
buckets,
iaasAccountId,
paasAcountId,
}: { buckets: Record<string, aws_s3.IBucket>; iaasAccountId: string; paasAcountId: string },
) {
super(scope, id)

const envStage = process.env.APP_STAGE === 'production' ? 'prod' : 'dev'

const liveBucket = aws_s3.Bucket.fromBucketName(this, 'lookup-live-bucket', `${BucketNames.LIVE}-${envStage}`)
const logBucket = aws_s3.Bucket.fromBucketName(this, 'lookup-log-bucket', `${BucketNames.LOG}-${envStage}`)
const repoBucket = aws_s3.Bucket.fromBucketName(this, 'lookup-repo-bucket', `${BucketNames.REPO_CACHE}-${envStage}`)
const versionBucket = aws_s3.Bucket.fromBucketName(
this,
'lookup-version-bucket',
`${BucketNames.VERSION}-${envStage}`,
)

const resolveProductionS3Lambda = new aws_lambda.Function(
scope,
'resolve-S3-bucket-policy-conditions-custom-resource',
{
handler: 'dist/resolve-s3-bucket-policy-conditions.resolveS3BucketPolicyConditions',
code: aws_lambda.Code.fromAsset('bundle/resolve-s3-bucket-policy-conditions.zip'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could use Nodejsfunction here instead of bundling it then using the zip

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oooo really? I'll check that out!

memorySize: 1024,
timeout: Duration.seconds(60),
runtime: aws_lambda.Runtime.NODEJS_18_X,
environment: {
PAAS_ACCOUNT_ID: paasAcountId,
IAAS_ACCOUNT_ID: iaasAccountId,
BUCKETS: [liveBucket.bucketName, logBucket.bucketName, repoBucket.bucketName, versionBucket.bucketName].join(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it won't re-run if you change the env vars btw, don't know if you care about that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naa all good +1

',',
),
},
},
)

resolveProductionS3Lambda.addToRolePolicy(
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
resources: Object.values(buckets).map((bucket) => bucket.bucketArn),
actions: [
's3:ListBucket',
's3:PutObjectAcl',
's3:GetBucketAcl',
's3:GetObjectAcl',
's3:GetBucketLocation',
's3:GetObjectRetention',
's3:GetObjectVersionAcl',
'S3:PutBucketPolicy',
'S3:GetBucketPolicy',
's3:DeleteBucketPolicy',
's3:PutBucketPolicy',
's3:PutBucketOwnershipControls',
's3:PutBucketACL',
's3:PutBucketPublicAccessBlock',
],
}),
)

const resourceProvider = new custom_resources.Provider(scope, 'resolve-S3-bucket-policy-conditions', {
onEventHandler: resolveProductionS3Lambda,
logRetention: aws_logs.RetentionDays.TWO_WEEKS,
})

new CustomResource(scope, 'resolve-s3-bucket-policy-conditions-custom-resource', {
serviceToken: resourceProvider.serviceToken,
properties: {
fistonly: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

},
})
}
}
2 changes: 1 addition & 1 deletion packages/deployment-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"deploy": "rpt-cdk deploy cdk/cdk.ts",
"release": "echo '...skipping...'",
"synth": "rpt-cdk synth cdk/cdk.ts",
"bundle": "tsup-zip deployment-service && zip bundle/resolve-production-s3-bucket-policies.zip dist/resolve-production-s3-bucket-policies.js && zip bundle/resolve-production-s3-bucket-permissions.zip dist/resolve-production-s3-bucket-permissions.js && zip bundle/resolve-production-apply-OAC-to-all-distros.zip dist/resolve-production-apply-OAC-to-all-distros.js",
"bundle": "tsup-zip deployment-service && zip bundle/resolve-production-s3-bucket-policies.zip dist/resolve-production-s3-bucket-policies.js && zip bundle/resolve-production-s3-bucket-permissions.zip dist/resolve-production-s3-bucket-permissions.js && zip bundle/resolve-production-apply-OAC-to-all-distros.zip dist/resolve-production-apply-OAC-to-all-distros.js && zip bundle/resolve-s3-bucket-policy-conditions.zip dist/resolve-s3-bucket-policy-conditions.js",
"release:watch": "rpt-cdk watch cdk/cdk.ts",
"release:destroy": "rpt-cdk destroy cdk/cdk.ts",
"publish": "echo '...skipping...'",
Expand Down
181 changes: 181 additions & 0 deletions packages/deployment-service/src/resolve-s3-bucket-policy-conditions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import {
DeletePublicAccessBlockCommand,
GetBucketPolicyCommand,
PutBucketPolicyCommand,
PutPublicAccessBlockCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { OnEventHandler } from 'aws-cdk-lib/custom-resources/lib/provider-framework/types'

enum BucketPolicyConditions {
StringEquals = 'StringEquals',
}

enum BucketPolicyConditionKey {
'aws:SourceAccount' = 'aws:SourceAccount',
'aws:PrincipalAccount' = 'aws:PrincipalAccount',
}

type BucketPolicyCondition = {
[key in BucketPolicyConditions]: {
[key in BucketPolicyConditionKey]?: string | string[]
}
}

type BucketPolicyStatement = {
Effect: 'Allow' | 'Deny'
Action: string[]
Condition?: BucketPolicyCondition
Resource: string
Principal: {
[s: string]: string
}
}

const resolveBucketPolicyConditions =
(client: S3Client) =>
async (
bucketName: string,
modifyPolicyStatement: (policyStatement: BucketPolicyStatement) => BucketPolicyStatement,
) => {
const policyResult = await client.send(
new GetBucketPolicyCommand({
Bucket: bucketName,
}),
)

if (!policyResult.Policy) throw new Error('Policy was not provided')

const policy: {
Version: string
Statement: BucketPolicyStatement[]
} = JSON.parse(policyResult?.Policy)

await client.send(
new PutBucketPolicyCommand({
Bucket: bucketName,
Policy: JSON.stringify({
...policy,
Statement: policy.Statement.map((statement) => modifyPolicyStatement(statement)),
}),
}),
)
}

const migrateS3BucketPolicyConditions = async ({
bucketNames,
iaasAccountId,
paasAccountId,
}: {
iaasAccountId: string
paasAccountId: string
bucketNames: string[]
}) => {
const client = new S3Client()

await Promise.all(
bucketNames.map((bucketName) =>
client.send(
new DeletePublicAccessBlockCommand({
Bucket: bucketName,
}),
),
),
)

await Promise.all(
bucketNames.map((bucketName) =>
resolveBucketPolicyConditions(client)(bucketName, (statement) => ({
...statement,
Condition: statement?.Principal?.Service?.includes('cloudfront')
? undefined
: {
[BucketPolicyConditions.StringEquals]: {
[BucketPolicyConditionKey['aws:PrincipalAccount']]: [paasAccountId, iaasAccountId],
},
},
})),
),
)

await Promise.all(
bucketNames.map((bucketName) =>
client.send(
new PutPublicAccessBlockCommand({
Bucket: bucketName,
PublicAccessBlockConfiguration: {
BlockPublicPolicy: true,
BlockPublicAcls: true,
RestrictPublicBuckets: true,
IgnorePublicAcls: true,
},
}),
),
),
)
}

const rollbackS3BucketPolicyConditions = async (bucketNames: string[]) => {
const client = new S3Client()

await Promise.all(
bucketNames.map((bucketName) =>
client.send(
new DeletePublicAccessBlockCommand({
Bucket: bucketName,
}),
),
),
)

await Promise.all(
bucketNames.map((bucketName) =>
resolveBucketPolicyConditions(client)(bucketName, (statement) => {
delete statement.Condition

return statement
}),
),
)

await Promise.all(
bucketNames.map((bucketName) =>
client.send(
new PutPublicAccessBlockCommand({
Bucket: bucketName,
PublicAccessBlockConfiguration: {
BlockPublicPolicy: true,
BlockPublicAcls: true,
RestrictPublicBuckets: true,
IgnorePublicAcls: true,
},
}),
),
),
)
}

export const resolveS3BucketPolicyConditions: OnEventHandler = async (event) => {
const bucketNames = process.env.BUCKETS ? process.env.BUCKETS?.split(',') : []

const iaasAccountId = process.env.IAAS_ACCOUNT_ID
const paasAccountId = process.env.PAAS_ACCOUNT_ID

if (typeof iaasAccountId !== 'string' || typeof paasAccountId !== 'string')
throw new Error('envs for IAAS_ACCOUNT_ID or PAAS_ACCOUNT_ID was not found')

if (event.RequestType === 'Create') {
await migrateS3BucketPolicyConditions({
bucketNames,
iaasAccountId,
paasAccountId,
})
} else if (event.RequestType === 'Delete') {
await rollbackS3BucketPolicyConditions(bucketNames)
}

return {
PhysicalResourceId: event.PhysicalResourceId,
Data: {},
}
}
1 change: 1 addition & 0 deletions packages/deployment-service/tsup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export default defineConfig([
'src/resolve-production-s3-bucket-permissions.ts',
'src/resolve-production-s3-bucket-policies.ts',
'src/resolve-production-apply-OAC-to-all-distros.ts',
'src/resolve-s3-bucket-policy-conditions.ts',
],
target: 'node18',
clean: true,
Expand Down
Loading