Last active
September 1, 2023 05:24
-
-
Save jeiea/b3a8b03aea3ecd0f9b5f17b595606a0a to your computer and use it in GitHub Desktop.
Why should I write this
This file contains hidden or 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
import { parse } from "https://deno.land/x/[email protected]/mod.ts"; | |
import { parseFlags } from "https://deno.land/x/[email protected]/flags/mod.ts"; | |
type Config = { profile?: string; serial?: string }; | |
let fileConfig: undefined | Config; | |
if (import.meta.main) { | |
await main(); | |
} | |
async function main() { | |
const { flags, literal } = parseFlags(Deno.args, { | |
flags: [ | |
{ name: "profile", aliases: ["p"], type: "string" }, | |
{ name: "serial", aliases: ["s"], type: "string" }, | |
], | |
}); | |
const shell = getShell(); | |
const profile = (flags.profile as string | undefined) ?? | |
(await getProfileFromConfigFile()) ?? | |
"default"; | |
const serial = (flags.serial as string | undefined) ?? | |
await getSerialFromConfigFile(); | |
const otp = literal?.[0] ?? promptOtp(); | |
if (!serial) { | |
console.log( | |
`Please set mfa serial string in ~/.aws.config like the following. | |
[easy-mfa] | |
profile = default | |
serial = arn:aws:iam::123456789012:mfa/username`, | |
); | |
Deno.exit(1); | |
} | |
await spawnShellWithEnvironment(profile, serial, otp, shell); | |
} | |
async function getProfileFromConfigFile() { | |
try { | |
const { profile } = await getFileConfig(); | |
return profile; | |
} catch (error) { | |
console.error(`Cannot read serial in ~/.aws/config: ${error}`); | |
Deno.exit(2); | |
} | |
} | |
function promptOtp() { | |
for (let trial = 0; trial < 3; trial++) { | |
const otp = prompt("Input OTP:")?.trim() ?? ""; | |
if (otp.length !== 6) { | |
console.log("OTP length should be 6, but it was not. Please retype."); | |
continue; | |
} | |
return otp; | |
} | |
console.error("OTP input failure. exit."); | |
Deno.exit(3); | |
} | |
async function spawnShellWithEnvironment( | |
profile: string, | |
serial: string, | |
otp: string, | |
shell: string, | |
) { | |
const args = [ | |
...["sts", "get-session-token"], | |
...["--profile", profile], | |
...["--serial-number", serial], | |
...["--token-code", otp], | |
...["--duration", "129600"], | |
]; | |
const command = new Deno.Command("aws", { | |
args, | |
}); | |
const result = await command.output(); | |
const decoder = new TextDecoder(); | |
const maybeJson = decoder.decode(result.stdout); | |
if (!result.success) { | |
console.log(`> aws ${args.join(" ")}`); | |
console.log(maybeJson); | |
console.error(decoder.decode(result.stderr)); | |
Deno.exit(4); | |
} | |
const { Credentials: { AccessKeyId, SecretAccessKey, SessionToken } } = JSON | |
.parse(maybeJson) as { | |
Credentials: { | |
AccessKeyId: string; | |
SecretAccessKey: string; | |
SessionToken: string; | |
Expiration: string; | |
}; | |
}; | |
Deno.env.set("AWS_ACCESS_KEY_ID", AccessKeyId); | |
Deno.env.set("AWS_SECRET_ACCESS_KEY", SecretAccessKey); | |
Deno.env.set("AWS_SESSION_TOKEN", SessionToken); | |
const subShell = new Deno.Command(shell); | |
subShell.spawn(); | |
} | |
function getShell() { | |
const shell = Deno.env.get("SHELL"); | |
if (!shell) { | |
console.warn("Cannot read $SHELL, it'll use /bin/bash"); | |
return "/bin/bash"; | |
} | |
return shell; | |
} | |
async function getSerialFromConfigFile(): Promise<string | undefined> { | |
try { | |
const { serial } = await getFileConfig(); | |
return serial; | |
} catch (error) { | |
console.error(`Cannot read serial in ~/.aws/config: ${error}`); | |
Deno.exit(2); | |
} | |
} | |
async function getFileConfig(): Promise<Config> { | |
if (fileConfig) { | |
return fileConfig; | |
} | |
try { | |
return (fileConfig = await readConfigFile()); | |
} catch { | |
return (fileConfig = {}); | |
} | |
} | |
async function readConfigFile() { | |
const home = Deno.env.get("HOME"); | |
const ini = await Deno.readTextFile(`${home}/.aws/config`); | |
return parse(ini)?.["easy-mfa"] ?? {}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment