Skip to content

Instantly share code, notes, and snippets.

@pandomic
Last active August 24, 2020 15:14
Show Gist options
  • Save pandomic/3a4d534a85138b19b8b7c4e900a855d6 to your computer and use it in GitHub Desktop.
Save pandomic/3a4d534a85138b19b8b7c4e900a855d6 to your computer and use it in GitHub Desktop.
Pure nodejs script (bin) to assume a AWS IAM role (use --help for help)
#!/usr/bin/env node
const fs = require('fs');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const Writable = require('stream').Writable;
const NEWLINE_SEPARATOR = process.env.NEWLINE_SEPARATOR || "\n";
const AWS_SESSION_DURATION = process.env.AWS_SESSION_DURATION || 3600;
const mutedOutput = new Writable({
write: (chunk, encoding, callback) => callback(),
});
const readline = require('readline').createInterface({
input: process.stdin,
output: mutedOutput,
terminal: true,
});
const getHomePath = async () => {
const homePathCmdResult = await exec('cd ~/ && pwd');
return homePathCmdResult.stdout.trim();
};
const readAwsConfigFile = async (fileName) => {
const homePath = await getHomePath();
const fileContentBuffer = fs.readFileSync(`${homePath}/.aws/${fileName}`);
return fileContentBuffer.toString();
};
const parseAwsConfig = async (configContent) => {
const configLines = configContent.split(NEWLINE_SEPARATOR);
const sectionRegex = /\[(?:profile\s+)?([a-z0-9_-]+)]/i;
const paramRegex = /([a-z_]+)\s*=\s*(.+)/i;
let currentSection = null;
let parsedAwsConfig = {};
configLines.forEach((line) => {
const trimmedLine = line.trim();
if (sectionRegex.test(trimmedLine)) {
currentSection = sectionRegex.exec(trimmedLine)[1];
if (!parsedAwsConfig[currentSection]) {
parsedAwsConfig[currentSection] = {};
}
return;
}
if (paramRegex.test(trimmedLine)) {
const paramMatches = paramRegex.exec(trimmedLine);
parsedAwsConfig[currentSection][paramMatches[1]] = paramMatches[2];
}
});
return parsedAwsConfig;
};
const combineAwsConfigs = (configA, configB) => {
const configBKeys = Object.keys(configB);
return configBKeys.reduce((combinedConfig, key) => {
combinedConfig[key] = { ...combinedConfig[key], ...configB[key] };
return combinedConfig;
}, configA);
};
const resolveAwsConfigDependencies = (awsConfig) => {
const configKeys = Object.keys(awsConfig);
return configKeys.reduce((resolvedConfig, key) => {
if (resolvedConfig[key].source_profile) {
const sourceProfile = resolvedConfig[key].source_profile;
delete resolvedConfig[key].source_profile;
if (resolvedConfig[sourceProfile]) {
resolvedConfig[key] = {
...resolvedConfig[key],
...resolvedConfig[sourceProfile],
};
resolvedConfig = resolveAwsConfigDependencies(awsConfig);
}
}
return resolvedConfig;
}, awsConfig);
};
const promptInput = async (question) => {
return new Promise((resolve) => {
process.stdout.write(question);
readline.question('', (input) => {
process.stdout.write('\033c');
readline.close();
resolve(input);
})
});
};
const assumeRoleJsonParams = async (profileConfig) => {
const jsonParams = {
RoleArn: profileConfig.role_arn,
RoleSessionName: 'cli',
DurationSeconds: AWS_SESSION_DURATION,
};
if (profileConfig.mfa_serial) {
jsonParams.SerialNumber = profileConfig.mfa_serial;
jsonParams.TokenCode = await promptInput('MFA code:');
}
if (profileConfig.external_id) {
jsonParams.ExternalId = profileConfig.external_id;
}
return jsonParams;
};
const retrieveAssumedRoleParams = async (profileConfig) => {
const params = await assumeRoleJsonParams(profileConfig);
const prepared = `"${JSON.stringify(params).replace(/"/g, '\\\"')}"`;
const assumeRoleCmdResult = await exec(`aws sts assume-role --cli-input-json ${prepared}`);
return JSON.parse(assumeRoleCmdResult.stdout);
};
const exportEnvVariable = async (name, value) => {
const homePath = await getHomePath();
await exec(`echo export ${name}=${value} >> ${homePath}/.aws/assumerole`);
};
const exportAssumedRoleParams = async (assumedRoleParams) => {
const homePath = await getHomePath();
const credentials = assumedRoleParams.Credentials;
await exec(`touch ${homePath}/.aws/assumerole`);
await exec(`echo '' > ${homePath}/.aws/assumerole`);
await exportEnvVariable('AWS_ACCESS_KEY_ID', credentials.AccessKeyId);
await exportEnvVariable('AWS_SECRET_ACCESS_KEY', credentials.SecretAccessKey);
await exportEnvVariable('AWS_SESSION_TOKEN', credentials.SessionToken);
await exportEnvVariable('AWS_SECURITY_TOKEN', credentials.SessionToken);
};
const printHelp = () => {
process.stdout.write(`
To assume a role:
1) Run: assume-role <role-name>
2) Run: eval $(cat ~/.aws/assumerole)
3) RUN: eval $(assume-role --clean)
To assume another role, just end current terminal session
`);
process.exit();
};
const printClean = () => {
process.stdout.write("unset AWS_ACCESS_KEY_ID\nunset AWS_SECRET_ACCESS_KEY\n");
process.exit();
};
const assumeRole = async (profileName) => {
const awsConfigContent = await readAwsConfigFile('config');
const awsConfig = await parseAwsConfig(awsConfigContent);
const awsCredentialsContent = await readAwsConfigFile('credentials');
const awsCredentials = await parseAwsConfig(awsCredentialsContent);
const combinedConfig = combineAwsConfigs(awsConfig, awsCredentials);
const resolvedConfig = resolveAwsConfigDependencies(combinedConfig);
const profileConfig = resolvedConfig[profileName];
if (!profileConfig) {
throw `Configuration for profile "${profileName}" could not be located`;
}
try {
const assumedRoleParams = await retrieveAssumedRoleParams(profileConfig);
await exportAssumedRoleParams(assumedRoleParams);
process.stdout.write("Cool, now run: eval $(cat ~/.aws/assumerole)\n");
} catch (e) {
process.stdout.write(`${e.stderr}\n`);
}
process.exit();
};
if (process.argv.includes('--help')) {
return printHelp();
}
if (process.argv.includes('--clean')) {
return printClean();
}
assumeRole(process.argv[2]);
cp assume-role.js /usr/local/bin/assume-role
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment