<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John Rotenstein</title>
    <description>The latest articles on DEV Community by John Rotenstein (@aws_john).</description>
    <link>https://dev.to/aws_john</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F330410%2Fd9a297ed-2a6c-416c-bbb0-24cab923ba39.jpeg</url>
      <title>DEV Community: John Rotenstein</title>
      <link>https://dev.to/aws_john</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aws_john"/>
    <language>en</language>
    <item>
      <title>Simple EC2 Stopinator in Lambda</title>
      <dc:creator>John Rotenstein</dc:creator>
      <pubDate>Mon, 25 May 2020 09:46:23 +0000</pubDate>
      <link>https://dev.to/aws/simple-ec2-stopinator-in-lambda-5goj</link>
      <guid>https://dev.to/aws/simple-ec2-stopinator-in-lambda-5goj</guid>
      <description>&lt;p&gt;A &lt;em&gt;Stopinator&lt;/em&gt; is the general term applied to a utility that can start/stop Amazon EC2 instances on a particular schedule or in particular circumstances.&lt;/p&gt;

&lt;p&gt;There are quite a few Stopinators available, such as the &lt;a href="https://aws.amazon.com/solutions/implementations/instance-scheduler/" rel="noopener noreferrer"&gt;Instance Scheduler&lt;/a&gt; from AWS. However these Stopinators tend to be quite complex, involving CloudFormation templates and databases.&lt;/p&gt;

&lt;p&gt;Therefore, I set myself a challenge to create a &lt;strong&gt;Simple Lambda Stopinator&lt;/strong&gt; to help people in situations such as &lt;a href="https://stackoverflow.com/q/61587158/174777" rel="noopener noreferrer"&gt;this StackOverflow question&lt;/a&gt; asking how to send a notification when an EC2 instance is still running and then eventually terminate it after a given period. How simple? Just paste some code into a Lambda function and you're done!&lt;/p&gt;

&lt;p&gt;I have, in fact, written two stopinators:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type 1:&lt;/strong&gt; Run this Lambda function once per night to stop/terminate tagged Amazon EC2 instances&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type 2:&lt;/strong&gt; Run this Lambda function throughout the day to notify/stop/terminate instances after a given period of time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find them in: &lt;a href="https://github.com/aws-john/simple-lambda-stopinator-for-ec2" rel="noopener noreferrer"&gt;GitHub: Simple Lambda Stopinators&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(I hope to also write another Stopinator with start/stop scheduling, but that gets a bit more complicated with weekdays/weekends and timezones.)&lt;/p&gt;

&lt;p&gt;Here's an explanation of the Stopinators and how to use them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Type 1 Stopinator: Stop/Terminate instances at night
&lt;/h2&gt;

&lt;p&gt;I originally wrote this Stopinator when I was delivering AWS training courses. I would launch Amazon EC2 instances for demonstrations during the day and would often forget to terminate them. So, I wanted a script that could &lt;strong&gt;identify instances tagged as 'temporary' and terminate them at night&lt;/strong&gt;, so things were clean for teaching the next day.&lt;/p&gt;

&lt;p&gt;However, I didn't always remember to tag the temporary instances, so I also added functionality that would &lt;strong&gt;stop unidentified instances at night&lt;/strong&gt; rather than terminating them (just in case I still needed them). This then also required the ability to &lt;strong&gt;mark instances that the Stopinator should 'ignore'&lt;/strong&gt; because I wanted them to keep running.&lt;/p&gt;

