Skip to content

Instantly share code, notes, and snippets.

@gangstead
Created January 12, 2021 22:57
Show Gist options
  • Save gangstead/4dd8b53c6fab949a4a07ea70c27febbc to your computer and use it in GitHub Desktop.
Save gangstead/4dd8b53c6fab949a4a07ea70c27febbc to your computer and use it in GitHub Desktop.
Node.js script to make report very similar to iam users home https://console.aws.amazon.com/iam/home
// Print report very similar to iam users home https://console.aws.amazon.com/iam/home
// But output is in a more easily digestable format (hint: it goes in a spreadsheet)
// 0a. Make sure you have aws cli 2.0 installed and configured
// 0b. `npm install` there should be a package.json with this, but you need lodash, moment, bluebird
// 1. Usage: `node list-users.js`
const exec = require("child_process").exec;
const _ = require("lodash");
const B = require("bluebird");
const moment = require("moment");
function execPromise(cmd) {
const child = exec(cmd);
return new Promise(function (resolve, reject) {
const stdOut = [];
child.stdout.on("data", (data) => stdOut.push(data));
child.addListener("error", reject);
child.addListener("exit", () => resolve(stdOut.join("")));
});
}
B.resolve()
// Step: get users
.then(() => {
console.debug("Getting users...");
return execPromise("aws iam list-users");
})
// Step: add user groups
.then((data) => {
console.debug("Getting groups for users...");
const users = JSON.parse(data).Users;
return B.map(
users,
(u) => {
return execPromise(
`aws iam list-groups-for-user --user-name ${u.UserName}`
).then((data) => {
const groups = JSON.parse(data).Groups.map((g) => g.GroupName);
return _.set(u, "groups", `"${groups.join(",")}"`);
});
},
{ concurrency: 3 }
);
})
// Step: add access key info
.then((users) => {
console.debug("Getting access keys for users...");
return B.map(
users,
(u) => {
return execPromise(
`aws iam list-access-keys --user-name ${u.UserName}`
).then((data) => {
const keys = _.filter(JSON.parse(data).AccessKeyMetadata, {
Status: "Active",
});
const oldest = _.get(_.minBy(keys, "CreateDate"), "CreateDate", "-");
const defaults = { PasswordLastUsed: "-" };
return _.merge(defaults, u, {
activeKeyCount: keys.length,
oldestActiveKey: oldest,
accessKeyIds: keys.map((k) => k.AccessKeyId),
});
});
},
{ concurrency: 3 }
);
})
// Step: see when access keys were last used
.then((users) => {
return B.map(
users,
(u) => {
return B.map(u.accessKeyIds, (keyId) => {
return execPromise(
`aws iam get-access-key-last-used --access-key-id ${keyId}`
).then((data) => {
return JSON.parse(data).AccessKeyLastUsed.LastUsedDate;
});
}).then((keysLastUsed) => {
const lastUsedKey = _.min(keysLastUsed);
const lastUsedKeyDays = lastUsedKey
? moment().diff(lastUsedKey, "d")
: "-";
return _.set(u, "lastUsedKeyDays", lastUsedKeyDays);
});
},
{ concurrency: 3 }
);
})
// Step: add user policies
.then((users) => {
console.debug("Getting policies for users...");
return B.map(
users,
(u) => {
return execPromise(
`aws iam list-user-policies --user-name ${u.UserName}`
).then((data) => {
const inlinePermissionCount = JSON.parse(data).PolicyNames.length;
return _.merge(u, { inlinePermissionCount });
});
},
{ concurrency: 3 }
);
})
// Step: needs MFA enabled
.then((users) => {
console.debug("Getting mfa status..");
return B.map(
users,
(u) => {
// Controversial: Users who only use access (servers, probably) keys don't need MFA
if (u.PasswordLastUsed == "-") {
return B.resolve(_.set(u, "mfa?", "-"));
}
return execPromise(
`aws iam list-mfa-devices --user-name ${u.UserName}`
).then((data) => {
const mfa = JSON.parse(data).MFADevices.length > 0;
return _.set(u, "mfa?", mfa ? "✅" : "NEEDS MFA");
});
},
{ concurrency: 3 }
);
})
// Step: add "attached" user policies, I think these are what we want to look for
// ie if someone is not in the Administrator group but has the "AdministratorAccess" policy directly on their user
.then((users) => {
console.debug("Getting attached policies for users...");
return B.map(
users,
(u) => {
return execPromise(
`aws iam list-attached-user-policies --user-name ${u.UserName}`
).then((data) => {
const attachedPolicyCount = JSON.parse(data).AttachedPolicies.length;
return _.merge(u, { attachedPolicyCount });
});
},
{ concurrency: 3 }
);
})
// Step: calculate ages of things
.then((users) => {
const now = moment();
return users.map((u) => {
const pwLastUsedDays =
u.PasswordLastUsed == "-" ? "-" : now.diff(u.PasswordLastUsed, "days");
const oldestKeyDays =
u.oldestActiveKey == "-" ? "-" : now.diff(u.oldestActiveKey, "days");
const worthless = // If you can't access anything why are you even here?
u.groups == `""` &&
u.inlinePermissionCount == 0 &&
u.attachedPolicyCount == 0
? "worthless"
: "-";
const created = moment(u.CreateDate).format("YYYY-MM-DD");
return _.merge(u, { created, pwLastUsedDays, oldestKeyDays, worthless });
});
})
// Step: output tab delimited
.then((finalUsers) => {
const users = _.map(finalUsers, (u) =>
_.pick(
u,
"UserName",
"created",
"groups",
"activeKeyCount",
"inlinePermissionCount",
"attachedPolicyCount",
"oldestKeyDays",
"lastUsedKeyDays",
"pwLastUsedDays",
"worthless",
"mfa?"
)
);
const header = Object.keys(users[0]).join(",");
const rows = users.map((u) => Object.values(u).join(","));
const out = [header, ...rows].join("\n");
console.log(out);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment