Last active
August 24, 2020 15:14
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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