const { RDSClient, StartDBClusterCommand, StartDBInstanceCommand, StopDBInstanceCommand, StopDBClusterCommand } = require("@aws-sdk/client-rds"); const { EC2Client, DescribeInstancesCommand, StopInstancesCommand, StartInstancesCommand } = require("@aws-sdk/client-ec2"); const { IAMClient, CreateAccessKeyCommand, ListAccessKeysCommand, DeleteAccessKeyCommand } = require("@aws-sdk/client-iam"); const { RedshiftClient, PauseClusterCommand, ResumeClusterCommand } = require("@aws-sdk/client-redshift"); const db = require("./mongo"); const utils = require("./utils"); function _getRgName(provision) { let rgName = provision.scenario.toUpperCase(); rgName = rgName.replace(/AZQMI/g, 'QMI'); rgName = rgName + "-" + provision._id.toString(); return rgName; } function _getRegion(provision) { let region = "eu-west-1"; if ( provision.deployOpts.location === 'East US') { region = "us-east-1"; } else if ( provision.deployOpts.location === 'Southeast Asia' ) { region = "ap-southeast-1"; } return region; } function _getDBType(provision) { let type = "instance"; if (provision.options && provision.options.db && provision.options.db.selected && provision.options.db.selected.value) { let selectedLower = provision.options.db.selected.value.toLowerCase(); if (selectedLower.indexOf("aurora") !== -1) { type = "cluster"; } else { type = "instance"; } } return type; } function _getDbIndentifier(provision) { var out; if ( provision.outputs['db_instance_id'] ) { out = provision.outputs['db_instance_id']; } else if (provision.outputs['db_instance_endpoint']) { out = provision.outputs['db_instance_endpoint'].split(".")[0]; } return out; } async function rotateAccessKey(provision) { if (!provision || !provision.outputs ) { console.log(`AWSCLI# rotateAccessKey - Provision not found or Provision (${provision._id}) does not contain Outputs`); return null; } var username; var prefix = ""; if (provision.outputs['iam_user_name']){ username = provision.outputs['iam_user_name']; } else if ( provision.outputs['S3_iam_user_name'] ) { prefix = "S3_"; username = provision.outputs['S3_iam_user_name']; } else if ( provision.outputs['s3_iam_user_name'] ) { prefix = "s3_"; username = provision.outputs['s3_iam_user_name']; } else { console.log(`AWSCLI# rotateAccessKey - Provision (${provision._id}) does not contain an IAM user`); return null; } try { const client = new IAMClient({ region: _getRegion(provision) }); // Step 1: Create a new access key const createKeyCommand = new CreateAccessKeyCommand({ UserName: username }); const newKey = await client.send(createKeyCommand); console.log(`AWSCLI# rotateAccessKey (${username}) - New Access Key Created:`, newKey.AccessKey); // You should now update your application with newKey.AccessKeyId and newKey.SecretAccessKey // Step 2: List existing access keys const listKeysCommand = new ListAccessKeysCommand({ UserName: username }); const keys = await client.send(listKeysCommand); // Find the old access key (assuming we only need to keep the latest one) for (const key of keys.AccessKeyMetadata) { if (key.AccessKeyId !== newKey.AccessKey.AccessKeyId) { // Step 3: Delete the old access key console.log(`AWSCLI# rotateAccessKey (${username}): Deleting old access key: ${key.AccessKeyId}`); const deleteKeyCommand = new DeleteAccessKeyCommand({ UserName: username, AccessKeyId: key.AccessKeyId, }); await client.send(deleteKeyCommand); } } console.log(`AWSCLI# rotateAccessKey (${username}): Old Access Key Deleted Successfully.`); let outputs = provision.outputs; outputs[prefix+'iam_user_access_key'] = newKey.AccessKey.AccessKeyId; outputs[prefix+'iam_user_access_secret'] = newKey.AccessKey.SecretAccessKey; return await db.provision.update(provision._id, {"outputs": outputs}); } catch (error) { console.log("AWSCLI# rotateAccessKey Error rotating access key:", error); return null; } } async function checkAccessKeys(provision) { if (!provision || !provision.outputs ) { console.log(`AWSCLI# checkAccessKeys - Provision not found or Provision (${provision._id}) does not contain Outputs`); return null; } var username; if (provision.outputs['iam_user_name']){ username = provision.outputs['iam_user_name']; } else if ( provision.outputs['S3_iam_user_name'] ) { username = provision.outputs['S3_iam_user_name']; } else if ( provision.outputs['s3_iam_user_name'] ) { username = provision.outputs['s3_iam_user_name']; } else { console.log(`AWSCLI# checkAccessKeys - Provision (${provision._id}) does not contain an IAM user`); return null; } try { const client = new IAMClient({ region: _getRegion(provision) }); // Step 1: List all access keys for the user const listKeysCommand = new ListAccessKeysCommand({ UserName: username }); const response = await client.send(listKeysCommand); console.log(`AWSCLI# checkAccessKeys (${username})`); var out = []; response.AccessKeyMetadata.forEach((key) => { // Step 2: Check if the key is older than 90 days const createdDate = new Date(key.CreateDate); const now = new Date(); const ageInDays = (now - createdDate) / (1000 * 60 * 60 * 24); key.ageInDays = ageInDays; key.is90dExpired = ageInDays > 90; out.push(key); }); if (out.length === 0) { out.push({ is90dExpired: true }); } return out; } catch (error) { console.error("Error checking access keys:", error); return null; } } async function stopDbInstance(provision, triggerUserId, isSendEmailAfter) { if (provision.statusVms === 'Stopped' || provision.statusVms === 'Stopping'){ return; } var identifier = _getDbIndentifier(provision); if ( !identifier ) { return; } await db.provision.update(provision._id.toString(), {"statusVms": "Stopping" }); db.event.add({ user: triggerUserId, provision: provision._id, type: `db.stopping` }); let region = _getRegion(provision); let type = _getDBType(provision); console.log(`AWSCLI# DB (${type})-(${identifier}) in region (${region}) stopping...`); try { const client = new RDSClient({ region: region }); var params, command; if (type === "instance") { params = { DBInstanceIdentifier: identifier }; command = new StopDBInstanceCommand(params); } else { params = { DBClusterIdentifier: identifier }; command = new StopDBClusterCommand(params); } const response = await client.send(command); console.log(`AWSCLI# DB (${type})-(${identifier}) stopped!`); //console.log(`AWSCLI# DB`, response); return response; } catch (error) { console.log("AWSCLI# ERROR stopping DB: "+identifier, error); await db.provision.update(provision._id.toString(), {"statusVms": "Running" }); return; } } async function startDbInstance(provision, triggerUserId) { if (provision.statusVms === 'Running' || provision.statusVms === 'Starting'){ return; } var identifier = _getDbIndentifier(provision); if ( !identifier ) { return; } await db.provision.update(provision._id.toString(), {"statusVms": "Starting" }); db.event.add({ user: triggerUserId, provision: provision._id, type: `db.starting` }); let region = _getRegion(provision); let type = _getDBType(provision); console.log(`AWSCLI# DB (${type})-(${identifier}) in region (${region}) starting...`); try { const client = new RDSClient({ region: region }); var params, command; if (type === "instance") { params = { DBInstanceIdentifier: identifier }; command = new StartDBInstanceCommand(params); } else { params = { DBClusterIdentifier: identifier }; command = new StartDBClusterCommand(params); } const response = await client.send(command); console.log(`AWSCLI# DB (${identifier}) started!`); await utils.afterStartVms( provision, triggerUserId, "db" ); //console.log(`AWSCLI# DB`, response); return response; } catch (error) { console.log("AWSCLI# ERROR starting DB: "+identifier, error); await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" }); return; } } async function deallocate(provision, triggerUserId, isSendEmailAfter) { let rgName = _getRgName(provision); let region = _getRegion(provision); console.log("AWSCLI# Stopping EC2s for resource group: "+rgName); var client = new EC2Client({region: region}); var params = { DryRun: false, Filters: [ { Name: 'tag:Name', Values: [ `fort-${provision._id}` ] }, ] }; try { var command = new DescribeInstancesCommand(params); const data = await client.send(command); if (data && data.Reservations && data.Reservations.length) { var ec2Ids = data.Reservations[0].Instances.map(ec2=> ec2.InstanceId); console.log("AWSCLI# Stopping Ec2s ids...", ec2Ids); params = { DryRun: false, InstanceIds: ec2Ids }; command = new StopInstancesCommand(params); await client.send(command); await utils.afterStopVms( provision, triggerUserId, isSendEmailAfter ); } } catch (error) { console.log("AWSCLI# ERROR describing/stopping EC2s: "+rgName, error); await db.provision.update(provision._id.toString(), {"statusVms": "Running" }); } } async function start(provision, triggerUserId) { let rgName = _getRgName(provision); let region = _getRegion(provision); console.log("AWSCLI# Stopping EC2s for resource group: "+rgName); var client = new EC2Client({region: region}); var params = { DryRun: false, Filters: [ { Name: 'tag:Name', Values: [ `fort-${provision._id}` ] }, ] }; try { var command = new DescribeInstancesCommand(params); const data = await client.send(command); if (data && data.Reservations && data.Reservations.length) { var ec2Ids = data.Reservations[0].Instances.map(ec2=> ec2.InstanceId); console.log("AWSCLI# Starting Ec2s ids...", ec2Ids); params = { DryRun: false, InstanceIds: ec2Ids }; command = new StartInstancesCommand(params); await client.send(command); console.log("AWSCLI# Ec2s started!"); await utils.afterStartVms( provision, triggerUserId ); } } catch (error) { console.log("AWSCLI# ERROR describing/starting EC2s: "+rgName, error); await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" }); } } // Stop Redshift cluster async function stopRedshiftCluster(provision, triggerUserId) { /*if (!provision || !provision.outputs || !provision.outputs['redshift_cluster_id']) { console.log('AWSCLI# stopRedshiftCluster - Missing Redshift cluster ID in provision'); return null; }*/ const clusterId = "qmi-"+provision._id.toString(); const region = _getRegion(provision); try { const client = new RedshiftClient({ region }); const command = new PauseClusterCommand({ ClusterIdentifier: clusterId }); const response = await client.send(command); console.log(`AWSCLI# Redshift cluster (${clusterId}) paused.`); await utils.afterStopVms( provision, triggerUserId, false, "redshift-cluster" ); return response; } catch (error) { console.log(`AWSCLI# ERROR pausing Redshift cluster: ${clusterId}`, error); return null; } } // Start Redshift cluster async function startRedshiftCluster(provision, triggerUserId) { /*if (!provision || !provision.outputs || !provision.outputs['redshift_cluster_id']) { console.log('AWSCLI# startRedshiftCluster - Missing Redshift cluster ID in provision'); return null; }*/ const clusterId = "qmi-"+provision._id.toString(); const region = _getRegion(provision); try { const client = new RedshiftClient({ region }); const command = new ResumeClusterCommand({ ClusterIdentifier: clusterId }); const response = await client.send(command); console.log(`AWSCLI# Redshift cluster (${clusterId}) resumed.`); await utils.afterStartVms( provision, triggerUserId, "redshift-cluster" ); return response; } catch (error) { console.log(`AWSCLI# ERROR resuming Redshift cluster: ${clusterId}`, error); return null; } } module.exports.deallocate = deallocate; module.exports.start = start; module.exports.stopDbInstance = stopDbInstance; module.exports.startDbInstance = startDbInstance; module.exports.rotateAccessKey = rotateAccessKey; module.exports.checkAccessKeys = checkAccessKeys; module.exports.stopRedshiftCluster = stopRedshiftCluster; module.exports.startRedshiftCluster = startRedshiftCluster;