Skip to content

Commit

Permalink
fix: hardcoded ARN in qbusiness policy
Browse files Browse the repository at this point in the history
  • Loading branch information
rstrahan committed Dec 5, 2023
1 parent ff17194 commit fea527c
Showing 1 changed file with 74 additions and 30 deletions.
104 changes: 74 additions & 30 deletions bin/convert-cfn-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,38 @@ async function main() {
console.log(`Reading template from ${templatePath}...`);
const template = JSON.parse(fs.readFileSync(templatePath, 'utf8'));

// Identify all Lambda function assets in the template
console.log('Identifying Lambda function assets...');
const assets = identifyLambdaAssets(template);

// Zip and upload assets from cdk.out to new S3 location
for (let asset of assets) {
console.log(`Zipping and uploading asset: ${asset.key}`);
const zippedFilePath = await zipAsset(asset);
await uploadAsset(zippedFilePath, destinationBucket, destinationPrefix, asset.key);
// Identify all Lambda functions in the template
console.log('Identifying Lambda functions...');
const lambdas = identifyLambdas(template);

// Zip and upload code assets from cdk.out to new S3 location
for (let lambda of lambdas) {
console.log(`Zipping and uploading lambda code asset: ${lambda.codeAssetS3Key}`);
const zippedFilePath = await zipAsset(lambda);
await uploadAsset(
zippedFilePath,
destinationBucket,
destinationPrefix,
lambda.codeAssetS3Key
);
fs.unlinkSync(zippedFilePath); // Clean up local zipped file
}

// Update template with new asset paths
console.log('Updating CloudFormation template with new asset paths...');
updateTemplateLambdaAssetPaths(template, assets, destinationBucket, destinationPrefix);
// Update template with new code asset paths
console.log('Updating CloudFormation template with new code asset paths...');
updateTemplateLambdaAssetPaths(template, lambdas, destinationBucket, destinationPrefix);

// Modify Lambda roles to reference AppId parameter
console.log('Updating CloudFormation template with new resource ARNs...');
updateTemplateLambdaRolePermissions(template, lambdas);

// Remove remaining CDK vestiges
delete template.Parameters.BootstrapVersion;
delete template.Rules.CheckBootstrapVersion;

// Parameterize the environment variables
console.log('Adding parameters to CloudFormation template...');
parameterizeTemplate(template, assets);
parameterizeTemplate(template, lambdas);

// Add slack app manifest to the Outputs
console.log('Adding slack manifest to CloudFormation template...');
Expand All @@ -75,22 +84,23 @@ async function main() {
}
}

function identifyLambdaAssets(template) {
let assets = [];
function identifyLambdas(template) {
let lambdas = [];
for (let [key, value] of Object.entries(template.Resources)) {
if (value.Type === 'AWS::Lambda::Function' && value.Properties.Code?.S3Key) {
console.log(`Found asset in resource ${key}`);
assets.push({
key: value.Properties.Code.S3Key,
resourceName: key
console.log(`Found lambda function: resource ${key}`);
lambdas.push({
resourceName: key,
codeAssetS3Key: value.Properties.Code.S3Key,
roleResourceName: value.Properties.Role['Fn::GetAtt'][0]
});
}
}
return assets;
return lambdas;
}

async function zipAsset(asset) {
const assetHash = asset.key.split('.')[0];
async function zipAsset(lambda) {
const assetHash = lambda.codeAssetS3Key.split('.')[0];
const sourceDir = path.join('cdk.out', `asset.${assetHash}`);
const outPath = `${path.join('cdk.out', assetHash)}.zip`;

Expand Down Expand Up @@ -118,7 +128,7 @@ async function uploadAsset(zippedFilePath, destinationBucket, destinationPrefix,
const fileStream = fs.createReadStream(zippedFilePath);
const destinationKey = `${destinationPrefix}/${path.basename(originalKey)}`;

console.log(`Uploading zipped asset to s3://${destinationBucket}/${destinationKey}`);
console.log(`Uploading zipped code asset to s3://${destinationBucket}/${destinationKey}`);

try {
await s3Client.send(
Expand All @@ -133,15 +143,49 @@ async function uploadAsset(zippedFilePath, destinationBucket, destinationPrefix,
}
}

function updateTemplateLambdaAssetPaths(template, assets, destinationBucket, destinationPrefix) {
for (let asset of assets) {
let lambdaResource = template.Resources[asset.resourceName];
function updateTemplateLambdaAssetPaths(template, lambdas, destinationBucket, destinationPrefix) {
for (let lambda of lambdas) {
let lambdaResource = template.Resources[lambda.resourceName];
lambdaResource.Properties.Code.S3Bucket = destinationBucket;
lambdaResource.Properties.Code.S3Key = `${destinationPrefix}/${path.basename(asset.key)}`;
lambdaResource.Properties.Code.S3Key = `${destinationPrefix}/${path.basename(
lambda.codeAssetS3Key
)}`;
}
}

function replaceQAppIdResourceArn(role, arn) {
const amazonQAppResourceArn = {
'Fn::Sub': 'arn:aws:qbusiness:*:*:application/${AmazonQAppId}'
};
if (typeof arn === 'string' && arn.startsWith('arn:aws:qbusiness:*:*:application/')) {
console.log(
`Role ${role}: updating Q application arn: ${arn} to ${JSON.stringify(amazonQAppResourceArn)}`
);
arn = amazonQAppResourceArn;
}
return arn;
}

function updateTemplateLambdaRolePermissions(template, lambdas) {
for (let lambda of lambdas) {
const roleResourceName = lambda.roleResourceName;
const roleResource = template.Resources[roleResourceName];
for (let policy of roleResource.Properties.Policies) {
for (let statement of policy.PolicyDocument.Statement) {
const resource = statement.Resource;
if (Array.isArray(resource)) {
for (let i = 0; i < resource.length; i++) {
statement.Resource[i] = replaceQAppIdResourceArn(roleResourceName, resource[i]);
}
} else {
statement.Resource = replaceQAppIdResourceArn(roleResourceName, resource);
}
}
}
}
}

function parameterizeTemplate(template, assets) {
function parameterizeTemplate(template, lambdas) {
template.Parameters = {
AmazonQUserId: {
Type: 'String',
Expand All @@ -168,8 +212,8 @@ function parameterizeTemplate(template, assets) {
Description: 'Number of days to keep conversation context'
}
};
for (let asset of assets) {
let lambdaResource = template.Resources[asset.resourceName];
for (let lambda of lambdas) {
let lambdaResource = template.Resources[lambda.resourceName];
lambdaResource.Properties.Environment.Variables.AMAZON_Q_ENDPOINT = ''; // use default endpoint
lambdaResource.Properties.Environment.Variables.AMAZON_Q_USER_ID = { Ref: 'AmazonQUserId' };
lambdaResource.Properties.Environment.Variables.AMAZON_Q_APP_ID = { Ref: 'AmazonQAppId' };
Expand Down

0 comments on commit fea527c

Please sign in to comment.