{"info":{"_postman_id":"89c8cd9d-e285-46d7-b4bc-bebfd1ac3874","name":"AWS IAM MFA Audit","description":"<html><head></head><body><p>A simple collection to enforce the presence of MFA for human users in an AWS organization.</p>\n<p>This is done as follows:</p>\n<ol>\n<li>List all human users (users with console sign in access)</li>\n<li>Stringify and save the acquired list of human users to an environment variable.</li>\n<li>Using <code>postman.setNextRequest</code>, iterate over the list of users, maintaining an <code>index</code> environment variable to represent the current user being audited.</li>\n<li>Fetch the MFA device details to determine the current MFA state for the current user. If MFA is disabled, fail a test and add the user's name to a separate list for users that do not have MFA enabled.</li>\n<li>Compile the non-MFA list users to be sent to Slack as a daily report.</li>\n</ol>\n<p>The following environment variables are required by this collection:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>SNo</th>\n<th>Variable</th>\n<th>Description</th>\n<th>Required</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>1</td>\n<td>id</td>\n<td>The AWS Access Key ID</td>\n<td>Yes</td>\n</tr>\n<tr>\n<td>2</td>\n<td>key</td>\n<td>The AWS Access Key Secret</td>\n<td>Yes</td>\n</tr>\n<tr>\n<td>3</td>\n<td>slack_channel</td>\n<td>The # prefixed channel name to send Slack reports to</td>\n<td>Yes</td>\n</tr>\n<tr>\n<td>4</td>\n<td>slack_webhook_url</td>\n<td>The Slack webhook URL for sending Slack reports</td>\n<td>No</td>\n</tr>\n<tr>\n<td>5</td>\n<td>slack_username</td>\n<td>The username with which to send notifications. Defaults to aws-iam-audit-bot</td>\n<td>No</td>\n</tr>\n<tr>\n<td>6</td>\n<td>slack_icon</td>\n<td>The icon used for the Slack message. Defaults to :closed_lock_with_key:</td>\n<td>No</td>\n</tr>\n</tbody>\n</table>\n</div><p>Note: While creating the AWS access key and ID, ensure the following:</p>\n<ol>\n<li>The access key id and secret are not associated with an account that has console login access.</li>\n<li>The access key id and secret must be associated with a non-console login user that has the following permissions:</li>\n</ol>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n          \"Sid\": \"GrantIAMUserMFAAccess\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"iam:ListUsers\",\n                \"iam:ListMFADevices\"\n            ],\n            \"Resource\": \"*\"\n        }\n    ]\n}\n</code></pre><p>More reading:</p>\n<ol>\n<li>To enforce MFA for your AWS accounts, see <a href=\"http://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_users-self-manage-mfa-and-creds.html\">http://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_users-self-manage-mfa-and-creds.html</a></li>\n</ol>\n</body></html>","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","toc":[],"owner":"500773","collectionId":"89c8cd9d-e285-46d7-b4bc-bebfd1ac3874","publishedId":"6Z5MBAe","public":true,"customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"EF5B25"},"publishDate":"2020-09-08T15:36:30.000Z"},"item":[{"name":"List Users","event":[{"listen":"prerequest","script":{"type":"text/javascript","exec":["pm.environment.get('users') && pm.environment.unset('users');","pm.environment.get('badUsers') && pm.environment.unset('badUsers');",""],"id":"7d7a4442-0c48-4fa5-85fb-a088267432cc"}},{"listen":"test","script":{"type":"text/javascript","exec":["var users,","    result = _.get(xml2Json(responseBody), 'ListUsersResponse.ListUsersResult'),","    newUsers = _.filter(_.get(result, 'Users.member', []), 'PasswordLastUsed');","","try {","    users = JSON.parse(pm.environment.get('users') || '[]');","    tests['User list should be valid'] = _.isArray(users);","}","catch (e) {","    users = [];","    tests[`Should have a valid list of user records ${e.message}`] = false;","    console.error(e);","}","","// prevent user records from previous/manual runs from interfering with the current run","!pm.environment.get('marker') && pm.environment.unset('users');","","tests['Should be OK'] = responseCode.code === 200;","tests['Should have a valid response'] = !_.isEmpty(result);","","pm.environment.set('users', JSON.stringify(_.isArray(users) ? users.concat(newUsers) : newUsers));",""],"id":"45324f81-1df9-4230-b6a1-4f25709a01a9"}}],"id":"04df41b7-e21b-47aa-a915-cd3b95e0fadd","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"awsv4","awsv4":{"basicConfig":[{"key":"accessKey","value":"{{id}}"},{"key":"secretKey","value":"{{key}}"}],"advancedConfig":[{"key":"region","value":"<region>"},{"key":"service","value":"<service>"}]},"isInherited":false},"method":"GET","header":[{"key":"Content-Type","value":"application/x-www-form-urlencoded"},{"key":"X-Amz-Date","value":"20170901T145542Z"},{"key":"Authorization","value":"AWS4-HMAC-SHA256 Credential=AKIAJSC5VPELVYZXCMLA/20170901/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=53ddcb5962268601b20c61fccb476dda9cfa418d6111a0d34e459e1a14d346ae"}],"url":"https://iam.amazonaws.com?Action=ListUsers&Version=2010-05-08&MaxItems=1000","description":"<p>A simple <code>GET</code> request to fetch all users under the AWS organization for Postman. This is step one in the described process. Since AWS users can be configured to not have console sign in access, the fetched list is reducued to users with valid console passwords only. (Bot users should not have console sign in access, so the presence of the last password used timestamp will indicate the current user is a human user).</p>\n","urlObject":{"protocol":"https","host":["iam","amazonaws","com"],"query":[{"key":"Action","value":"ListUsers"},{"key":"Version","value":"2010-05-08"},{"key":"MaxItems","value":"1000"}],"variable":[]}},"response":[],"_postman_id":"04df41b7-e21b-47aa-a915-cd3b95e0fadd"},{"name":"List MFA devices","event":[{"listen":"prerequest","script":{"type":"text/javascript","exec":["var user,","    users,","    index = (Number(pm.environment.get(\"index\")) + 1) || 0;","","pm.environment.set(\"index\", index);","","try {","    users = JSON.parse(pm.environment.get(\"users\") || '[]');","    tests['User list must be valid'] = !_.isEmpty(users);","    pm.environment.set('user', _.get(users, [index, 'UserName']));","} catch (e) {","    console.error(e);","}",""],"id":"e2887f35-2984-4326-9717-ef52bf08440a"}},{"listen":"test","script":{"type":"text/javascript","exec":["var url = require('url'),","","    users,","    hasMFA,","    badUsers,","    user = pm.environment.get('user'),","    index = Number(pm.environment.get('index')) || 0,","    slackUrl = pm.environment.get('slack_webhook_url'),","    urlObj = slackUrl && url.parse(slackUrl),","    result = _.get(xml2Json(pm.response.text()), 'ListMFADevicesResponse.ListMFADevicesResult.MFADevices.member', []);","","tests['Should be OK'] = pm.response.code === 200;","","try {","    users = JSON.parse(pm.environment.get('users') || '[]');","    tests['Should have a non-empty array of user records'] = _.isArray(users) && !_.isEmpty(users);","}","catch (e) {","    users = [];","    tests['Existing user records are valid'] = false;","    console.error(e.message);","}","","try {","    badUsers = JSON.parse(pm.environment.get('badUsers') || '[]');","    tests['Bad user records are valid'] = _.isArray(badUsers);","}","catch (e) {","    badUsers = [];","    tests['Bad user records are valid'] = false;","    console.error(e.message);","}","","tests[`${user} should have MFA enabled`] = (hasMFA = !_.isEmpty(result));","","// Update the list of bad users if needed","if (!hasMFA) {","    badUsers.push(user);","    pm.environment.set('badUsers', JSON.stringify(badUsers));","}","","// Loop back over the current request if there are more user records to be audited","if (index < (_.size(users) - 1)) {","    return postman.setNextRequest(request.name);","}","if (urlObj && urlObj.path && (urlObj.host === 'hooks.slack.com')) {","    return;","}","postman.setNextRequest(null);"],"id":"56531d8d-0f61-4773-8755-43562de34685"}}],"id":"ea3f1a69-8540-4305-8834-c0bfb15d23f8","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"awsv4","awsv4":{"basicConfig":[{"key":"accessKey","value":"{{id}}"},{"key":"secretKey","value":"{{key}}"}],"advancedConfig":[{"key":"region","value":"<region>"},{"key":"service","value":"<service>"}]},"isInherited":false},"method":"GET","header":[{"key":"Content-Type","value":"application/x-www-form-urlencoded"},{"key":"X-Amz-Date","value":"20170901T152033Z"},{"key":"Authorization","value":"AWS4-HMAC-SHA256 Credential=AKIAJSC5VPELVYZXCMLA/20170901/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=cd757bfe3bd0ca3f4b30f762e2124a32a1a9ff6c36523c88e974b2fe72c8b33d"}],"url":"https://iam.amazonaws.com?Action=ListMFADevices&Version=2010-05-08&MaxItems=1000&UserName={{user}}","description":"<p>A simple <code>GET</code> request to fetch all MFA devices in use under the Postman org (steps three, four in the process). This is done one user at a time, so <code>postman.setNextRequest</code> is used to handle looping over this list of users (obtained in the previous step).</p>\n","urlObject":{"protocol":"https","host":["iam","amazonaws","com"],"query":[{"key":"Action","value":"ListMFADevices"},{"key":"Version","value":"2010-05-08"},{"key":"MaxItems","value":"1000"},{"key":"UserName","value":"{{user}}"}],"variable":[]}},"response":[],"_postman_id":"ea3f1a69-8540-4305-8834-c0bfb15d23f8"},{"name":"Send IAM MFA Audit report to Slack","event":[{"listen":"prerequest","script":{"type":"text/javascript","exec":["var err,","    users,","    badUsers,","    red = '#ff0000',","    blankArrayStr = '[]',","    slackBody = {","      channel: pm.environment.get('slack_channel') || '#<default-slack-channel-name-goes-here>',","      username: pm.environment.get('slack_username') || 'aws-iam-audit-bot',","      icon_emoji: pm.environment.get('slack_icon') || ':closed_lock_with_key:'","    };","","try {","    users = JSON.parse(pm.environment.get('users') || blankArrayStr);","}","catch (e) {","    users = [];","    console.error(e.message);","}","","try {","    badUsers = JSON.parse(pm.environment.get('badUsers') || blankArrayStr);","}","catch (e) {","    err = e;","    badUsers = [];","    console.error(e.message);","}","","if (err) {","    slackBody.attachments = [];","    slackBody.text = `There was an error auditing AWS accounts. ${err.message || err}`;","}","else {","    slackBody.text = badUsers.length ? `${badUsers.length} ${users.length ? 'out of ' + users.length + ' ' : ''}AWS users do not have MFA enabled.`","        : 'All AWS users have MFA enabled! :tada:';","    slackBody.attachments = _.map(badUsers, function (user, index) {","        var value = `${index + 1}. @${user}`;","","        return {","            text: value,","            fallback: value,","            color: red","        };","    });","}","","pm.environment.set('slack_body', JSON.stringify(slackBody));"],"id":"7aa2b010-7eb7-4b9c-89ab-f6046d23d50e"}},{"listen":"test","script":{"type":"text/javascript","exec":["tests['Should have sent the report successfully'] = pm.response.code === 200;","tests['Should have responded correctly'] = pm.response.text() === 'ok';","","pm.environment.unset('user');","pm.environment.unset('users');","pm.environment.unset('index');",""],"id":"a92585e3-6f45-41a4-b634-8ccd42096844"}}],"id":"c594f71c-cd08-4e42-8eac-2d53fc42e07e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{{slack_body}}"},"url":"{{slack_webhook_url}}","description":"<p>A simple <code>POST</code> requst to send reports to Slack. This report is merely a list of all users that do not have MFA enabled.</p>\n","urlObject":{"host":["{{slack_webhook_url}}"],"query":[],"variable":[]}},"response":[],"_postman_id":"c594f71c-cd08-4e42-8eac-2d53fc42e07e"}]}