&lt;p&gt;This is how the Type 1 Stopinator works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Lambda function should be &lt;strong&gt;scheduled to run each night&lt;/strong&gt; (eg 7pm) using Amazon CloudWatch Events&lt;/li&gt;
&lt;li&gt;It looks at &lt;strong&gt;every running instance in every region&lt;/strong&gt; (or just the regions you specify)&lt;/li&gt;
&lt;li&gt;If the instance has an &lt;code&gt;Auto-Stop = Terminate&lt;/code&gt; tag, it &lt;strong&gt;terminates&lt;/strong&gt; the instance&lt;/li&gt;
&lt;li&gt;If the instance has an &lt;code&gt;Auto-Stop = Ignore&lt;/code&gt; tag, it &lt;strong&gt;does nothing&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Otherwise, it &lt;strong&gt;stops&lt;/strong&gt; the instance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it — simple, eh‽ (Yes, that's an &lt;a href="https://en.wikipedia.org/wiki/Interrobang" rel="noopener noreferrer"&gt;interrobang&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;To install it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an &lt;strong&gt;IAM Role&lt;/strong&gt; and add an in-line policy with permissions from the &lt;a href="https://github.com/aws-john/simple-lambda-stopinator-for-ec2/blob/master/iam_role.json" rel="noopener noreferrer"&gt;Stopinator IAM Role&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new &lt;strong&gt;AWS Lambda Python function&lt;/strong&gt; that uses the IAM Role&lt;/li&gt;
&lt;li&gt;Paste in the &lt;a href="https://github.com/aws-john/simple-lambda-stopinator-for-ec2/blob/master/stopinator_type_1.py" rel="noopener noreferrer"&gt;Type 1 Stopinator code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Amazon CloudWatch&lt;/strong&gt;, click &lt;strong&gt;Events&lt;/strong&gt; and add a Scedule with a &lt;a href="https://en.wikipedia.org/wiki/Cron" rel="noopener noreferrer"&gt;cron expression&lt;/a&gt; that triggers the Lambda function each night. Be careful — AWS uses the UTC timezone, so you'll need to adjust for your particular timezone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default, it will automatically stop any untagged instances. If you want to terminate instances instead of stopping them, add an &lt;code&gt;Auto-Stop = Terminate&lt;/code&gt; tag (that is, Key = &lt;code&gt;Auto-Stop&lt;/code&gt;, Value = &lt;code&gt;Terminate&lt;/code&gt;). If there are instances you don't want it to touch, use &lt;code&gt;Auto-Stop = Ignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can also edit this section to control which regions are checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Provide regions here (eg ['us-west-2', 'ap-southeast-2'], or use [] for all regions
&lt;/span&gt;&lt;span class="n"&gt;regions_to_check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. So simple! Enjoy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Type 2 Stopinator: Stop/Terminate/Notify instances after a given duration
&lt;/h2&gt;

&lt;p&gt;This Stopinator was inspired by a &lt;a href="https://stackoverflow.com/q/61587158/174777" rel="noopener noreferrer"&gt;StackOverflow question&lt;/a&gt; that was asking for a way to send notifications about a running instance, and then eventually turn it off.&lt;/p&gt;

&lt;p&gt;This AWS Lambda function needs to be scheduled to run at regular intervals, so that it continually checks whether instances have exceeded their duration. The more often it runs, the closer it will match the desired running duration.&lt;/p&gt;

&lt;p&gt;This is how the Type 2 Stopinator works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Lambda function should be &lt;strong&gt;scheduled to run throughout the day&lt;/strong&gt; (eg every 5 minutes) using Amazon CloudWatch Events&lt;/li&gt;
&lt;li&gt;It looks at &lt;strong&gt;every running instance in every region&lt;/strong&gt; (or just the regions you specify) and checks for one of these tags (in this order):

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Terminate-After&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Stop-After&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Notify-After&lt;/code&gt; (or any tag that starts with &lt;code&gt;Notify-After&lt;/code&gt;, such as &lt;code&gt;Notify-After1&lt;/code&gt;, &lt;code&gt;Notify-After2&lt;/code&gt;, etc)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;It checks the &lt;strong&gt;duration&lt;/strong&gt; supplied in the tag value (eg &lt;code&gt;30m&lt;/code&gt;, &lt;code&gt;1.5h&lt;/code&gt;, &lt;code&gt;24h&lt;/code&gt;)&lt;/li&gt;

&lt;li&gt;If the instance has been running longer than the given duration, it will terminate/stop the instance or send a notification to an Amazon SNS topic.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;To install it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an &lt;strong&gt;IAM Role&lt;/strong&gt; and add an in-line policy with permissions from the &lt;a href="https://github.com/aws-john/simple-lambda-stopinator-for-ec2/blob/master/iam_role.json" rel="noopener noreferrer"&gt;Stopinator IAM Role&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new &lt;strong&gt;AWS Lambda Python function&lt;/strong&gt; that uses the IAM Role&lt;/li&gt;
&lt;li&gt;Paste in the &lt;a href="https://github.com/aws-john/simple-lambda-stopinator-for-ec2/blob/master/stopinator_type_2.py" rel="noopener noreferrer"&gt;Type 2 Stopinator code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Amazon CloudWatch&lt;/strong&gt;, click &lt;strong&gt;Events&lt;/strong&gt; and add a Schedule at a "Fixed rate of 5 minutes" (or whatever frequency you wish) that triggers the Lambda function&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional:&lt;/strong&gt; Create an &lt;strong&gt;Amazon SNS topic&lt;/strong&gt; and insert the ARN in this line at the top of the Lambda function:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# To send a notification, insert SNS Topic ARN here
&lt;/span&gt;&lt;span class="n"&gt;SNS_TOPIC_ARN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Then, subscribe to the Amazon SNS topic to receive notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To remember whether a notification has been sent, the Stopinator will remove the &lt;code&gt;Notify-After&lt;/code&gt; tag once it has been used. This allows multiple notifications to be tracked by supplying multiple tags that start with &lt;code&gt;Notify-After&lt;/code&gt; in the tag key.&lt;/p&gt;

&lt;p&gt;You can also edit this section to control which regions are checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Provide regions here (eg ['us-west-2', 'ap-southeast-2'], or use [] for all regions
&lt;/span&gt;&lt;span class="n"&gt;regions_to_check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;If you find any problems with the Stopinators, feel free to &lt;a href="https://github.com/aws-john/simple-lambda-stopinator-for-ec2/issues" rel="noopener noreferrer"&gt;add an issue&lt;/a&gt; to the GitHub repository.&lt;/p&gt;

&lt;p&gt;If they have been useful for you, feel free to add a comment below. Enjoy!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>ec2</category>
      <category>cloud</category>
    </item>
    <item>
      <title>S3 Pre-signed URLs for Requester Pays buckets</title>
      <dc:creator>John Rotenstein</dc:creator>
      <pubDate>Tue, 14 Apr 2020 04:37:38 +0000</pubDate>
      <link>https://dev.to/aws/s3-pre-signed-urls-for-requester-pays-buckets-55nc</link>
      <guid>https://dev.to/aws/s3-pre-signed-urls-for-requester-pays-buckets-55nc</guid>
      <description>&lt;p&gt;Ever heard of a &lt;strong&gt;Requester Pays&lt;/strong&gt; Amazon S3 bucket? &lt;em&gt;Great!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ever heard of an &lt;strong&gt;Amazon Pre-signed URL&lt;/strong&gt;? &lt;em&gt;Great!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Can you combine them together?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you're interested to know, read on...&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Requester Pays buckets
&lt;/h3&gt;

&lt;p&gt;First, let's cover some terminology. Imagine you are NASA and you have some &lt;a href="https://registry.opendata.aws/modis-astraea/" rel="noopener noreferrer"&gt;very detailed scientific pictures of the Earth&lt;/a&gt;. NASA would like to make these pictures available to everybody (since it is financed by the government) but bandwidth is costly. If lots of people downloaded the images, the &lt;strong&gt;Data Transfer&lt;/strong&gt; costs could be eye-watering.&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;Requester Pays buckets&lt;/strong&gt;, which allow you to avoid paying Data Transfer charges by transferring that cost to the person requesting the photos. Such buckets cannot provide 'anonymous access' because AWS needs to know who is requesting the objects (so they can be charged for the Data Transfer component). Thus, it's not possible to simply put the URL of a photo in a browser to access it (eg &lt;code&gt;http://my-bucket/photo.jpg&lt;/code&gt;). Instead, the request must come from an IAM Role or IAM User that the related AWS account can be appropriately charged.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-signed URLs
&lt;/h3&gt;

&lt;p&gt;Second, we have the concept of a pre-signed URL. The best way to explain this is by way of an example.&lt;/p&gt;

&lt;p&gt;Imagine that you have a photo-sharing website using Amazon S3 and you want to keep users' photos private. A user should be able to login and view their own pictures, but the pictures should not be publicly accessible. In addition, you want the ability for users to share selected photos with other users.&lt;/p&gt;

&lt;p&gt;In such a scenario, it would &lt;em&gt;not&lt;/em&gt; be easy to create an IAM policy that grants access to a user's own photos and also those that have been shared with them, since there could be hundreds of shared photos. Instead, the ability to access photos in S3 would work like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user &lt;strong&gt;authenticates&lt;/strong&gt; to the application&lt;/li&gt;
&lt;li&gt;A user &lt;strong&gt;requests a photo&lt;/strong&gt;, or they navigate to a page that displays photos on the web page (via an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag)&lt;/li&gt;
&lt;li&gt;The application then &lt;strong&gt;checks whether they are authorized&lt;/strong&gt; to view the image.&lt;/li&gt;
&lt;li&gt;If they are permitted access, the application generates a &lt;strong&gt;pre-signed URL&lt;/strong&gt; that grants &lt;em&gt;time-limited access&lt;/em&gt; to the object in Amazon S3&lt;/li&gt;
&lt;li&gt;The user's browser then uses that URL to request access to the object and &lt;strong&gt;Amazon S3 grants access&lt;/strong&gt; to the object&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A pre-signed URL looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://my-bucket.s3.ap-southeast-2.amazonaws.com/foo.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;amp;X-Amz-Credential=AKIAXXXX%2F20200414%2Fap-southeast-2%2Fs3%2Faws4_request&amp;amp;X-Amz-Date=20200414T034309Z&amp;amp;X-Amz-Expires=3600&amp;amp;X-Amz-SignedHeaders=host&amp;amp;X-Amz-Signature=e89331d191...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pre-signed URL contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A URL to the actual object in Amazon S3&lt;/li&gt;
&lt;li&gt;The time that the URL expires&lt;/li&gt;
&lt;li&gt;The authorized user's Access Key (which is okay to be public)&lt;/li&gt;
&lt;li&gt;A hash signature (based on the authorized user's Secret Access Key)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When Amazon S3 receives such a URL, it recalculates the signature and checks that the time has not expired. If everything seems okay, it grants access to the object in Amazon S3. If anything in the URL has been changed  or falsified (such as the expiry time), the request will fail.&lt;/p&gt;

&lt;p&gt;A pre-signed URL can be created in a number of ways.&lt;/p&gt;

&lt;p&gt;Using the AWS CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 presign s3://my-bucket.s3.amazonaws.com/foo.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, using the &lt;code&gt;boto3&lt;/code&gt; AWS SDK in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;s3_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;get_object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;object_name&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;expiration&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Combining Request Pays + Pre-Signed URLs
&lt;/h3&gt;

&lt;p&gt;Here's the fun bit.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://stackoverflow.com/questions/61199204" rel="noopener noreferrer"&gt;question on StackOverflow&lt;/a&gt; had a scenario where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Account A is providing a requester-pays bucket (&lt;code&gt;Bucket-A&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;An application in Account B wants to allow users to view/access an object in Bucket-A&lt;/li&gt;
&lt;li&gt;These users do not have their own AWS account, so the application will need to generate a pre-signed URL (which will send the Data Transfer costs to Account B)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, they were getting an &lt;code&gt;AccessDenied&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;The reason for this error turned out to be that requests going to a Requester-Pays bucket needs an acknowledgement that the requester is willing to pay for the Data Transfer costs (otherwise they might access a URL without knowing it would cost them more).&lt;/p&gt;

&lt;p&gt;To do this, a URL to a requester-pays bucket requires an additional header in the request:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://...&amp;amp;x-amz-request-payer=requester&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;However, adding this to a pre-signed URL results in a &lt;code&gt;SignatureDoesNotMatch&lt;/code&gt; error because the request URL no longer matches the signature that was originally generated.&lt;/p&gt;

&lt;p&gt;The solution, it turned out, was to specify this additional parameter within the signing request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;s3_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;get_object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;object_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RequestPayer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requester&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Additional&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;expiration&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resulting flow
&lt;/h3&gt;

&lt;p&gt;The final flow for this solution is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User wishes to access object in &lt;strong&gt;Requester Pays bucket&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Application &lt;strong&gt;generates a pre-signed URL&lt;/strong&gt; (since anonymous access is not permitted), but also specifies the &lt;strong&gt;additional parameter&lt;/strong&gt; to accept charges&lt;/li&gt;
&lt;li&gt;The pre-signed URL is sent to the user's web browser&lt;/li&gt;
&lt;li&gt;The web browser requests the object from Amazon S3&lt;/li&gt;
&lt;li&gt;Amazon S3 &lt;strong&gt;verifies the signature and expiry time&lt;/strong&gt; on the pre-signed URL&lt;/li&gt;
&lt;li&gt;Amazon S3 then provides the contents of the object and &lt;strong&gt;charges the Data Transfer portion to the AWS Account that created the pre-signed URL&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not Rocket Science, but does help the Rocket Scientists.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Auto-Stop EC2 instances when they finish a task</title>
      <dc:creator>John Rotenstein</dc:creator>
      <pubDate>Fri, 14 Feb 2020 06:26:28 +0000</pubDate>
      <link>https://dev.to/aws/auto-stop-ec2-instances-when-they-finish-a-task-2f0i</link>
      <guid>https://dev.to/aws/auto-stop-ec2-instances-when-they-finish-a-task-2f0i</guid>
      <description>&lt;p&gt;Sorry serverless folks, but AWS Lambda isn't the answer to every problem!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda functions run for a maximum of 15 minutes&lt;/li&gt;
&lt;li&gt;They only have 512MB of temporary disk space&lt;/li&gt;
&lt;li&gt;Packages can only be up to 250MB (unzipped)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For situations that don't fit these limitations, there's always Amazon EC2! (Containers with Fargate are another option.)&lt;/p&gt;

&lt;p&gt;Here's some tips on how to use Amazon EC2 instances for batch work including running scripts at startup and shutting down when finished.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running a script at every Start&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most people know about the ability to &lt;strong&gt;pass a script via User Data&lt;/strong&gt; that will executed when an EC2 instance is first booted.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html" rel="noopener noreferrer"&gt;Running Commands on Your Linux Instance at Launch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ec2-windows-user-data.html" rel="noopener noreferrer"&gt;Running Commands on Your Windows Instance at Launch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a bit of history... AWS didn't come up with the idea of passing a script to the instance. User Data was simply there to pass some information that was then accessible to applications running on the instance (eg "Here's the password for the database"). It was Canonical that came up with that concept and created &lt;a href="https://cloud-init.io/" rel="noopener noreferrer"&gt;Cloud-Init&lt;/a&gt;. They still maintain it today:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cloud-init is the &lt;em&gt;industry standard&lt;/em&gt; multi-distribution method for cross-platform cloud instance initialization. It is supported across all major public cloud providers, provisioning systems for private cloud infrastructure, and bare-metal installations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A script passed through User Data is executed &lt;strong&gt;the first time an instance is booted&lt;/strong&gt;. (Or, to be more accurate, &lt;strong&gt;once per Instance ID&lt;/strong&gt;.) This makes it great for installing software and configuring systems, since it only runs once.&lt;/p&gt;

&lt;p&gt;But... what if you want to run a script &lt;em&gt;every time the instance starts&lt;/em&gt;? Fortunately, Cloud-Init can also run scripts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On every boot&lt;/li&gt;
&lt;li&gt;On the next boot only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, if you're going to use an Amazon EC2 Linux instance to run a batch job on startup, simply install the script in this directory:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/var/lib/cloud/scripts/per-boot/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Each time the instance is booted (started), the script will run. This is a great way to trigger a batch process on the instance. The script could then retrieve information to be processed from a database, Amazon SQS queue or even the instance User Data. (For example, you could pass an S3 filename through User Data and the script could download and process the file. Please note that User Data can only be updated when an instance is in the Stopped state.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shutting Down when you're Finished&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once your batch job has completed, what's the easiest way to shutdown the instance?&lt;/p&gt;

&lt;p&gt;Yes, you could call the &lt;code&gt;StopInstances()&lt;/code&gt; command, passing the instance ID. However, an easier way is to simply issue the operating system shutdown command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo shutdown now -h&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(The &lt;code&gt;-h&lt;/code&gt; means 'halt', which tells the virtual hardware to turn itself off. If you don't include it, then the operating system turns off but the virtual computer keeps running.)&lt;/p&gt;

&lt;p&gt;Shutting down the instance from the operating system takes advantage of the &lt;strong&gt;Shutdown Behavior&lt;/strong&gt; parameter that tells EC2 what to do when the operating system shuts down the computer. It can be set to either &lt;strong&gt;Stop&lt;/strong&gt; or &lt;strong&gt;Terminate&lt;/strong&gt;. The default is Stop, which means the instance will be turned off, but can turn on again afterwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Warning!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If something goes wrong with the batch script, it might shutdown the instance before you can login and debug the situation. Then, if you start the instance again, it will once again fail and shutdown.&lt;/p&gt;

&lt;p&gt;I suggest you add a &lt;em&gt;circuit breaker&lt;/em&gt; in the code so you can tell the code not to shutdown. For example, the script could check if a particular tag has been added to the instance. If so, skip the shutdown step.&lt;/p&gt;

&lt;p&gt;Photo credits: &lt;a href="https://www.flickr.com/photos/59632563@N04/6645629155" rel="noopener noreferrer"&gt;Production cookie in factory | Vic | Flickr&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
    </item>
    <item>
      <title>IAM Policy to list an S3 bucket, except for the top-level (root) of the bucket</title>
      <dc:creator>John Rotenstein</dc:creator>
      <pubDate>Wed, 12 Feb 2020 23:37:34 +0000</pubDate>
      <link>https://dev.to/aws/iam-policy-to-list-an-s3-bucket-except-for-the-top-level-root-of-the-bucket-1294</link>
      <guid>https://dev.to/aws/iam-policy-to-list-an-s3-bucket-except-for-the-top-level-root-of-the-bucket-1294</guid>
      <description>&lt;p&gt;I saw a question on StackOverflow (&lt;a href="https://stackoverflow.com/q/60193252/174777" rel="noopener noreferrer"&gt;Allow S3 bucket top-level listing to specific users only&lt;/a&gt;) that posed an interesting challenge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow a user to list the contents of an Amazon S3 bucket&lt;/li&gt;
&lt;li&gt;But... Don't let them list the top-level of the bucket&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm always up for a challenge!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IAM Policy Variables&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The question reminded me a bit of &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html" rel="noopener noreferrer"&gt;IAM Policy Elements: Variables and Tags&lt;/a&gt;, which allows the construction of an IAM Policy that inserts a username in appropriate spots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::mybucket"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"StringLike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"s3:prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"${aws:username}/*"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::mybucket/${aws:username}/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This policy says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let the user list/get/put objects&lt;/li&gt;
&lt;li&gt;But only in a folder that matches my IAM User username&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's close, but sort of opposite of "list everything except the top-level".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playing with the ARN&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, I tried playing with the ARN to see whether I could grant permission for &lt;em&gt;any subfolder&lt;/em&gt; within a bucket:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arn:aws:s3:::BUCKET-NAME/*/*&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Granting permission for that ARN is saying &lt;em&gt;"Only for a path within the bucket that contains a slash"&lt;/em&gt;. Thus, it should work for sub-folders but not the top-level.&lt;/p&gt;

&lt;p&gt;Unfortunately, the &lt;code&gt;s3:ListBucket&lt;/code&gt; permission &lt;strong&gt;applies to the bucket itself&lt;/strong&gt; rather than &lt;em&gt;the contents of the bucket&lt;/em&gt;. When granting &lt;code&gt;s3:ListBucket&lt;/code&gt;, you must provide the ARN of the bucket without using &lt;code&gt;/*&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playing with the Prefix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This led me to the idea using the &lt;code&gt;Prefix&lt;/code&gt; to specify which folders can be used. The excellent &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/s3-folder-user-access/" rel="noopener noreferrer"&gt;Grant IAM User Access to a Folder in an Amazon S3 Bucket&lt;/a&gt; article from AWS Support shows some examples of how to use prefixes, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::BUCKET-NAME"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"s3:prefix"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This permits listing of the bucket, but only the top-level folder, or the &lt;code&gt;media&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;This is getting closer!&lt;/p&gt;

&lt;p&gt;I then tried applying negative logic with &lt;code&gt;StringNotEquals&lt;/code&gt;, stating that listing was permitted if the prefix was not empty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::BUCKET-NAME"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"StringNotEquals"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"s3:prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This correctly denied my listing the root directory, but permitted listing a sub-folder. However, it also allowed me to do this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws s3 ls s3://BUCKET-NAME/a&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command would list all objects that started with &lt;code&gt;a&lt;/code&gt;. This was permitted because the prefix was not empty. Therefore, the solution is invalidated because people could try every letter/number as a prefix to view the contents of the top-level of the bucket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;More playing led me to &lt;strong&gt;this solution:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::BUCKET-NAME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"StringLike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"s3:prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This says that listing the bucket is &lt;strong&gt;permitted if the &lt;code&gt;Prefix&lt;/code&gt; contains a slash&lt;/strong&gt;. This prevents listing of the top-level of the bucket and allows any sub-folder to be listed.&lt;/p&gt;

&lt;p&gt;It also requires that at least one slash exists in the path, so this will work:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws s3 ls s3://BUCKET-NAME/folder/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;However, this will &lt;em&gt;not&lt;/em&gt; work since it does not contain a slash:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws s3 ls s3://BUCKET-NAME/folder&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You'll also find my solution on StackOverflow: &lt;a href="https://stackoverflow.com/a/60196839/174777" rel="noopener noreferrer"&gt;Allow S3 bucket top-level listing to specific users only&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
    </item>
    <item>
      <title>Amazon Route 53: How to automatically update IP addresses without using Elastic IPs</title>
      <dc:creator>John Rotenstein</dc:creator>
      <pubDate>Thu, 06 Feb 2020 03:56:09 +0000</pubDate>
      <link>https://dev.to/aws/amazon-route-53-how-to-automatically-update-ip-addresses-without-using-elastic-ips-h7o</link>
      <guid>https://dev.to/aws/amazon-route-53-how-to-automatically-update-ip-addresses-without-using-elastic-ips-h7o</guid>
      <description>&lt;p&gt;Here's a handy way to automatically update an A-Record in Amazon Route 53 whenever an EC2 instance changes IP address.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; You have a domain name in Amazon Route 53 pointing to an Amazon EC2 instance. However, if the instance is stopped and started, its public IP address changes. This breaks the A-Record since it is pointing to the wrong IP address.&lt;/p&gt;

&lt;p&gt;"Wait!" you might say, "Why not simply use an Elastic IP address?"&lt;/p&gt;

&lt;p&gt;(An &lt;strong&gt;Elastic IP address&lt;/strong&gt; is a &lt;em&gt;static IP address&lt;/em&gt; that can be assigned to resources in an Amazon VPC. The IP address stays the same until you return the Elastic IP address to AWS.)&lt;/p&gt;

&lt;p&gt;Yes, using an Elastic IP address on the instance will prevent the A-Record from breaking. However, AWS gives a default limit of 5 Elastic IP addresses per region. You can request a limit increase, but what if you need &lt;em&gt;lots&lt;/em&gt; of them?&lt;/p&gt;

&lt;p&gt;As an example, a service might provide a separate Amazon EC2 instance for each customer, to ensure secure separation of data. Each customer is given a domain name to access their server (&lt;code&gt;acme.example.com&lt;/code&gt;). The required number of Elastic IP addresses could be &lt;em&gt;huge&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Fortunately, there is a fairly simple way to achieve the objective without needing &lt;em&gt;any&lt;/em&gt; Elastic IP addresses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnjqrhei3mgub0eziadic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnjqrhei3mgub0eziadic.png" alt="Architecture" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture is quite simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the EC2 instance starts, it should get its new public IP address and update its own record in Route 53&lt;/li&gt;
&lt;li&gt;The DNS name to update is stored in a &lt;strong&gt;Tag&lt;/strong&gt; on the EC2 instance&lt;/li&gt;
&lt;li&gt;The script should execute every time the EC2 instance starts (that is, &lt;em&gt;every&lt;/em&gt; time it starts, not just the first boot)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Implementation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, there should be a &lt;strong&gt;Record Set in Amazon Route 53&lt;/strong&gt; that defines the existing domain name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb334adb7voya5kjof44v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb334adb7voya5kjof44v.png" alt="Route 53 Record Set" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is currently pointing to the public IP address of an EC2 instance.&lt;/p&gt;

&lt;p&gt;Next, add some tags to the EC2 instance that will be used by the script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DNS Name:&lt;/strong&gt; The DNS Name to associate with the instance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosted Zone ID:&lt;/strong&gt; Uniquely identifies the Zone record in Route 53 that needs to be updated (get it from your Route 53 Hosted Zone record)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fojij5xvifkv799rk4ney.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fojij5xvifkv799rk4ney.png" alt="EC2 instance tags" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whenever the EC2 instance starts, it will run a script that will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grab the information from the above tags&lt;/li&gt;
&lt;li&gt;Retrieve the instance's current public IP address&lt;/li&gt;
&lt;li&gt;Update the Route 53 record set with the new IP address
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Extract information about the Instance&lt;/span&gt;
&lt;span class="nv"&gt;INSTANCE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://169.254.169.254/latest/meta-data/instance-id/&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;AZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://169.254.169.254/latest/meta-data/placement/availability-zone/&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;MY_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://169.254.169.254/latest/meta-data/public-ipv4/&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Extract tags associated with instance&lt;/span&gt;
&lt;span class="nv"&gt;ZONE_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws ec2 describe-tags &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AZ&lt;/span&gt;::-1&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="s2"&gt;"Name=resource-id,Values=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;INSTANCE_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Tags[?Key==`AUTO_DNS_ZONE`].Value'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;NAME_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws ec2 describe-tags &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AZ&lt;/span&gt;::-1&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="s2"&gt;"Name=resource-id,Values=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;INSTANCE_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Tags[?Key==`AUTO_DNS_NAME`].Value'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Update Route 53 Record Set based on the Name tag to the current Public IP address of the Instance&lt;/span&gt;
aws route53 change-resource-record-sets &lt;span class="nt"&gt;--hosted-zone-id&lt;/span&gt; &lt;span class="nv"&gt;$ZONE_TAG&lt;/span&gt; &lt;span class="nt"&gt;--change-batch&lt;/span&gt; &lt;span class="s1"&gt;'{"Changes":[{"Action":"UPSERT","ResourceRecordSet":{"Name":"'&lt;/span&gt;&lt;span class="nv"&gt;$NAME_TAG&lt;/span&gt;&lt;span class="s1"&gt;'","Type":"A","TTL":300,"ResourceRecords":[{"Value":"'&lt;/span&gt;&lt;span class="nv"&gt;$MY_IP&lt;/span&gt;&lt;span class="s1"&gt;'"}]}}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To execute the script automatically each time the instance starts (as opposed to User Data scripts that only run on the &lt;em&gt;first&lt;/em&gt; boot), put the above script in this directory:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/var/lib/cloud/scripts/per-boot/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Finally, the EC2 instance will need an IAM Role assigned that has permission to run the above commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeTags"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"route53:ChangeResourceRecordSets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:route53:::hostedzone/HOSTED-ZONE-ID"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How to test&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To test the script, simply &lt;strong&gt;Stop&lt;/strong&gt; the instance then &lt;strong&gt;Start&lt;/strong&gt; it again.&lt;/p&gt;

&lt;p&gt;This will result in a new public IP address being assigned to the instance. The script will call Amazon Route 53 to update the record set. This might take a minute to update.&lt;/p&gt;

&lt;p&gt;Then, return to Route 53 and look at the IP address assigned to the A-Record. It should be updated with the new IP address.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>python</category>
      <category>dns</category>
    </item>
  </channel>
</rss>
