Skip to content

Instantly share code, notes, and snippets.

@bennyrw
Created September 23, 2022 09:53
Show Gist options
  • Save bennyrw/4c6b18221611332605ea91474ae04f10 to your computer and use it in GitHub Desktop.
Save bennyrw/4c6b18221611332605ea91474ae04f10 to your computer and use it in GitHub Desktop.
Generate AWS credentials file content from SSO

Script that adds a button to the AWS SSO landing page that will generate the content of the ~/.aws/credentials file and copy it to the clipboard.

It's most convenient to run this from a bookmarklet in your browser.

Bookmarklets must be URI encoded and start with 'javascript:'. You can generate the bookmarklet content with Node like this:

node -e "console.log('javascript:' + encodeURIComponent(require('fs').readFileSync('generateAwsCredsFile.js').toString()))" > ./generateAwsCredsFile.bookmarklet.js
//
// Intended to be used as a bookmarklet that is executed when on the AWS Single Sign-On landing page,
// this script scans the named SSO application and its accounts (specified below) and generates the
// ~/.aws/credentials content and copies it to the clipboard ready to be used.
//
// the SSO application to use
const APPLICATION_TITLE = "AWS Account";
// specify the names of the accounts to include (case sensitive). Any that are not found will be ignored.
const ACCOUNTS = ["account1", "account2"]
const waitFor = async (predicate, attempts = 10, delayMs = 250) => {
for (let i = 0; i < attempts; ++i) {
const value = predicate();
if (Boolean(value)) {
return;
}
await new Promise(r => setTimeout(r, delayMs));
}
console.error(`Predicate check failed after ${attempts} attempts`);
}
const expandApplication = async (title) => {
const application = [...document.querySelectorAll("portal-application")].find(el => el.getAttribute("title") === title);
if (!application) {
console.error(`Could not find application titled '${title}'`);
}
else if (!application.className.includes("selected")) {
application.click();
waitFor(() => application.className.includes("selected"));
}
}
const findAccountSections = (...accounts) => {
const allSections = [...document.querySelectorAll(".portal-instance-section")];
const matchedSections = allSections.reduce((acc, section) => {
const name = section.querySelector(".instance-block")?.querySelector(".name")?.textContent;
if (accounts.includes(name)) {
acc[name] = section;
}
return acc;
}, {});
accounts.forEach(name => {
if (!matchedSections[name]) {
console.warn(`Couldn't find account named ${name}, ignoring...`);
}
});
return matchedSections;
}
const findCredsLink = (section) => section.querySelector("#temp-credentials-button");
const expandAccountSection = async (section) => {
const credsLink = findCredsLink(section);
if (!credsLink) {
section.querySelector(".instance-section")?.click();
await waitFor(() => findCredsLink(section));
}
}
const collapseAccountSection = async (section) => {
const credsLink = findCredsLink(section);
if (credsLink) {
section.querySelector(".instance-section")?.click();
await waitFor(() => !findCredsLink(section));
}
}
const getCredsDialog = () => document.querySelector("creds-modal");
const getCreds = (logErrors = true) => {
const dialog = getCredsDialog();
if (!dialog) {
if (logErrors) {
console.error("Credential dialog not found")
}
return null;
}
const creds = {
accessKeyId: getCredsDialog()?.querySelector("#accessKeyId")?.value,
secretAccessKey: getCredsDialog()?.querySelector("#secretAccessKey")?.value,
sessionToken: getCredsDialog()?.querySelector("#sessionToken")?.value,
};
for (const k of Object.keys(creds)) {
if (!creds[k]) {
if (logErrors) {
console.error(`Missing value for ${k}`);
}
return null;
}
}
return creds;
}
const openCredsDialog = async (section) => {
const credsLink = findCredsLink(section);
if (!credsLink) {
console.error('Credentials link not found in section');
return;
}
credsLink.click();
await waitFor(() => getCreds(false));
}
const closeCredsDialog = async () => {
getCredsDialog()?.querySelector(".close")?.click();
await waitFor(() => !getCredsDialog());
}
const formatAccountCreds = (name, creds) => `[${name}]\naws_access_key_id=${creds?.accessKeyId}\naws_secret_access_key=${creds?.secretAccessKey}\naws_session_token=${creds?.sessionToken}\n`;
const getCredsFileContent = async () => {
expandApplication(APPLICATION_TITLE);
const namedSections = findAccountSections(...ACCOUNTS);
let content = "";
for (const name of Object.keys(namedSections)) {
const section = namedSections[name];
await expandAccountSection(section);
await openCredsDialog(section);
const creds = getCreds();
content += formatAccountCreds(name, creds) + '\n';
await closeCredsDialog(section);
await collapseAccountSection(section);
};
return content;
}
const installCopyToClipboardButton = () => {
// can't use navigator.clipboard directly from console, has to be from user interaction
// so create a button that will do this
const initialButtonText = "📋 Copy ~/.aws/credentials content";
const button = document.createElement("button");
button.textContent = initialButtonText;
button.style.fontSize = "14px"
button.onclick = async () => {
try {
button.disabled = true;
button.textContent = "📋 Generating...";
const credsContent = await getCredsFileContent();
navigator.clipboard.writeText(credsContent);
console.log("Copied ~/.aws/credentials content to clipboard");
} finally {
button.textContent = initialButtonText;
button.disabled = false;
}
}
const portalApplicationList = document.querySelector("portal-application-list");
portalApplicationList.parentElement.insertBefore(button, portalApplicationList);
}
installCopyToClipboardButton();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment