Created
January 12, 2021 22:57
-
-
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
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
// 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