{"id":414,"date":"2023-01-05T14:48:24","date_gmt":"2023-01-05T14:48:24","guid":{"rendered":"https:\/\/computeoncloud.eu\/?p=414"},"modified":"2023-01-05T16:16:17","modified_gmt":"2023-01-05T16:16:17","slug":"aws-lambda-automate-analyzing-your-permissions-using-iam-access-advisor","status":"publish","type":"post","link":"https:\/\/computeoncloud.eu\/index.php\/2023\/01\/05\/aws-lambda-automate-analyzing-your-permissions-using-iam-access-advisor\/","title":{"rendered":"AWS Lambda \u2014 Automate Analyzing your Permissions using IAM Access Advisor"},"content":{"rendered":"\n<p id=\"3dd8\">As soon as I read about AWS IAM access advisor APIs, I knew this is something useful. Last week, we came across a use case where we wanted to get notified for all the IAM Roles with services, not accessed for more than 90 days.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"9271\">\u26a1TL;DR<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>AWS Lambda \u2014 list IAM Roles with services, not accessed from more than 90 days.<\/li>\n\n\n\n<li>AWS SES \u2014 get notified about all IAM Roles with the list of services not accessed for the last 90 days.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"0476\">\ud83d\udd25But, how does It work?<\/h2>\n\n\n\n<p id=\"3dd5\">We\u2019ll go through creating an AWS Lambda to automate this task using access advisor APIs provided by Python Boto3. We\u2019ll be using following access advisor APIs:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>generate_service_last_accessed_details<\/strong>&nbsp;\u2014 generates the service last accessed data for an IAM resource (user, role, group, or policy). You need to call this API first to start a job that generates the service last accessed data for the IAM resource. This API returns a JobId that you will use for the other APIs, such as get_service_last_accessed_details, to determine the status of the job completion.<\/li>\n\n\n\n<li><strong>get_service_last_accessed_details<\/strong>&nbsp;\u2014 use this to retrieve the service last accessed data for an IAM resource based on the JobID you pass in.<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\" id=\"0ea0\">Let\u2019s get started with coding!<\/h1>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Create Lambda, select Python 3.x run time and Attach IAM Role<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">{<br>  \"Version\": \"2012-10-17\",<br>  \"Statement\": [<br>    {<br>      \"Effect\": \"Allow\",<br>      \"Action\": [<br>        \"ses:SendEmail\",<br>        \"logs:CreateLogStream\",<br>        \"ses:SendRawEmail\",<br>        \"iam:GenerateServiceLastAccessedDetails\",<br>        \"iam:ListRoles\",<br>        \"iam:GetServiceLastAccessedDetails\",<br>        \"logs:CreateLogGroup\",<br>        \"logs:PutLogEvents\"<br>      ],<br>      \"Resource\": \"*\"<br>    }<br>  ]<br>}<\/pre>\n\n\n\n<p id=\"03dc\">2. Get IAM Client to get service last accessed details:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">def get_iam_client():<br>  \"\"\"<br>  Get identity and access management client<br>  \"\"\"<br>  return boto3.client('iam')<\/pre>\n\n\n\n<p id=\"9472\">3. Get SES Client for sending email to notify:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">def get_ses_client():<br>  \"\"\"<br>  Get simple email service client<br>  \"\"\"<br>  return boto3.client('ses')<\/pre>\n\n\n\n<p id=\"180e\">4. Overall Lambda Function will look like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import boto3\nfrom botocore.exceptions import ClientError\nfrom datetime import datetime, timezone\nimport traceback\nfrom time import sleep\n\nRECIPIENTS = &#91;\"recipient_user@abc.com\"]\nSENDER = \"John Doe &lt;johndoe@abc.com&gt;\"\n\nLAST_ACCESS_THRESHOLD = 90\n\ndef get_iam_client():\n  \"\"\"\n  Get identity and access management client\n  \"\"\"\n  return boto3.client(\n    'iam'\n  )\n\ndef get_ses_client():\n  \"\"\" \n  Get simple email service client\n  \"\"\"\n  return boto3.client(\n    'ses'\n  )\n\ndef get_older_iam_roles(client):\n  \"\"\" \n  Get dictionary of iam roles with servics older than 90 days\n  :param client: iam client to list roles\n  \"\"\"\n\n  Services_Access = {}\n\n  # listing arns\n  list_roles_arn = client.list_roles()\n\n  if not list_roles_arn:\n    raise Exception(\"No roles found in account\")\n\n  # iterating over arns\n  for single_arn in list_roles_arn&#91;'Roles']:\n\n    TODAYS_DAY = datetime.now(timezone.utc)\n\n    ARNS = single_arn&#91;'Arn']\n\n\n    jobid_response = client.generate_service_last_accessed_details(\n      Arn=ARNS\n    )\n\n    role_jobid = jobid_response&#91;'JobId']\n\n    service_response = client.get_service_last_accessed_details(\n      JobId=role_jobid\n    )\n\n    # checking if job is completed else wait and retry until job is completed\n    while service_response&#91;'JobStatus'] != 'COMPLETED':\n      # print(f'pending : {service_response}')\n      service_response = client.get_service_last_accessed_details(\n        JobId=role_jobid\n      )\n      sleep(1)\n\n    # getting last access services\n    last_accessed_services = service_response&#91;'ServicesLastAccessed']\n\n    # iterating over servies to get the services with last access date greater than 90\n    for last_accessed in last_accessed_services:\n      try:\n        # print(last_accessed)\n        role_lastaccess_day = last_accessed&#91;'LastAuthenticated']\n        \n        # difference between today and last access date        \n        days_difference =  TODAYS_DAY - role_lastaccess_day\n        \n        # checking if difference is greater than 90\n        check_difference = days_difference.days &gt; LAST_ACCESS_THRESHOLD \n\n\n        if check_difference:\n          Services_Access&#91;ARNS] = {}\n          Services_Access&#91;ARNS]&#91;last_accessed&#91;'ServiceName']] = days_difference.days\n\n      except Exception as e:\n        continue\n  # returning dictionary containing iam roles for services with no access &gt; 90 days \n  return Services_Access\n\ndef send_email_iam_services(client, old_iams, sender, receipients):\n  \"\"\"\n  Send email for all the iam roles with services access more than 90 days\n  \n  :param client: simple emailing service client\n  :param old_iams: iam role arns dictionary\n  :param sender: email sender\n  :param receipients: list of receipients e.g. &#91;'abc@gmail.com', 'abc123@gmail.com'] etc.\n  \"\"\"\n\n  CHARSET = \"UTF-8\"\n\n  SUBJECT = \"List of IAM Roles\"\n  \n  TABLE_START=\"\"\"&lt;table style=\"width:100%\"&gt;\n  &lt;tr&gt;\n    &lt;th&gt;Role ARN&lt;\/th&gt;\n    &lt;th&gt;Service&lt;\/th&gt; \n    &lt;th&gt;Days&lt;\/th&gt;\n  &lt;\/tr&gt; \"\"\"\n\n  TABLE_BODY = \"\"\" \"\"\"\n\n  TABLE_END = \"\"\"&lt;\/table&gt;\"\"\"\n\n  for role, services in old_iams.items():\n    TABLE_BODY +=  \"&lt;tr&gt;\"\n    for service in services.items():\n        TABLE_BODY +=  \"&lt;td&gt;\" + str(role) + \"&lt;\/td&gt;\" +\"&lt;td&gt;\"  + str(service&#91;0]) + \"&lt;\/td&gt;\" + \"&lt;td&gt;\" + str(service&#91;1]) + \"&lt;\/td&gt;\"\n    TABLE_BODY += \"&lt;\/tr&gt;\"\n\n  TOTAL_TABLE = TABLE_START + TABLE_BODY  + TABLE_END\n\n  # Try to send the email.\n  try:\n      #Provide the contents of the email.\n      response = client.send_email(\n          Destination={\n              'ToAddresses': receipients,\n          },\n          Message={\n              'Body': {\n                  'Html': {\n                      'Charset': CHARSET,\n                      'Data': TOTAL_TABLE,\n                  }\n              },\n              'Subject': {\n                  'Charset': CHARSET,\n                  'Data': SUBJECT,\n              },\n          },\n          Source=sender,\n          # If you are not using a configuration set, comment or delete the\n          # following line\n      )\n  # Display an error if something goes wrong.\t\n  except ClientError as e:\n      print(e.response&#91;'Error']&#91;'Message'])\n      raise Exception(e.response&#91;'Error']&#91;'Message'])\n  else:\n      print(\"Email sent for older IAM Roles! Message ID:\"),\n      print(response&#91;'MessageId'])\n\ndef lambda_handler(event, context):\n  try:\n    # getting iam client to use\n    iam_client = get_iam_client()\n    # getting ses client to use\n    ses_client = get_ses_client()\n    # getting dictionary of all iam roles with last access &gt; 90\n    old_iams = get_older_iam_roles(iam_client)\n    # sending email for iam roles we got in last step\n    send_email_iam_services(ses_client, old_iams, SENDER, RECIPIENTS)\n  except Exception as e:\n    traceback.print_exc()\n    raise Exception(str(e))<\/code><\/pre>\n\n\n\n<p id=\"88fb\"><em>You\u2019ll receive a clean email for all IAM roles like this:<\/em><\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/miro.medium.com\/max\/1400\/1*244S4hVpW1DtF7I-eRe9oQ.png\" alt=\"\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"961a\">Couple of things to note:<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>You may need to upload the deployment package manually with the latest boto3 version instead of using AWS imported boto3 (more info:&nbsp;<a href=\"https:\/\/aws.amazon.com\/premiumsupport\/knowledge-center\/build-python-lambda-deployment-package\/\" rel=\"noreferrer noopener\" target=\"_blank\">https:\/\/aws.amazon.com\/premiumsupport\/knowledge-center\/build-python-lambda-deployment-package\/<\/a>)<\/li>\n\n\n\n<li>Recipients should be verified SES emails if we are using SES sandbox environment.<\/li>\n\n\n\n<li>Lambda timeout should be in minutes at least as some times it takes time for AWS to process jobs.<\/li>\n\n\n\n<li>Tested on Python version 3.x<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>As soon as I read about AWS IAM access advisor APIs, I knew this is something useful. Last week, we came across a use case where we wanted to get notified for all the IAM Roles with services, not accessed for more than 90 days. \u26a1TL;DR \ud83d\udd25But, how does It work? We\u2019ll go through creating &hellip;<\/p>\n<p class=\"read-more\"> <a class=\"\" href=\"https:\/\/computeoncloud.eu\/index.php\/2023\/01\/05\/aws-lambda-automate-analyzing-your-permissions-using-iam-access-advisor\/\"> <span class=\"screen-reader-text\">AWS Lambda \u2014 Automate Analyzing your Permissions using IAM Access Advisor<\/span> Read More &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":415,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,17,18],"tags":[],"class_list":["post-414","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-aws","category-iam","category-security"],"_links":{"self":[{"href":"https:\/\/computeoncloud.eu\/index.php\/wp-json\/wp\/v2\/posts\/414","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/computeoncloud.eu\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computeoncloud.eu\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computeoncloud.eu\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/computeoncloud.eu\/index.php\/wp-json\/wp\/v2\/comments?post=414"}],"version-history":[{"count":2,"href":"https:\/\/computeoncloud.eu\/index.php\/wp-json\/wp\/v2\/posts\/414\/revisions"}],"predecessor-version":[{"id":437,"href":"https:\/\/computeoncloud.eu\/index.php\/wp-json\/wp\/v2\/posts\/414\/revisions\/437"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/computeoncloud.eu\/index.php\/wp-json\/wp\/v2\/media\/415"}],"wp:attachment":[{"href":"https:\/\/computeoncloud.eu\/index.php\/wp-json\/wp\/v2\/media?parent=414"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computeoncloud.eu\/index.php\/wp-json\/wp\/v2\/categories?post=414"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computeoncloud.eu\/index.php\/wp-json\/wp\/v2\/tags?post=414"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}