<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>AWS on Nelson Figueroa</title>
    <link>https://nelson.cloud/categories/aws/</link>
    <description>Recent content in AWS on Nelson Figueroa</description>
    <image>
      <title>Nelson Figueroa</title>
      <url>https://nelson.cloud/opengraph-images/default.png</url>
      <link>https://nelson.cloud/opengraph-images/default.png</link>
    </image>
    <language>en</language>
    <lastBuildDate>Sat, 25 Apr 2026 23:51:05 -0700</lastBuildDate>
    <atom:link href="https://nelson.cloud/categories/aws/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Proxying GoatCounter Requests Through CloudFront to Bypass Ad Blockers</title>
      <link>https://nelson.cloud/proxying-goatcounter-requests-through-cloudfront-to-bypass-ad-blockers/?ref=rss</link>
      <pubDate>Thu, 09 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/proxying-goatcounter-requests-through-cloudfront-to-bypass-ad-blockers/?ref=rss</guid>
      <description>How to configure CloudFront to proxy requests to GoatCounter so that adblockers don&amp;rsquo;t block page views.</description><content:encoded><![CDATA[<p>This blog is a 

<a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo</a>-generated static site hosted on AWS using S3 and CloudFront. I&rsquo;ve been running 

<a href="https://www.goatcounter.com/" target="_blank" rel="noopener">GoatCounter</a> on my site using 

<a href="https://gc.zgo.at/count.js" target="_blank" rel="noopener">the provided script</a> to see who views my blog posts. Every time someone visits my site, a request goes out to GoatCounter. The problem is that adblockers like uBlock Origin block it (understandably).</p>
<img src="/proxying-goatcounter-requests/before.webp" alt="uBlock Origin showing a blocked domain" width="720" height="546" style="max-width: 100%; height: auto; aspect-ratio: 1292 / 980;" loading="lazy" decoding="async">
<p>To get around this, I set up proxying so that the GoatCounter requests go to an endpoint under my domain <code>nelson.cloud/gc/count</code>, and then from there CloudFront handles it and sends it to GoatCounter. Most ad blockers work based on domain and GoatCounter is on the blocklists. Since the browser is now sending requests to the same domain as my site, it shouldn&rsquo;t trigger any ad blockers. This post explains how I did it in case it&rsquo;s useful for anyone else.</p>
<p>It&rsquo;s possible to 

<a href="https://github.com/arp242/goatcounter" target="_blank" rel="noopener">self-host</a> GoatCounter, but my approach was easier to do and less infrastructure to maintain. Perhaps in the future.</p>
<h2 id="on-analytics-and-privacy">On Analytics and Privacy</h2>
<p>I know I&rsquo;m bypassing a user&rsquo;s preference to not be tracked, even if it&rsquo;s (in my opinion) a harmless analytics tool. I just want to see who reads my stuff, that&rsquo;s all.</p>
<p>Read the GoatCounter developer&rsquo;s take if you want another opinion: 

<a href="https://www.arp242.net/personal-analytics.html" target="_blank" rel="noopener">Analytics on personal websites</a>.</p>
<h2 id="managing-infrastructure-with-pulumi">Managing Infrastructure with Pulumi</h2>
<p>Clicking through the AWS console to configure CloudFront distributions is a pain in the ass. I took the time to finally get the infrastructure for my blog managed as infrastructure-as-code with 

<a href="https://www.pulumi.com/docs/iac/languages-sdks/python/" target="_blank" rel="noopener">Pulumi and Python</a>. So while you can click around the console and do all of this, I will be showing how to configure everything with Pulumi.</p>
<p>If you don&rsquo;t want to use IaC, you can still find all of these options/settings in AWS itself.</p>
<h2 id="setting-it-up">Setting it up</h2>
<p>To set up GoatCounter proxying via CloudFront, we&rsquo;ll need to</p>
<ul>
<li>Create a new 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html" target="_blank" rel="noopener">CloudFront function</a> resource</li>
<li>Add a second 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html" target="_blank" rel="noopener">origin</a> to the distribution</li>
<li>Add an ordered 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesCacheBehavior.html" target="_blank" rel="noopener">cache behavior</a> to the distribution (which references the CloudFront function using its ARN)</li>
<li>Update the GoatCounter script to point to this new endpoint</li>
</ul>
<h3 id="cloudfront-function">CloudFront Function</h3>
<p>CloudFront functions are JavaScript scripts that run before a request reaches a CloudFront distribution&rsquo;s origin. In this case, the function strips the <code>/gc</code> from <code>nelson.cloud/gc/count</code>.</p>
<p>We need to strip <code>/gc</code> for two reasons:</p>
<ol>
<li>I chose to proxy requests that hit the <code>/gc/count</code> endpoint on my site to make sure there&rsquo;s no collision with post titles/slugs. I&rsquo;ll never use the <code>/gc/*</code> path for posts.</li>
<li>GoatCounter accepts requests under <code>/count</code>, not <code>/gc/count</code></li>
</ol>
<p>Here is the code for the function:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">handler</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">request</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">request</span><span class="p">.</span><span class="nx">uri</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">uri</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/^\/gc/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">uri</span> <span class="o">===</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="nx">request</span><span class="p">.</span><span class="nx">uri</span> <span class="o">=</span> <span class="s1">&#39;/&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And here is the CloudFront function resource defined in Pulumi (using Python) that includes the JavaScript from above. This is a new resource defined in the same Python file where my existing distribution already exists:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">goatcounter_rewrite</span> <span class="o">=</span> <span class="n">aws</span><span class="o">.</span><span class="n">cloudfront</span><span class="o">.</span><span class="n">Function</span><span class="p">(</span><span class="s2">&#34;goatcounter-rewrite&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span><span class="o">=</span><span class="s2">&#34;goatcounter-rewrite&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">runtime</span><span class="o">=</span><span class="s2">&#34;cloudfront-js-2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">code</span><span class="o">=</span><span class="s2">&#34;&#34;&#34;</span><span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="s2">function handler(event) {
</span></span></span><span class="line"><span class="cl"><span class="s2">    var request = event.request;
</span></span></span><span class="line"><span class="cl"><span class="s2">    request.uri = request.uri.replace(/^</span><span class="se">\\</span><span class="s2">/gc/, &#39;&#39;);
</span></span></span><span class="line"><span class="cl"><span class="s2">    if (request.uri === &#39;&#39;) request.uri = &#39;/&#39;;
</span></span></span><span class="line"><span class="cl"><span class="s2">    return request;
</span></span></span><span class="line"><span class="cl"><span class="s2">}
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="cloudfront-distribution-origin-and-cache-behavior">CloudFront Distribution Origin and Cache Behavior</h3>
<p>Here is my existing CloudFront distribution being updated with a new origin and cache behavior in Pulumi code.</p>
<blockquote><p><strong>Note:</strong></p><p>At the time of writing CloudFront only allows <code>allowed_methods</code> to be a list of HTTP methods in specific combinations. The value must be one of these:</p>
<ul>
<li><code>[HEAD, GET]</code></li>
<li><code>[HEAD, GET, OPTIONS]</code></li>
<li><code>[HEAD, DELETE, POST, GET, OPTIONS, PUT, PATCH]</code></li>
</ul>
<p>Since the GoatCounter JavaScript sends a <code>POST</code> request, and the third option is the only one that includes <code>POST</code>, we&rsquo;re forced to use all HTTP verbs. It should be harmless though.</p>
</blockquote>

<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">nelson_cloud_distribution</span> <span class="o">=</span> <span class="n">aws</span><span class="o">.</span><span class="n">cloudfront</span><span class="o">.</span><span class="n">Distribution</span><span class="p">(</span><span class="s2">&#34;nelson-cloud&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">aliases</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;nelson.cloud&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="c1">###</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># other configuration</span>
</span></span><span class="line"><span class="cl">    <span class="c1">###</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># route /gc/* requests to GoatCounter, stripping the /gc prefix via CloudFront function</span>
</span></span><span class="line"><span class="cl">    <span class="n">ordered_cache_behaviors</span><span class="o">=</span><span class="p">[{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;path_pattern&#34;</span><span class="p">:</span> <span class="s2">&#34;/gc/*&#34;</span><span class="p">,</span> <span class="c1"># the path gets matched here</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;allowed_methods&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;GET&#34;</span><span class="p">,</span> <span class="s2">&#34;HEAD&#34;</span><span class="p">,</span> <span class="s2">&#34;OPTIONS&#34;</span><span class="p">,</span> <span class="s2">&#34;PUT&#34;</span><span class="p">,</span> <span class="s2">&#34;PATCH&#34;</span><span class="p">,</span> <span class="s2">&#34;POST&#34;</span><span class="p">,</span> <span class="s2">&#34;DELETE&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;cached_methods&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;GET&#34;</span><span class="p">,</span> <span class="s2">&#34;HEAD&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;target_origin_id&#34;</span><span class="p">:</span> <span class="s2">&#34;goatcounter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;viewer_protocol_policy&#34;</span><span class="p">:</span> <span class="s2">&#34;redirect-to-https&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;compress&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;cache_policy_id&#34;</span><span class="p">:</span> <span class="s2">&#34;4135ea2d-6df8-44a3-9df3-4b5a84be39ad&#34;</span><span class="p">,</span>  <span class="c1"># CachingDisabled</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;origin_request_policy_id&#34;</span><span class="p">:</span> <span class="s2">&#34;b689b0a8-53d0-40ab-baf2-68738e2966ac&#34;</span><span class="p">,</span>  <span class="c1"># AllViewerExceptHostHeader</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;function_associations&#34;</span><span class="p">:</span> <span class="p">[{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;event_type&#34;</span><span class="p">:</span> <span class="s2">&#34;viewer-request&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;function_arn&#34;</span><span class="p">:</span> <span class="n">goatcounter_rewrite</span><span class="o">.</span><span class="n">arn</span><span class="p">,</span> <span class="c1"># CloudFront function from above</span>
</span></span><span class="line"><span class="cl">        <span class="p">}],</span>
</span></span><span class="line"><span class="cl">    <span class="p">}],</span>
</span></span><span class="line"><span class="cl">    <span class="n">origins</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="c1">###</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># other existing origins</span>
</span></span><span class="line"><span class="cl">        <span class="c1">###</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># a new origin to proxy GoatCounter requests</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;custom_origin_config&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;http_port&#34;</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;https_port&#34;</span><span class="p">:</span> <span class="mi">443</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;origin_protocol_policy&#34;</span><span class="p">:</span> <span class="s2">&#34;https-only&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;origin_ssl_protocols&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;TLSv1.2&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;domain_name&#34;</span><span class="p">:</span> <span class="s2">&#34;nelsonfigueroa.goatcounter.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;origin_id&#34;</span><span class="p">:</span> <span class="s2">&#34;goatcounter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="c1">###</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># rest of configuration</span>
</span></span><span class="line"><span class="cl">    <span class="c1">###</span>
</span></span><span class="line"><span class="cl">    <span class="n">opts</span> <span class="o">=</span> <span class="n">pulumi</span><span class="o">.</span><span class="n">ResourceOptions</span><span class="p">(</span><span class="n">protect</span><span class="o">=</span><span class="kc">True</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Now that my Pulumi code has both the CloudFront function defined and the CloudFront distribution has been updated, I ran <code>pulumi up</code> to apply changes.</p>
<h3 id="update-the-goatcounter-script">Update the GoatCounter Script</h3>
<p>Finally, I updated goatcounter.js to use the new endpoint. So instead of <code>goatcounter.com</code> I changed it to my own domain <code>nelson.cloud/gc/count</code> at the very top of the snippet:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="o">&lt;</span><span class="nx">script</span> <span class="nx">data</span><span class="o">-</span><span class="nx">goatcounter</span><span class="o">=</span><span class="s2">&#34;https://nelson.cloud/gc/count&#34;</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="cm">/* rest of script */</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="err">/script&gt;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>After this, I built my site with Hugo and deployed it on S3/CloudFront by updating the freshly built HTML/CSS/JS in my S3 Bucket and then 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html" target="_blank" rel="noopener">invalidating the existing CloudFront cache</a>.</p>
<h2 id="verifying-that-it-works">Verifying that it Works</h2>
<p>Now, GoatCounter should no longer be blocked by uBlock Origin. I tested by loading my site on an incognito browser window and checked that uBlock Origin was no longer blocking anything on my domain.</p>
<img src="/proxying-goatcounter-requests/after.webp" alt="uBlock Origin no longer showing a blocked domain" width="720" height="540" style="max-width: 100%; height: auto; aspect-ratio: 1290 / 968;" loading="lazy" decoding="async">
<p>And for further proof, checking the network tab shows a successful <code>POST</code> request to the <code>/gc/count</code> endpoint on my domain along with response headers from AWS/CloudFront:</p>
<img src="/proxying-goatcounter-requests/network.webp" alt="Firefox network tab showing a successful request to /gc/count" width="609" height="720" style="max-width: 100%; height: auto; aspect-ratio: 870 / 1028;" loading="lazy" decoding="async">
<p>Everything looks good!</p>
<h2 id="support-goatcounter">Support GoatCounter</h2>
<p>If you&rsquo;re using GoatCounter you should consider 

<a href="https://github.com/sponsors/arp242/" target="_blank" rel="noopener">sponsoring the developer</a>. It&rsquo;s a great project.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html</a></li>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html</a></li>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesCacheBehavior.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesCacheBehavior.html</a></li>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html</a></li>
<li>

<a href="https://www.goatcounter.com/help/js" target="_blank" rel="noopener">https://www.goatcounter.com/help/js</a></li>
<li>

<a href="https://www.goatcounter.com/help/backend" target="_blank" rel="noopener">https://www.goatcounter.com/help/backend</a></li>
<li>

<a href="https://www.goatcounter.com/help/countjs-host" target="_blank" rel="noopener">https://www.goatcounter.com/help/countjs-host</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>GitHub Actions for Pulumi with an AWS S3 Backend</title>
      <link>https://nelson.cloud/github-actions-for-pulumi-with-an-aws-s3-backend/?ref=rss</link>
      <pubDate>Thu, 11 Dec 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/github-actions-for-pulumi-with-an-aws-s3-backend/?ref=rss</guid>
      <description>How to set up GitHub Actions for Pulumi when the state is stored in an AWS S3 Bucket.</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>This is a quick guide to set up GitHub Actions for Pulumi with an 

<a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener">AWS S3</a> backend. It builds on a previous guide I wrote: 

<a href="https://nelson.cloud/how-to-use-an-aws-s3-bucket-as-a-pulumi-state-backend/">How to Use an AWS S3 Bucket as a Pulumi State Backend</a>.</p>
<p>This guide assumes you have the following:</p>
<ul>
<li>An AWS S3 Bucket created and ready to be used with Pulumi</li>
<li>An IAM User that has permissions to read/write to the S3 bucket</li>
<li>The Access Key and Secret Access Key for the IAM User to use for authenticating to AWS within GitHub Actions</li>
<li>A passphrase of your choosing that will be used to encrypt secrets in the pulumi stack</li>
<li>A GitHub repository</li>
</ul>
<h2 id="setting-up-repository-secrets">Setting up Repository Secrets</h2>
<p>First, set up secrets on your GitHub repository. These will be filled in by GitHub Actions once we create a workflow YAML. You&rsquo;ll need to create 3 secrets. You can name them whatever you want, but I&rsquo;ll be naming them:</p>
<ul>
<li><code>AWS_ACCESS_KEY_ID</code></li>
<li><code>AWS_SECRET_ACCESS_KEY</code></li>
<li><code>PULUMI_CONFIG_PASSPHRASE</code></li>
</ul>
<p>You can create these by browsing to your GitHub repository &gt; Settings &gt; Secrets and variables &gt; Actions. There are &ldquo;Environment secrets&rdquo; and &ldquo;Repository secrets&rdquo;. In this case go with &ldquo;Repository secrets&rdquo;.</p>
<p>Create the three secrets and fill in their respective values. <code>PULUMI_CONFIG_PASSPHRASE</code> can be whatever you want and doesn&rsquo;t come from AWS.</p>
<blockquote><p><strong>Warning:</strong></p>Make sure you don&rsquo;t change this passphrase after the fact, or your Pulumi state may break.</blockquote>

<p>The end result should look like this:</p>
<img src="/github-actions-with-pulumi-s3/github-secrets.webp" alt="GitHub repository secrets showing AWS credentials and Pulumi passphrase" width="3096" height="1708" style="max-width: 100%; height: auto; aspect-ratio: 774 / 427;" loading="lazy" decoding="async">
<h2 id="defining-the-github-actions-workflow">Defining the GitHub Actions Workflow</h2>
<p>Next, clone the repository locally with <code>git clone</code>. We&rsquo;ll need to create a few files for a minimum viable Pulumi program. Run <code>pulumi new</code> and it&rsquo;ll guide you through the creation of a basic Pulumi program. The language you choose doesn&rsquo;t matter.</p>
<p>Then we can create the YAML file to set up GitHub Actions. Create a YAML file under <code>&lt;your-repository-name&gt;/.github/workflows/</code>. I&rsquo;ll call it <code>preview.yml</code> in my example. Fill it in with the following YAML, changing values as needed.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Pulumi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">main</span><span class="w"> </span><span class="c"># change this if you&#39;re using a different branch name</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">preview</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Preview</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Pulumi Preview</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">pulumi/actions@v6.6.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">preview</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">stack-name</span><span class="p">:</span><span class="w"> </span><span class="l">dev</span><span class="w"> </span><span class="c"># change this to your stack&#39;s name (or whatever you want it to be if it doesn&#39;t exist)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">cloud-url</span><span class="p">:</span><span class="w"> </span><span class="l">s3://nelson-test-bucket-for-github-actions</span><span class="w"> </span><span class="c"># change this to your bucket name to be used as a pulumi backend</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">upsert</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># creates a stack if it doesn&#39;t exist (along with Pulumi.&lt;stack&gt;.yaml)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">AWS_ACCESS_KEY_ID</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.AWS_ACCESS_KEY_ID }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">AWS_SECRET_ACCESS_KEY</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.AWS_SECRET_ACCESS_KEY }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">PULUMI_CONFIG_PASSPHRASE</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.PULUMI_CONFIG_PASSPHRASE }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">AWS_REGION</span><span class="p">:</span><span class="w"> </span><span class="l">us-east-1</span><span class="w"> </span><span class="c"># change the region as needed</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>This runs a <code>pulumi preview</code> so it&rsquo;ll verify that everything is set up correctly without actually deploying anything.</p>
<h2 id="testing-the-workflow">Testing the Workflow</h2>
<p>Now push your code to GitHub and see if the GitHub Action workflow ran successfully. You should see output from a successful <code>pulumi preview</code>.</p>
<img src="/github-actions-with-pulumi-s3/github-actions-logs.webp" alt="GitHub Actions workflow logs showing successful Pulumi preview" width="2854" height="1632" style="max-width: 100%; height: auto; aspect-ratio: 1427 / 816;" loading="lazy" decoding="async">
<p>If this works, you should be good to go. You can update your Pulumi code and change <code>command: preview</code> to <code>command: up</code> in the GitHub Actions YAML file and run it again to actually deploy some infrastructure.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://github.com/pulumi/actions" target="_blank" rel="noopener">https://github.com/pulumi/actions</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to Use an AWS S3 Bucket as a Pulumi State Backend</title>
      <link>https://nelson.cloud/how-to-use-an-aws-s3-bucket-as-a-pulumi-state-backend/?ref=rss</link>
      <pubDate>Sat, 20 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-to-use-an-aws-s3-bucket-as-a-pulumi-state-backend/?ref=rss</guid>
      <description>Self-host Pulumi state with an S3 Bucket, an IAM User, and the Pulumi CLI.</description><content:encoded><![CDATA[<p>I&rsquo;ll be starting from scratch and creating an IAM user with access to an S3 bucket that will be used to store the Pulumi state file. If you&rsquo;re working in an enterprise setting, your authentication methods may vary.</p>
<blockquote><p><strong>tl;dr:</strong></p><p>You can run this command (replacing placeholders as needed) if you already have an S3 bucket and AWS credentials configured on your machine:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pulumi login <span class="s1">&#39;s3://&lt;bucket-name&gt;?region=&lt;region&gt;&amp;awssdk=v2&amp;profile=&lt;aws-profile-name&gt;&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote>
</div>

<p>This post assumes you have the Pulumi CLI installed. Check out the following guide if you don&rsquo;t have it installed: 

<a href="https://www.pulumi.com/docs/iac/download-install/" target="_blank" rel="noopener">Download &amp; install Pulumi</a>.</p>
<h2 id="creating-an-s3-bucket">Creating an S3 Bucket</h2>
<p>First, we need to create the S3 bucket where the Pulumi state file will be stored. I created a bucket called <code>nelsons-pulumi-state-backend</code> and left all the default settings as-is.</p>
<img src="/pulumi-with-s3-backend/s3-bucket.webp" alt="S3 Bucket naming" width="720" height="391" style="max-width: 100%; height: auto; aspect-ratio: 1520 / 824;" loading="lazy" decoding="async">
<h2 id="creating-an-iam-user">Creating an IAM User</h2>
<p>Then we need to create an IAM user in AWS that the Pulumi CLI can use. This IAM user needs permissions to access the S3 bucket we just created.</p>
<p>I go to IAM and create a new user. I just called it <code>pulumi</code>:</p>
<img src="/pulumi-with-s3-backend/iam-user.webp" alt="Creating an IAM user" width="720" height="434" style="max-width: 100%; height: auto; aspect-ratio: 1500 / 904;" loading="lazy" decoding="async">
<p>Then in the next step, I selected &ldquo;Attach policies directly&rdquo; and selected the AWS-managed &ldquo;AdministratorAccess&rdquo; policy just to keep things simple. You can provide more fine-grained access depending on your needs. Then click &ldquo;Next&rdquo; at the bottom.</p>
<img src="/pulumi-with-s3-backend/iam-permissions.webp" alt="IAM permissions" width="720" height="418" style="max-width: 100%; height: auto; aspect-ratio: 1968 / 1144;" loading="lazy" decoding="async">
<p>In the next screen, double check everything and then click on &ldquo;Create user&rdquo;.</p>
<img src="/pulumi-with-s3-backend/iam-review.webp" alt="Reviewing IAM user" width="720" height="384" style="max-width: 100%; height: auto; aspect-ratio: 2540 / 1356;" loading="lazy" decoding="async">
<p>Now that we have a user with the appropriate permissions, we&rsquo;ll need to get an AWS access key and secret to use with the Pulumi CLI.</p>
<p>Go to your IAM user and click on &ldquo;Create access key&rdquo; on the right side.</p>
<img src="/pulumi-with-s3-backend/iam-user-view.webp" alt="The new IAM user" width="720" height="183" style="max-width: 100%; height: auto; aspect-ratio: 2348 / 596;" loading="lazy" decoding="async">
<p>In the next screen, select &ldquo;Command Line Interface (CLI)&rdquo;. Check the box at the bottom, then click &ldquo;Next&rdquo;.</p>
<img src="/pulumi-with-s3-backend/iam-access-key.webp" alt="Creating an access key" width="720" height="601" style="max-width: 100%; height: auto; aspect-ratio: 1836 / 1532;" loading="lazy" decoding="async">
<p>The next screen will ask for setting a description tag. This is optional. I chose to skip it and clicked on &ldquo;Create access key&rdquo;.</p>
<img src="/pulumi-with-s3-backend/iam-description-tag.webp" alt="Access key description tag" width="720" height="206" style="max-width: 100%; height: auto; aspect-ratio: 1820 / 520;" loading="lazy" decoding="async">
<p>We finally have our Access key and Secret access key. Save these somewhere safe and click &ldquo;Done&rdquo;. (Don&rsquo;t worry, the credentials in the screenshot are fake.)</p>
<img src="/pulumi-with-s3-backend/retrieve-access-keys.webp" alt="Retrieving AWS access keys" width="720" height="435" style="max-width: 100%; height: auto; aspect-ratio: 2244 / 1356;" loading="lazy" decoding="async">
<h2 id="setting-up-aws-credentials-for-the-pulumi-cli">Setting Up AWS Credentials for the Pulumi CLI</h2>
<p>Now we can try using these credentials to tell the Pulumi CLI to use the S3 bucket as a backend.</p>
<blockquote><p><strong>Note:</strong></p>Note that you do NOT need the 

<a href="https://aws.amazon.com/cli/" target="_blank" rel="noopener">AWS CLI</a> installed. Pulumi just needs the AWS credentials.</blockquote>

<p>Create the file <code>~/.aws/credentials</code> if you don&rsquo;t have it. Then add in your credentials there under the <code>[default]</code> profile. (You can add more profiles, but this is beyond the scope of this post.)</p>
<pre tabindex="0"><code>[default]
aws_access_key_id = &lt;key_id&gt;
aws_secret_access_key = &lt;access_key&gt;
</code></pre><p>You&rsquo;ll need the bucket&rsquo;s region and your local AWS profile name to use S3 as a backend.</p>
<p>The command formula looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pulumi login <span class="s1">&#39;s3://&lt;bucket-name&gt;?region=&lt;region&gt;&amp;awssdk=v2&amp;profile=&lt;aws-profile-name&gt;&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>In my case, the command looks like this (make sure to edit for your needs):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pulumi login <span class="s1">&#39;s3://nelsons-pulumi-state-backend?region=us-west-1&amp;awssdk=v2&amp;profile=default&#39;</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>A successful login shows the following message:</p>
<pre tabindex="0"><code>Logged in to 0x6E.local as nelson (s3://nelsons-pulumi-state-backend?region=us-west-1&amp;awssdk=v2&amp;profile=default)
</code></pre><p>Alternatively, you can add your backend to your <code>Pulumi.yaml</code> file. This is useful if you&rsquo;re working on multiple Pulumi projects that each have different backends. You won&rsquo;t need to run <code>pulumi login</code> all the time. Just add a <code>backend</code> key and a nested <code>url</code> key:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-pulumi-project</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">a pulumi project for testing</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">runtime</span><span class="p">:</span><span class="w"> </span><span class="l">nodejs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># add this section</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">backend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="l">s3://nelsons-pulumi-state-backend?region=us-west-1&amp;awssdk=v2&amp;profile=default</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</blockquote><p>More information here: 

<a href="https://www.pulumi.com/docs/iac/concepts/projects/project-file/" target="_blank" rel="noopener">Pulumi project file reference</a>.</p>
<h2 id="testing-the-setup">Testing The Setup</h2>
<p>Finally, it&rsquo;s time to test this out.</p>
<p>To demonstrate, I created a simple Pulumi program by running:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pulumi new aws-python
</span></span></code></pre></td></tr></table>
</blockquote><p>You can choose whatever language you want though.</p>
<p>This is the main Pulumi code that is generated. It&rsquo;s code for creating an S3 bucket:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;An AWS Python Pulumi program&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pulumi</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pulumi_aws</span> <span class="kn">import</span> <span class="n">s3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create an AWS resource (S3 Bucket)</span>
</span></span><span class="line"><span class="cl"><span class="n">bucket</span> <span class="o">=</span> <span class="n">s3</span><span class="o">.</span><span class="n">Bucket</span><span class="p">(</span><span class="s1">&#39;my-bucket&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Export the name of the bucket</span>
</span></span><span class="line"><span class="cl"><span class="n">pulumi</span><span class="o">.</span><span class="n">export</span><span class="p">(</span><span class="s1">&#39;bucket_name&#39;</span><span class="p">,</span> <span class="n">bucket</span><span class="o">.</span><span class="n">id</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Then I ran <code>pulumi up -y</code> and it worked!</p>
<img src="/pulumi-with-s3-backend/pulumi-up-y.gif" alt="terminal output when running pulumi up -y" width="720" height="475" style="max-width: 100%; height: auto; aspect-ratio: 3532 / 2329;" loading="lazy" decoding="async">
<p>And just to double check, I can see that my previously empty S3 bucket now has contents created by the Pulumi CLI:</p>
<img src="/pulumi-with-s3-backend/s3-bucket-with-contents.webp" alt="S3 Bucket with Pulumi state contents" width="720" height="274" style="max-width: 100%; height: auto; aspect-ratio: 2236 / 850;" loading="lazy" decoding="async">
<p>Everything works!</p>
<hr>
<p>If you&rsquo;re interested in setting up CI/CD with this setup, I wrote a post showing how to do so: 

<a href="https://nelson.cloud/github-actions-for-pulumi-with-an-aws-s3-backend/">GitHub Actions for Pulumi with an AWS S3 Backend</a>.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://www.pulumi.com/docs/iac/download-install/" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/download-install/</a></li>
<li>

<a href="https://www.pulumi.com/registry/packages/aws/installation-configuration/" target="_blank" rel="noopener">https://www.pulumi.com/registry/packages/aws/installation-configuration/</a></li>
<li>

<a href="https://www.pulumi.com/docs/iac/concepts/state-and-backends/#aws-s3" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/concepts/state-and-backends/#aws-s3</a></li>
<li>

<a href="https://www.pulumi.com/docs/iac/concepts/projects/project-file/" target="_blank" rel="noopener">https://www.pulumi.com/docs/iac/concepts/projects/project-file/</a></li>
<li>

<a href="https://ashoksubburaj.medium.com/pulumi-with-aws-s3-as-backend-ac79533820f1" target="_blank" rel="noopener">https://ashoksubburaj.medium.com/pulumi-with-aws-s3-as-backend-ac79533820f1</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Fix &#34;Efficient Cache Policy&#34; Warning on PageSpeed Insights When Using Amazon CloudFront</title>
      <link>https://nelson.cloud/fix-efficient-cache-policy-warning-on-pagespeed-insights-when-using-amazon-cloudfront/?ref=rss</link>
      <pubDate>Thu, 02 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/fix-efficient-cache-policy-warning-on-pagespeed-insights-when-using-amazon-cloudfront/?ref=rss</guid>
      <description>Get rid of the efficient cache policy warning on PageSpeed Insights by adding a &lt;code&gt;Cache-Control&lt;/code&gt; header to CloudFront.</description><content:encoded><![CDATA[<p>I ran into this warning recently on 

<a href="https://pagespeed.web.dev/" target="_blank" rel="noopener">PageSpeed Insights</a>: <em>&ldquo;Serve static assets with an efficient cache policy&rdquo;</em>. The warning highlighted three assets that had no &ldquo;Cache TTL&rdquo; defined:</p>
<img src="/fix-efficient-cache-policy-on-gsc/before.webp" alt="Efficient cache policy warning on PageSpeed Insights" width="720" height="295" style="max-width: 100%; height: auto; aspect-ratio: 1904 / 782;" loading="lazy" decoding="async">
<p>To resolve this warning, I added a <code>Cache-Control</code> header with the value <code>max-age=31536000</code> to the HTTP responses of my domain (<code>31536000</code> is the number of seconds in a year).</p>
<p>I host 

<a href="https://nelson.cloud" target="_blank" rel="noopener">nelson.cloud</a> on 

<a href="https://aws.amazon.com/cloudfront/" target="_blank" rel="noopener">Amazon CloudFront</a>. I added this header to the CloudFront Distribution that the <code>nelson.cloud</code> domain points to. This can be done by adding a &ldquo;Response headers policy&rdquo; in the Behavior of the Distribution. I wrote a post demonstrating how to do this in detail: 

<a href="https://nelson.cloud/how-to-add-a-custom-response-header-to-an-amazon-cloudfront-distribution/">How to Add a Custom Response Header to an Amazon CloudFront Distribution</a>.</p>
<p>After the header was configured, I checked PageSpeed Insights again and the warning had gone away for the assets under my domain <code>nelson.cloud</code>:</p>
<img src="/fix-efficient-cache-policy-on-gsc/after.webp" alt="Efficient cache policy warning is gone for 2 assets." width="720" height="194" style="max-width: 100%; height: auto; aspect-ratio: 1912 / 516;" loading="lazy" decoding="async">
<p>(The remaining asset is not under my control so I can&rsquo;t fix that one.)</p>
<blockquote><p><strong>Note:</strong></p>You may be able to use a different value smaller than <code>31536000</code> for your header. I chose a year in seconds to be on the safe side for PageSpeed Insights.</blockquote>

<h2 id="references">References</h2>
<ul>
<li>

<a href="https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/" target="_blank" rel="noopener">https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/</a></li>
<li>

<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control</a></li>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to Add a Custom Response Header to an Amazon CloudFront Distribution</title>
      <link>https://nelson.cloud/how-to-add-a-custom-response-header-to-an-amazon-cloudfront-distribution/?ref=rss</link>
      <pubDate>Wed, 01 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/how-to-add-a-custom-response-header-to-an-amazon-cloudfront-distribution/?ref=rss</guid>
      <description>A guide to adding custom response headers to a CloudFront distribution using response headers policies in the AWS Console.</description><content:encoded><![CDATA[<p>This post assumes you already have an 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html" target="_blank" rel="noopener">Amazon CloudFront</a> Distribution deployed and properly configured. You&rsquo;ll also need knowledge about 

<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" target="_blank" rel="noopener">HTTP headers</a> if you found your way here because your boss told you to do this and &ldquo;figure it out&rdquo;.</p>
<p>Go to the AWS Console and click into your CloudFront Distribution. Then click on the &ldquo;Behaviors&rdquo; tab. If you already have a behavior, you can edit the existing one. Otherwise, create a behavior.</p>
<img src="/aws-cloudfront-custom-response-header/distribution.webp" alt="CloudFront Distribution Behaviors tab" width="720" height="180" style="max-width: 100%; height: auto; aspect-ratio: 2284 / 572;" loading="lazy" decoding="async">
<p>I won&rsquo;t cover the other Behavior settings as that is out of the scope of this post, but to add a custom response header look for the &ldquo;Response headers policy - <em>optional</em>&rdquo; field which is under &ldquo;Cache key and origin requests&rdquo;.</p>
<img src="/aws-cloudfront-custom-response-header/response-headers-policy.webp" alt="Response headers policy field" width="720" height="107" style="max-width: 100%; height: auto; aspect-ratio: 1580 / 236;" loading="lazy" decoding="async">
<p>Click on the &ldquo;Create response headers policy&rdquo; link. On the next page fill in the &ldquo;Name&rdquo; field at the very top. Then, scroll a bit down and you should see the &ldquo;Custom headers - <em>optional</em>&rdquo; field.</p>
<img src="/aws-cloudfront-custom-response-header/custom-headers.webp" alt="Custom headers field" width="720" height="589" style="max-width: 100%; height: auto; aspect-ratio: 2184 / 1788;" loading="lazy" decoding="async">
<p>Click on the &ldquo;Add header&rdquo; button and add the custom header you&rsquo;d like. In my case, I added the <code>Cache-Control</code> header with a value of <code>max-age=31536000</code>. I also checked the <code>Origin override</code> box because I want this header to take precedence over other headers that may be set in the origin.</p>
<img src="/aws-cloudfront-custom-response-header/custom-header-filled.webp" alt="Custom headers field filled in" width="720" height="134" style="max-width: 100%; height: auto; aspect-ratio: 2172 / 404;" loading="lazy" decoding="async">
<p>Then click the &ldquo;Create&rdquo; button on the bottom right. You&rsquo;ll be redirected to CloudFront &gt; Policies. You can see your newly created response header under the &ldquo;Response headers&rdquo; tab. In my case it&rsquo;s called <code>cache-control-header</code>.</p>
<img src="/aws-cloudfront-custom-response-header/policies.webp" alt="Policies view showing custom policy" width="720" height="533" style="max-width: 100%; height: auto; aspect-ratio: 1738 / 1286;" loading="lazy" decoding="async">
<p>Now that this custom header policy is created, browse back to your Distribution and click on the &ldquo;Behaviors&rdquo; tab. Edit an existing Behavior or create one. Scroll down to the &ldquo;Response headers policy - <em>optional</em>&rdquo; field again. Then, select your new response header policy.</p>
<img src="/aws-cloudfront-custom-response-header/behavior-filled.webp" alt="Selecting new response header policy in Behaviors tab" width="720" height="131" style="max-width: 100%; height: auto; aspect-ratio: 1236 / 224;" loading="lazy" decoding="async">
<p>Then save your changes.</p>
<p>Wait a few minutes and your distribution will be updated with the new header. There is no need to create an 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html" target="_blank" rel="noopener">Invalidation</a>.</p>
<p>You can test this out by running <code>curl -IX GET &lt;your-distribution-domain-name&gt;</code>. Your output should look something like this. Look for your custom header in the output. In my case, <code>cache-control</code> shows up at the very bottom.</p>
<pre tabindex="0"><code>$ curl -IX GET https://abcdefghijklmn.cloudfront.net

HTTP/2 301
content-length: 0
location: https://nelson.cloud/index.html
date: Wed, 01 Jan 2025 08:12:11 GMT
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 98167d64569fd17ca63a5b7db2edfe28.cloudfront.net (CloudFront)
x-amz-cf-pop: LAX54-P1
x-amz-cf-id: l5n0bciPQfswC27jGnhcrkuldHWFDgZx0JdYo406qkoKKKP87i_dsA==
age: 2292
cache-control: max-age=31536000
</code></pre><p>Done!</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers</a></li>
<li>

<a href="https://stackoverflow.com/questions/56187791/how-to-set-cache-control-header-in-amazon-cloudfront" target="_blank" rel="noopener">https://stackoverflow.com/questions/56187791/how-to-set-cache-control-header-in-amazon-cloudfront</a></li>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/modifying-response-headers.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/modifying-response-headers.html</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>AWS SSM: Run a Shell Command Against Multiple EC2 Instances</title>
      <link>https://nelson.cloud/aws-ssm-run-a-shell-command-against-multiple-ec2-instances/?ref=rss</link>
      <pubDate>Thu, 18 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/aws-ssm-run-a-shell-command-against-multiple-ec2-instances/?ref=rss</guid>
      <description>How to run a shell command against multiple EC2 instances using AWS Systems Manager</description><content:encoded><![CDATA[<p>In AWS, you can use 

<a href="https://aws.amazon.com/systems-manager/" target="_blank" rel="noopener">AWS Systems Manager</a> to run commands on any EC2 Instances that have the 

<a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html" target="_blank" rel="noopener">AWS Systems Manager Agent</a> installed. AWS Systems Manager has a lot more functionality than what I&rsquo;m demonstrating here, but this is a specific use case.</p>
<h2 id="shell-command-setup">Shell Command Setup</h2>
<p>To get started running a shell command on multiple EC2 Instances, head over to 

<a href="https://console.aws.amazon.com/systems-manager/home" target="_blank" rel="noopener">AWS Systems Manager via the AWS Console</a>. Then, on the left side under &ldquo;Node Management&rdquo;, click on &ldquo;Run Command&rdquo;.</p>
<img src="/run-a-shell-command-on-ec2-instances/node-management.webp" alt="Node Management menu" width="500" height="608" style="max-width: 100%; height: auto; aspect-ratio: 500 / 608;" loading="lazy" decoding="async">
<p>On the next screen, click on the orange &ldquo;Run a Command&rdquo; button on the right.</p>
<img src="/run-a-shell-command-on-ec2-instances/aws-ssm-run-command.webp" alt="AWS SSM home page" width="720" height="214" style="max-width: 100%; height: auto; aspect-ratio: 2040 / 608;" loading="lazy" decoding="async">
<p>Next, under &ldquo;Command document&rdquo; search for &ldquo;shell&rdquo; to find <code>AWS-RunShellScript</code> and select it.</p>
<img src="/run-a-shell-command-on-ec2-instances/command-document.webp" alt="AWS SSM command document" width="720" height="328" style="max-width: 100%; height: auto; aspect-ratio: 2704 / 1232;" loading="lazy" decoding="async">
<p>Then, under &ldquo;Command parameters&rdquo; type in the shell command you want to run on your EC2 instances. In my case, I want to run security updates on my instances.</p>
<img src="/run-a-shell-command-on-ec2-instances/command-parameters.webp" alt="AWS SSM command parameters" width="720" height="330" style="max-width: 100%; height: auto; aspect-ratio: 984 / 452;" loading="lazy" decoding="async">
<p>Then select EC2 Instances by your preferred method under &ldquo;Target Selection&rdquo;. I chose to manually select my instances but you can also select based on tags or resource groups. Make sure you are using the same region your EC2 instances are in!</p>
<img src="/run-a-shell-command-on-ec2-instances/target-selection.webp" alt="AWS SSM target selection" width="720" height="333" style="max-width: 100%; height: auto; aspect-ratio: 2692 / 1248;" loading="lazy" decoding="async">
<p>This part is optional. If you want log outputs of the commands that are run on every instance you can save logs to a S3 Bucket under &ldquo;Output options&rdquo;. Make sure the Instance Profile IAM Role has permissions to write to this bucket!</p>
<img src="/run-a-shell-command-on-ec2-instances/output-options.webp" alt="AWS SSM output options" width="720" height="269" style="max-width: 100%; height: auto; aspect-ratio: 2688 / 1006;" loading="lazy" decoding="async">
<p>I left all other settings with their default values.</p>
<p>Now we can click the orange &ldquo;Run&rdquo; button on the bottom of the page to execute our command.</p>
<h2 id="after-running-a-command">After Running a Command</h2>
<p>After running a command you&rsquo;ll see the following screen. You can see the status of the command execution on each EC2 instance to ensure everything went fine. You can also see the Command ID here which will come in handy if you enabled S3 logging.</p>
<img src="/run-a-shell-command-on-ec2-instances/command-id.webp" alt="SSM screen after execution" width="720" height="261" style="max-width: 100%; height: auto; aspect-ratio: 2708 / 984;" loading="lazy" decoding="async">
<h3 id="viewing-logs">Viewing Logs</h3>
<p>If you enabled logging to S3, you can browse to the bucket you selected and view logs. The output log file will be several directories deep under <code>&lt;Command ID&gt;/&lt;ec2-instance-id&gt;/&lt;command document&gt;/...</code>. It&rsquo;s easier if you refer to the screenshot below:</p>
<img src="/run-a-shell-command-on-ec2-instances/s3-logs.webp" alt="AWS SSM output logs" width="720" height="324" style="max-width: 100%; height: auto; aspect-ratio: 2444 / 1100;" loading="lazy" decoding="async">
<p>I downloaded the <code>stdout</code> file locally and viewed it via the command line to confirm that my command executed properly on this particular EC2 instance:</p>
<pre tabindex="0"><code>$ cat stdout

Last metadata expiration check: 0:15:25 ago on Thu Jan 18 21:10:06 2024.
Dependencies resolved.
Nothing to do.
Complete!
</code></pre><h2 id="troubleshooting">Troubleshooting</h2>
<p>I personally ran into some issues when trying to do this for the first time. Here are a few troubleshooting solutions.</p>
<h3 id="ec2-instances-dont-show-up-in-systems-manager">EC2 Instances Don&rsquo;t Show Up in Systems Manager</h3>
<p>If your EC2 Instances do not show up under &ldquo;Target selection&rdquo; they may not have the SSM agent installed. AWS has documentation to guide you through the installation process: 

<a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-manual-agent-install.html" target="_blank" rel="noopener">Manually installing SSM Agent on EC2 instances for Linux</a>.</p>
<p>If your EC2 Instances do have the SSM agent installed but still aren&rsquo;t showing up, they may not have an IAM instance profile. You can confirm this by clicking on an EC2 Instance and then checking under the &ldquo;IAM Role&rdquo; field. If this field is empty, assign an IAM Role and ensure it has proper permissions. In my case, I used two AWS managed policies <code>AmazonSSMManagedInstanceCore</code> and <code>AmazonSSMPatchAssociation</code>.</p>
<img src="/run-a-shell-command-on-ec2-instances/permission-policies.webp" alt="AWS IAM permission policies" width="720" height="299" style="max-width: 100%; height: auto; aspect-ratio: 1500 / 624;" loading="lazy" decoding="async">
<p>(The inline policy in the screenshot is to grant this Role S3 permissions for logging purposes which I cover in the next section.)</p>
<h3 id="no-log-output-to-s3">No Log Output to S3</h3>
<p>If you see no log output in your S3 bucket, you may need to add proper permissions to the instance profile IAM Role of the EC2 instances you selected.</p>
<p>Here is the policy I attached to the IAM instance profile role that got logging to work for me. Change the bucket name <code>my-bucket</code> accordingly!</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:PutObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:GetBucketAcl&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:GetBucketLocation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:GetObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;s3:ListBucket&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;arn:aws:s3:::my-bucket&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;arn:aws:s3:::my-bucket/*&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Getting EC2 Instance Metadata Using IMDSv2</title>
      <link>https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv2/?ref=rss</link>
      <pubDate>Fri, 01 Dec 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv2/?ref=rss</guid>
      <description>Curl examples for retrieving EC2 instance metadata using IMDSv2 including IP address, instance ID, and security groups.</description><content:encoded><![CDATA[<p>The EC2 Instance Metadata Service (IMDS) allows us to make an API call within an EC2 instance to retrieve instance metadata, such as a local IP address. There are 2 versions of IMDS.</p>
<p>Using IMDSv1, all we needed to do was to hit the <code>http://169.254.169.254/latest/meta-data/</code> endpoint to retrieve metadata. I previously created a post with some examples: 

<a href="https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv1/">Getting EC2 Instance Metadata Using IMDSv1</a>.</p>
<p>Using IMDSv2, we now need to make an API call to <code>http://169.254.169.254/latest/api/token</code> to retrieve a token, then include that token in a <code>X-aws-ec2-metadata-token</code> header to hit the metadata endpoint <code>http://169.254.169.254/latest/meta-data/</code>.</p>
<p>During EC2 creation you can configure your instance to use IMDSv2. This is what the setting looks like under &ldquo;Advanced details&rdquo; when creating an EC2 Instance. As of December 2023, it defaulted to &ldquo;V2 only (token required)&rdquo;:
<img src="/getting-ec2-metadata-using-imdsv2/imdsv2.webp" alt="AWS EC2 creation process" width="720" height="414" style="max-width: 100%; height: auto; aspect-ratio: 1140 / 656;" loading="lazy" decoding="async"></p>
<p>Once IMDSv2 is enabled on an instance, you can SSH into your instance and start making API calls from within.</p>
<h2 id="useful-examples">Useful Examples</h2>
<p>Here are some examples I&rsquo;ve found useful in my career.</p>
<p>View all available categories of metadata:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">ami-id
</span></span><span class="line"><span class="cl">ami-launch-index
</span></span><span class="line"><span class="cl">ami-manifest-path
</span></span><span class="line"><span class="cl">block-device-mapping/
</span></span><span class="line"><span class="cl">events/
</span></span><span class="line"><span class="cl">hostname
</span></span><span class="line"><span class="cl">iam/
</span></span><span class="line"><span class="cl">identity-credentials/
</span></span><span class="line"><span class="cl">instance-action
</span></span><span class="line"><span class="cl">instance-id
</span></span><span class="line"><span class="cl">instance-life-cycle
</span></span><span class="line"><span class="cl">instance-type
</span></span><span class="line"><span class="cl">local-hostname
</span></span><span class="line"><span class="cl">local-ipv4
</span></span><span class="line"><span class="cl">mac
</span></span><span class="line"><span class="cl">metrics/
</span></span><span class="line"><span class="cl">network/
</span></span><span class="line"><span class="cl">placement/
</span></span><span class="line"><span class="cl">profile
</span></span><span class="line"><span class="cl">public-hostname
</span></span><span class="line"><span class="cl">public-ipv4
</span></span><span class="line"><span class="cl">reservation-id
</span></span><span class="line"><span class="cl">security-groups
</span></span><span class="line"><span class="cl">services/
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance AMI ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/ami-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">ami-0230bd60aa48260c6
</span></span></code></pre></td></tr></table>
</blockquote><p>Get hostname:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/hostname
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">ip-172-31-42-147.ec2.internal
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/instance-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">i-0bca16d8b48523ac7
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance type:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/instance-type
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">t2.micro
</span></span></code></pre></td></tr></table>
</blockquote><p>Get local IPv4 address:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/local-ipv4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">172.31.42.147
</span></span></code></pre></td></tr></table>
</blockquote><p>Get AWS account ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/identity-credentials/ec2/info <span class="p">|</span> grep <span class="s2">&#34;AccountId&#34;</span> <span class="p">|</span> awk -F<span class="se">\&#34;</span> <span class="s1">&#39;{print $4}&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl"><span class="m">123456789012</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Get MAC address:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/mac
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">0e:53:26:7a:45:0b
</span></span></code></pre></td></tr></table>
</blockquote><p>Get availability zone:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/placement/availability-zone
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">us-east-1c
</span></span></code></pre></td></tr></table>
</blockquote><p>Get availability zone ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/placement/availability-zone-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">use1-az6
</span></span></code></pre></td></tr></table>
</blockquote><p>Get security groups associated with the instance:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="sb">`</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="sb">`</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="o">&amp;&amp;</span> curl -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/security-groups
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#---- output ----#</span>
</span></span><span class="line"><span class="cl">my-security-group-1
</span></span><span class="line"><span class="cl">my-security-group-2
</span></span><span class="line"><span class="cl">my-security-group-3
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="bash-script">Bash Script</h2>
<p>Copy and paste this Bash snippet and use the assigned values as needed:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">TOKEN</span><span class="o">=</span><span class="k">$(</span>curl -s -X PUT <span class="s2">&#34;http://169.254.169.254/latest/api/token&#34;</span> -H <span class="s2">&#34;X-aws-ec2-metadata-token-ttl-seconds: 21600&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">ACCOUNT_ID</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/identity-credentials/ec2/info <span class="p">|</span> grep <span class="s2">&#34;AccountId&#34;</span> <span class="p">|</span> awk -F<span class="se">\&#34;</span> <span class="s1">&#39;{print $4}&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">AMI_ID</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/ami-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">AVAILABILITY_ZONE</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/placement/availability-zone<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">AVAILABILITY_ZONE_ID</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/placement/availability-zone-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">HOSTNAME</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/hostname<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">INSTANCE_ID</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/instance-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">INSTANCE_TYPE</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/instance-type<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">LOCAL_IPV4</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/local-ipv4<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">MAC_ADDRESS</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/mac<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">SECURITY_GROUPS</span><span class="o">=</span><span class="k">$(</span>curl -s -H <span class="s2">&#34;X-aws-ec2-metadata-token: </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> http://169.254.169.254/latest/meta-data/security-groups<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$ACCOUNT_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AMI_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AVAILABILITY_ZONE</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AVAILABILITY_ZONE_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$HOSTNAME</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$INSTANCE_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$INSTANCE_TYPE</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$LOCAL_IPV4</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$MAC_ADDRESS</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$SECURITY_GROUPS</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="references">References</h2>
<ul>
<li>

<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Redirect One Domain to Another Using AWS S3 and CloudFront</title>
      <link>https://nelson.cloud/redirect-one-domain-to-another-using-aws-s3-and-cloudfront/?ref=rss</link>
      <pubDate>Thu, 16 Feb 2023 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/redirect-one-domain-to-another-using-aws-s3-and-cloudfront/?ref=rss</guid>
      <description>How to redirect a domain to another one using AWS S3, ACM, and CloudFront</description><content:encoded><![CDATA[<p>With AWS S3, it&rsquo;s possible to redirect one domain to another. This is useful when migrating domains.</p>
<p>I recently migrated my domain from <code>nelsonfigueroa.dev</code> to <code>nelson.cloud</code>, so I&rsquo;ll be using those domains as an example.</p>
<p>This guide assumes that you already have ownership or control of the domain you want to redirect.</p>
<h2 id="redirecting-the-old-domain-using-http">Redirecting The Old Domain Using HTTP</h2>
<p>This section covers the redirecting of the HTTP version of the old domain. In my case, this section covers how I redirected <code>http://nelsonfigueroa.dev</code> to <code>https://nelson.cloud</code>. There is an additional section you can read if you need to redirect the HTTPS version of the old domain.</p>
<h3 id="configuring-a-s3-bucket">Configuring a S3 Bucket</h3>
<p>To begin redirecting one domain to another, we need to create an S3 bucket.</p>
<p>Go to 

<a href="https://s3.console.aws.amazon.com/s3/" target="_blank" rel="noopener">Amazon S3</a> and click the &ldquo;Create bucket&rdquo; button.
I prefer to name the bucket with the same name as the domain being redirected, but you can choose any name you want.</p>
<p>Click into the bucket and then click on the &ldquo;Properties&rdquo; tab. Scroll to the bottom to the &ldquo;Static website hosting&rdquo; section and click the &ldquo;Edit&rdquo; button.</p>
<p>Set the following configuration:</p>
<ul>
<li>Under &ldquo;Static website hosting&rdquo; click the &ldquo;Enable&rdquo; radio button</li>
<li>Under &ldquo;Hosting type&rdquo; select &ldquo;Host a static website&rdquo;</li>
<li>Under &ldquo;Index document&rdquo; type in &ldquo;index.html&rdquo;, even though we won&rsquo;t need an index.html file.</li>
</ul>
<p>Finally, under the &ldquo;Redirection rules&rdquo; text box add the following (change the domain for your needs):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;Redirect&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;HostName&#34;</span><span class="p">:</span> <span class="s2">&#34;nelson.cloud&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Protocol&#34;</span><span class="p">:</span> <span class="s2">&#34;https&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This rule will redirect to the new domain and append paths as well. So if a user goes to <code>nelsonfigueroa.dev/about</code> they will be redirected to <code>https://nelson.cloud/about</code>.</p>
<blockquote><p><strong>Tip:</strong></p>If you want to redirect to HTTP instead of HTTPS, you can change <code>&quot;Protocol&quot;</code> to <code>&quot;http&quot;</code>.</blockquote>

<img src="/redirect-one-domain-to-another/bucket-configuration.webp" alt="S3 bucket configuration" width="720" height="727" style="max-width: 100%; height: auto; aspect-ratio: 1516 / 1532;" loading="lazy" decoding="async">
<p>Then click the &ldquo;Save changes&rdquo; button at the very bottom of the page.</p>
<p>Make a note of the &ldquo;Static website endpoint&rdquo; URL at the bottom of the &ldquo;Properties&rdquo; tab. Next, we&rsquo;ll need to point the old domain to this endpoint.</p>
<h3 id="setting-up-dns-for-the-old-domain">Setting Up DNS for the Old Domain</h3>
<p>Next, we&rsquo;ll need to create a CNAME record that points the old domain to the bucket website endpoint. We can use Route 53 to do this.</p>
<h4 id="creating-a-cname-record-in-route-53">Creating a CNAME Record in Route 53</h4>
<p>On Amazon Web Services, browse to the Route 53 console.</p>
<p>First, make sure you have a public hosted zone in Route 53. If not, create one and specify the old domain name:</p>
<img src="/redirect-one-domain-to-another/creating-hosted-zone.webp" alt="Route 53 hosted zone" width="720" height="521" style="max-width: 100%; height: auto; aspect-ratio: 1662 / 1204;" loading="lazy" decoding="async">
<p>You will need to update the DNS nameservers for your domain specifying the nameservers that AWS provides to you in the hosted zone. This varies depending on the registrar so I will let you do some Googling for this part. It&rsquo;s pretty easy though :).</p>
<p>Next, create a CNAME record in the new hosted zone that points the old domain to the S3 Bucket Website endpoint.</p>
<blockquote><p><strong>Tip:</strong></p>In Route 53 specifically you can create an <code>A</code> record, enable the &ldquo;Alias&rdquo; toggle, and then select the S3 Bucket website endpoint. This is an alternative solution.</blockquote>

<img src="/redirect-one-domain-to-another/cname-record.webp" alt="CNAME record" width="720" height="464" style="max-width: 100%; height: auto; aspect-ratio: 2208 / 1424;" loading="lazy" decoding="async">
<p>After the CNAME record is created (and changes have propagated), all requests going to the old domain will be redirected to the new domain.</p>
<blockquote><p><strong>Warning:</strong></p>This will only work with HTTP requests to the old domain and <em>not</em> HTTPS requests.</blockquote>

<p>In my case, <code>http://nelsonfigueroa.dev</code> will be redirected to <code>https://nelson.cloud</code>, but <code>https://nelsonfigueroa.dev</code> will time out. This is because there is nothing redirecting the HTTPS version of the old domain.</p>
<p>If this isn&rsquo;t a problem to you, you are free to stop here. Otherwise, read on.</p>
<h2 id="redirecting-the-old-domain-using-https">Redirecting the Old Domain Using HTTPS</h2>
<p>To redirect the old domain when using HTTPS we&rsquo;ll need an ACM SSL Certificate and a CloudFront Distribution. This will allow the HTTPS version of the old domain to be redirected.</p>
<h3 id="requesting-an-acm-ssl-certificate">Requesting an ACM SSL Certificate</h3>
<p>Browse to the AWS Certificate Manager console. Make sure you are in the us-east-1 region by looking at the region on the top-right corner of the screen. Only ACM certificates from the us-east-1 region will work with a CloudFront Distribution.</p>
<p>Once you are sure you&rsquo;re in the us-east-1 region, click on &ldquo;Request certificate&rdquo; on the left-hand sidebar.</p>
<p>For the &ldquo;Certificate type&rdquo;, select &ldquo;Request a public certificate&rdquo;. Then click the &ldquo;Next&rdquo; button:</p>
<img src="/redirect-one-domain-to-another/request-certificate.webp" alt="Requesting public certificate" width="720" height="228" style="max-width: 100%; height: auto; aspect-ratio: 2214 / 702;" loading="lazy" decoding="async">
<p>In the following form, do the following:</p>
<ul>
<li>For &ldquo;Fully qualified domain name&rdquo;, enter the old domain (<code>nelsonfigueroa.dev</code> in my case)</li>
<li>For &ldquo;Validation method&rdquo;, select &ldquo;DNS validation&rdquo;</li>
<li>For &ldquo;Key algorithm&rdquo;, it&rsquo;s okay to leave it at RSA 2048. You can select a different algorithm if you&rsquo;d like.</li>
</ul>
<p>Then click the &ldquo;Request&rdquo; button on the bottom of the page.</p>
<img src="/redirect-one-domain-to-another/certificate-configuration.webp" alt="Certificate configuration" width="720" height="523" style="max-width: 100%; height: auto; aspect-ratio: 2208 / 1606;" loading="lazy" decoding="async">
<p>After the certificate is created we&rsquo;ll need to validate the certificate (we need to prove to AWS that we own this domain by creating a CNAME record in the hosted zone for this domain). Click into the certificate and scroll down to the &ldquo;Domains&rdquo; section. There will be a table with two columns named &ldquo;CNAME name&rdquo; and &ldquo;CNAME value&rdquo;. You will need to create a CNAME record in Route 53 with this name/value combination following the same procedure as before.</p>
<p>After the CNAME record is created, wait around 15 minutes and the certificate will be validated and you should see &ldquo;Success&rdquo; under the &ldquo;Status&rdquo; and &ldquo;Renewal status&rdquo; columns:</p>
<img src="/redirect-one-domain-to-another/validated-certificate.webp" alt="Validated certificate" width="720" height="78" style="max-width: 100%; height: auto; aspect-ratio: 2694 / 292;" loading="lazy" decoding="async">
<p>Now we can create a CloudFront Distribution and associate this certificate to it.</p>
<h3 id="creating-a-cloudfront-distribution">Creating a CloudFront Distribution</h3>
<p>Browse to the CloudFront console and click on the &ldquo;Create distribution&rdquo; button on the top right.</p>
<p>Fill out the form with the following:</p>
<p>For &ldquo;Origin domain&rdquo; select the S3 bucket you created for your old domain. Or you can paste in the S3 bucket website endpoint from earlier (this is what I did). Either one of these work for our case.</p>
<img src="/redirect-one-domain-to-another/origin.webp" alt="CloudFront Distribution origin" width="720" height="588" style="max-width: 100%; height: auto; aspect-ratio: 1646 / 1346;" loading="lazy" decoding="async">
<p>For &ldquo;Viewer protocol policy&rdquo;, select &ldquo;Redirect HTTP to HTTPS&rdquo;:</p>
<img src="/redirect-one-domain-to-another/viewer-protocol-policy.webp" alt="CloudFront Distribution viewer protocol policy" width="438" height="210" style="max-width: 100%; height: auto; aspect-ratio: 438 / 210;" loading="lazy" decoding="async">
<p>Under &ldquo;Alternate domain name (CNAME)&rdquo;, click the &ldquo;Add item&rdquo; button and enter the old domain in the field that appears (<code>nelsonfigueroa.dev</code> in my case):</p>
<img src="/redirect-one-domain-to-another/alternate-domain-name.webp" alt="CloudFront Distribution alternate domain name" width="720" height="173" style="max-width: 100%; height: auto; aspect-ratio: 1132 / 272;" loading="lazy" decoding="async">
<p>Under &ldquo;Custom SSL certificate&rdquo;, select the ACM certificate that you created earlier.</p>
<img src="/redirect-one-domain-to-another/ssl-certificate.webp" alt="CloudFront Distribution SSL certificate" width="720" height="120" style="max-width: 100%; height: auto; aspect-ratio: 1412 / 236;" loading="lazy" decoding="async">
<p>The rest of the settings can remain as is. Click the &ldquo;Create distribution&rdquo; button on the bottom of the page. Wait a few minutes for the distribution to deploy out. You&rsquo;ll know deployment is done when the “Last modified” field displays a date. Make a note of the &ldquo;Domain name&rdquo; column for your distribution. You&rsquo;ll need this value for the next step.</p>
<h3 id="updating-dns-to-use-the-new-cloudfront-distribution">Updating DNS to use the new CloudFront Distribution</h3>
<p>The final step is to update the previous record so that it points to the distribution instead of the bucket website endpoint.</p>
<p>Go to Route 53. Click into the hosted zone for the old domain. Then edit the record that was previously created. Instead of entering the S3 Bucket website endpoint in the &ldquo;Value&rdquo; field, enter the CloudFront Distribution domain name. Then click &ldquo;Save&rdquo;.</p>
<img src="/redirect-one-domain-to-another/editing-record.webp" alt="Editing Route 53 CNAME record" width="720" height="257" style="max-width: 100%; height: auto; aspect-ratio: 2740 / 980;" loading="lazy" decoding="async">
<p>Now both <code>http://nelsonfigueroa.dev</code> and <code>https://nelsonfigueroa.dev/</code> will redirect to the new domain.</p>
<h2 id="testing-the-redirect">Testing the Redirect</h2>
<p>You can test this out by entering the old domain in a browser prefixed with <code>https://</code>. Or you can test this out in a terminal with <code>curl</code>.</p>
<p>The HTTP version of the domain should give us a location header redirecting us to <code>https://nelsonfigueroa.dev</code>:</p>
<pre tabindex="0"><code>$ curl -IX GET http://nelsonfigueroa.dev

HTTP/1.1 301 Moved Permanently
Server: CloudFront
Date: Thu, 16 Feb 2023 05:55:35 GMT
Content-Type: text/html
Content-Length: 167
Connection: keep-alive
Location: https://nelsonfigueroa.dev/
</code></pre><p>And the HTTPS version of the domain should then redirect to the new domain <code>https://nelson.cloud</code>:</p>
<pre tabindex="0"><code>$ curl -IX GET https://nelsonfigueroa.dev

HTTP/2 301
content-length: 0
location: https://nelson.cloud/index.html
date: Thu, 16 Feb 2023 05:42:44 GMT
server: AmazonS3
x-cache: Miss from cloudfront
</code></pre><p>As a final test, we can make sure that paths are also redirected to the new domain. In other words, <code>https://nelsonfigueroa.dev/test-path/</code> should redirect to <code>https://nelson.cloud/test-path/</code>, and we should see this value in the <code>location</code> header:</p>
<pre tabindex="0"><code>$ curl -IX GET https://nelsonfigueroa.dev/test-path/

HTTP/2 301
content-length: 0
location: https://nelson.cloud/test-path/
date: Thu, 16 Feb 2023 05:58:52 GMT
server: AmazonS3
x-cache: Miss from cloudfront
</code></pre><p>It works, we are done!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hosting a Static Website on AWS Using S3 and CloudFront</title>
      <link>https://nelson.cloud/hosting-a-static-website-on-aws-using-s3-and-cloudfront/?ref=rss</link>
      <pubDate>Fri, 30 Dec 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/hosting-a-static-website-on-aws-using-s3-and-cloudfront/?ref=rss</guid>
      <description>How to host a static website on Amazon Web Services using S3, CloudFront, ACM, and Route 53</description><content:encoded><![CDATA[<h2 id="overview">Overview</h2>
<p>Using Amazon Web Services, we can easily set up a static site worldwide without the need to maintain servers and load balancers.</p>
<p>In this guide, I&rsquo;ll go over how I set up this site on AWS. I used the following AWS services:</p>
<ul>
<li>

<a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener">S3</a></li>
<li>

<a href="https://aws.amazon.com/certificate-manager/" target="_blank" rel="noopener">Certificate Manager</a></li>
<li>

<a href="https://aws.amazon.com/cloudfront/" target="_blank" rel="noopener">CloudFront</a></li>
<li>

<a href="https://aws.amazon.com/route53/" target="_blank" rel="noopener">Route 53</a></li>
</ul>
<p>I recently migrated my domain from <code>nelsonfigueroa.dev</code> to <code>nelson.cloud</code>, so I&rsquo;ll be using this new domain as an example.</p>
<p>This guide assumes the following:</p>
<ul>
<li>You have already purchased a domain from a registrar like 

<a href="https://www.namecheap.com/" target="_blank" rel="noopener">Namecheap</a>, 

<a href="https://www.gandi.net/" target="_blank" rel="noopener">Gandi</a>, or 

<a href="https://porkbun.com/" target="_blank" rel="noopener">Porkbun</a>.</li>
<li>You have already signed up for an Amazon Web Services account.</li>
</ul>
<h2 id="creating-an-s3-bucket">Creating an S3 Bucket</h2>
<p>First, we&rsquo;ll need to create an S3 bucket. This is where static files (HTML/CSS/JavaScript/Images, etc) will go.</p>
<p>In the AWS console, browse to S3. Create a new bucket. I named my bucket with the same name as my domain.</p>
<p>Then click on the bucket and go to the &ldquo;properties&rdquo; tab. Scroll all the way to the bottom to the &ldquo;Static website hosting&rdquo; section. Click the &ldquo;Edit&rdquo; button. Now configure this bucket to host a static site:</p>
<ul>
<li>Under &ldquo;Static website hosting&rdquo; select &ldquo;Enable&rdquo;</li>
<li>Under &ldquo;Hosting type&rdquo; select &ldquo;Host a static website&rdquo;</li>
<li>In the &ldquo;Index document&rdquo; field, write in &ldquo;index.html&rdquo; (unless you want a different root page for your site)</li>
<li>Then click &ldquo;Save changes&rdquo;</li>
</ul>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/s3-static-website-hosting.webp" alt="Static website hosting configuration." width="720" height="729" style="max-width: 100%; height: auto; aspect-ratio: 1516 / 1536;" loading="lazy" decoding="async">
<p>Then go to the &ldquo;Permissions&rdquo; tab in the bucket. Click the &ldquo;Edit&rdquo; button under the &ldquo;Block public access (bucket settings)&rdquo; section.
Uncheck the &ldquo;Block <em>all</em> public access&rdquo; checkbox. We need this bucket to have public access so the site is viewable across the internet.
Save changes.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/bucket-public-access.webp" alt="S3 bucket access." width="720" height="586" style="max-width: 100%; height: auto; aspect-ratio: 1572 / 1280;" loading="lazy" decoding="async">
<p>Next, still under the &ldquo;Permissions tab&rdquo;, click the &ldquo;Edit&rdquo; button on the &ldquo;Bucket policy&rdquo; section. We need to add permissions for public access. Here is what the policy should look like. Change the bucket name after <code>Resource</code> to your bucket name:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Sid&#34;</span><span class="p">:</span> <span class="s2">&#34;PublicReadGetObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Principal&#34;</span><span class="p">:</span> <span class="s2">&#34;*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="s2">&#34;s3:GetObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="s2">&#34;arn:aws:s3:::nelson.cloud/*&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Then save changes.</p>
<p>At this point &ldquo;Block <em>all</em> public access&rdquo; should be off, and the bucket policy should show under the section:</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/completed-bucket-permissions.webp" alt="Completed S3 bucket configuration." width="720" height="410" style="max-width: 100%; height: auto; aspect-ratio: 2202 / 1254;" loading="lazy" decoding="async">
<p>To verify that everything works, we can upload some HTML files to test out the site.</p>
<p>Go to the &ldquo;Objects&rdquo; tab on the bucket and drop in your static files. Even a single <code>index.html</code> file will do. In my case I have a lot more files since my site has blog posts and other things:</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/bucket-objects.webp" alt="Objects in S3 bucket." width="720" height="271" style="max-width: 100%; height: auto; aspect-ratio: 2638 / 996;" loading="lazy" decoding="async">
<p>Then go to the &ldquo;Properties&rdquo; tab and at the very bottom you should see a &ldquo;Bucket website endpoint&rdquo;. Click on this link and your site should open in your browser. If you see the contents of your HTML file(s) then you are all done here with S3.</p>
<p>The AWS docs provide more information about S3 permissions for static hosting if you&rsquo;re curious:</p>
<ul>
<li>

<a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteAccessPermissionsReqd.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteAccessPermissionsReqd.html</a></li>
</ul>
<h2 id="creating-an-acm-ssl-certificate">Creating an ACM SSL Certificate</h2>
<p>Next we need to request an SSL certificate in ACM that we&rsquo;ll use with CloudFront. This will allow us to set up our site with HTTPS.</p>
<p>In AWS, go to &ldquo;AWS Certificate Manager (ACM)&rdquo;.</p>
<blockquote><p><strong>Warning:</strong></p>SSL certificates used with CloudFront must be in the us-east-1 region. Check the upper right corner and make sure you are in the us-east-1 region before proceeding.</blockquote>

<p>Then click the &ldquo;Request&rdquo; button.</p>
<p>For &ldquo;Certificate type&rdquo; select &ldquo;Request a public certificate&rdquo;. Then click Next.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/requesting-ssl-certificate.webp" alt="Requesting an ACM SSL certificate." width="720" height="236" style="max-width: 100%; height: auto; aspect-ratio: 2122 / 696;" loading="lazy" decoding="async">
<p>Then, under &ldquo;Fully qualified domain name&rdquo; write in the name of your domain
I also like to add in support for all subdomains. If you want to do this, add in the wildcard subdomain <code>*.yourdomain.com</code></p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/ssl-cert-domains.webp" alt="Adding domain names to ACM SSL certificate." width="720" height="196" style="max-width: 100%; height: auto; aspect-ratio: 1906 / 520;" loading="lazy" decoding="async">
<p>Under &ldquo;Validation method&rdquo; select DNS validation.
Under &ldquo;Key algorithm&rdquo; select &ldquo;RSA 2048&rdquo; or higher if you&rsquo;d like</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/ssl-cert-validation-and-key-algorithm.webp" alt="Validation method and key algorithm for ACM SSL certificate." width="720" height="317" style="max-width: 100%; height: auto; aspect-ratio: 1976 / 870;" loading="lazy" decoding="async">
<p>Then click the &ldquo;Request&rdquo; button.</p>
<p>You&rsquo;ll see that the new certificate says &ldquo;Pending validation&rdquo; under the &ldquo;Status&rdquo; column. Click into your certificate.</p>
<p>Under &ldquo;Domains&rdquo; you should see the columns &ldquo;CNAME name&rdquo; and &ldquo;CNAME value&rdquo;.
You&rsquo;ll have to create a CNAME record in Route 53 to validate this certificate.</p>
<p>Note that in my case the CNAME names and values are repeated. I only had to create the record once to validate both domains.</p>
<blockquote><p><strong>Info:</strong></p>The CNAME record(s) can also be created on your registrar, but I chose to do it all on AWS to keep things in one place.</blockquote>

<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/cname-record.webp" alt="ACM SSL certificate CNAME records." width="720" height="169" style="max-width: 100%; height: auto; aspect-ratio: 1440 / 338;" loading="lazy" decoding="async">
<p>Go to Route 53 and create a public hosted zone for your domain (note that at the time of this writing hosted zones are 50 cents per month).</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/hosted-zone.webp" alt="Creating a Hosted Zone in Route 53." width="720" height="554" style="max-width: 100%; height: auto; aspect-ratio: 1516 / 1168;" loading="lazy" decoding="async">
<p>Once the zone is created, go back to your certificate and click on the upper right button that says &ldquo;Create records in Route 53&rdquo;.
AWS will add the records for you without any manual work on your part.</p>
<p>After the record(s) are created, wait some time until the certificate is validated. In my case, it took around 10 minutes.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/certificate-validated.webp" alt="Validated ACM SSL certificate." width="720" height="162" style="max-width: 100%; height: auto; aspect-ratio: 968 / 218;" loading="lazy" decoding="async">
<p>This page from the AWS documentation elaborates more on DNS validation for ACM SSL Certificates:</p>
<ul>
<li>

<a href="https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html</a></li>
</ul>
<h2 id="creating-a-cloudfront-distribution">Creating a CloudFront Distribution</h2>
<p>Next, we need to create a CloudFront distribution. The distribution will use the bucket and ACM certificate we created in order to host our site.</p>
<p>CloudFront is 

<a href="https://aws.amazon.com/cloudfront/" target="_blank" rel="noopener">Amazon&rsquo;s content delivery network (CDN)</a>. By deploying our site on CloudFront, our site will be available worldwide with low latency.</p>
<p>First, we&rsquo;ll need the S3 bucket website endpoint that we clicked on earlier.</p>
<p>Go to the S3 bucket you created.
Then, under the &ldquo;Properties&rdquo; tab, scroll all the way down and copy the value of &ldquo;Bucket website endpoint&rdquo;</p>
<p>Then go to the CloudFront service in the AWS console. Click the &ldquo;Create distribution&rdquo; button on the top right.</p>
<p>Under &ldquo;Origin domain&rdquo;  paste the value of &ldquo;Bucket website endpoint&rdquo; that you copied. The value should be something like <code>http://mybucket.s3-website-us-east-1.amazonaws.com</code></p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/origin-domain.webp" alt="CloudFront Distribution Origin domain." width="720" height="129" style="max-width: 100%; height: auto; aspect-ratio: 1086 / 196;" loading="lazy" decoding="async">
<p>Under &ldquo;Viewer protocol policy&rdquo; I like to select &ldquo;Redirect HTTP to HTTPS&rdquo;, essentially disallowing HTTP connections.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/viewer-protocol-policy.webp" alt="CloudFront Distribution viewer protocol policy." width="720" height="191" style="max-width: 100%; height: auto; aspect-ratio: 806 / 214;" loading="lazy" decoding="async">
<p>Under &ldquo;Alternate domain name (CNAME)&rdquo;, add in your domain.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/alternate-domain-name.webp" alt="CloudFront Distribution Alternate domain name (CNAME)." width="720" height="174" style="max-width: 100%; height: auto; aspect-ratio: 1026 / 248;" loading="lazy" decoding="async">
<p>Under &ldquo;Custom SSL certificate&rdquo; select the ACM certificate that you created earlier. It should show up in the drop-down menu.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/custom-ssl-certificate.webp" alt="CloudFront Distribution custom SSL certificate." width="720" height="123" style="max-width: 100%; height: auto; aspect-ratio: 1264 / 216;" loading="lazy" decoding="async">
<p>Under &ldquo;default root object&rdquo; write in <code>index.html</code> or whatever you want your root to be. This is the page that gets loaded by default when hitting your domain without a path (<code>https://nelson.cloud/</code> in this case).</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/default-root-object.webp" alt="CloudFront Distribution default root object." width="720" height="120" style="max-width: 100%; height: auto; aspect-ratio: 1084 / 182;" loading="lazy" decoding="async">
<p>The rest of the settings can stay as is. Click the &ldquo;Create distribution&rdquo; button.
Allow some time for the distribution to deploy.
Once the &ldquo;Last modified&rdquo; field doesn&rsquo;t say &ldquo;Deploying&rdquo; and displays a date, it&rsquo;s done.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/distribution-created.webp" alt="Deployed CloudFront Distribution." width="720" height="51" style="max-width: 100%; height: auto; aspect-ratio: 1892 / 136;" loading="lazy" decoding="async">
<p>I chose to use the S3 website endpoint as the Origin, but you can also use the S3 bucket itself. I chose the website endpoint because my site has multiple <code>index.html</code> templates 

<a href="https://nelson.cloud/resolving-aws-cloudfront-access-denied-errors/">which can give errors in CloudFront</a>.</p>
<p>More on Origins in the AWS documentation:</p>
<ul>
<li>

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html" target="_blank" rel="noopener">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html</a></li>
</ul>
<h3 id="adding-a-404-page-to-the-cloudfront-distribution-optional">Adding a 404 Page to the CloudFront Distribution (Optional)</h3>
<p>CloudFront Distributions have barebones and unstylized error pages like this 404 page:</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/404.webp" alt="Default CloudFront 404 page." width="720" height="151" style="max-width: 100%; height: auto; aspect-ratio: 1566 / 330;" loading="lazy" decoding="async">
<p>If we want to use our own custom error pages, we need to create a custom error response.</p>
<p>Click into your newly created CloudFront Distribution. Click on the &ldquo;Error pages&rdquo; tab and then click the &ldquo;Create custom error response&rdquo; button.</p>
<p>Select the following settings:</p>
<ul>
<li>&ldquo;HTTP error code&rdquo; should be &ldquo;404: Not Found&rdquo;</li>
<li>Error caching minimum TTL can stay as is (10)</li>
<li>Select &ldquo;Yes&rdquo; for &ldquo;Customize error response&rdquo;</li>
<li>The &ldquo;Response page path&rdquo; should be &ldquo;/404.html&rdquo; (or whatever you want your 404 page file name to be).
<ul>
<li>You&rsquo;ll need to create a <code>404.html</code> template and place it in your S3 Bucket.</li>
</ul>
</li>
<li>Select &ldquo;404: Not Found&rdquo; for the &ldquo;HTTP Response code&rdquo; field</li>
<li>Click the &ldquo;Create custom error response&rdquo; button</li>
</ul>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/custom-error.webp" alt="Creating a custom error page." width="720" height="622" style="max-width: 100%; height: auto; aspect-ratio: 1508 / 1304;" loading="lazy" decoding="async">
<p>Give your distribution a few minutes to update and you&rsquo;re done here.</p>
<p>There are several other error codes you can account for using the same process. I only added a 404 page since it&rsquo;s one of the most common.</p>
<h2 id="pointing-a-custom-domain-to-the-cloudfront-distribution">Pointing a Custom Domain to the CloudFront Distribution</h2>
<p>The last step is to point your domain to the cloudfront distribution.
For this step, I once again used Route 53, but you can also create a CNAME or ALIAS record in your domain&rsquo;s registrar and get the same results.</p>
<p>Go to Route 53 again. Click on the Hosted Zone you created earlier.
Create a record with the following settings:</p>
<ul>
<li>Leave the subdomain blank, unless you want your site to be accessible under a subdomain like <code>blog.mysite.com</code>.</li>
<li>For Record type, select &ldquo;A - Routes traffic to an IPv4 address and some AWS resources.&rdquo;</li>
<li>Click the &ldquo;Alias&rdquo; toggle to enable it.</li>
<li>Under &ldquo;Route traffic to&rdquo; select &ldquo;Alias to CloudFront distribution&rdquo;</li>
<li>Under the &ldquo;Choose distribution&rdquo; field select the distribution you created.</li>
<li>The routing policy can stay as is.</li>
</ul>
<p>Then click the &ldquo;Create records&rdquo; button.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/route-53-record.webp" alt="Creating a Route 53 record." width="720" height="446" style="max-width: 100%; height: auto; aspect-ratio: 2004 / 1244;" loading="lazy" decoding="async">
<p>It will take some time for these changes to propagate across DNS servers. In my experience, it&rsquo;s never been more than around 15 minutes. Usually faster.</p>
<h2 id="viewing-the-live-site">Viewing The Live Site</h2>
<p>After adding the DNS record and waiting some time, you should be able to go to your domain on a browser and see your site.</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/live-site.webp" alt="Live site." width="720" height="348" style="max-width: 100%; height: auto; aspect-ratio: 2158 / 1044;" loading="lazy" decoding="async">
<p>You can also test out your custom error page(s). In my case, I browsed to 

<a href="https://nelson.cloud/test" target="_blank" rel="noopener">https://nelson.cloud/test</a> to see my custom 404 page:</p>
<img src="/hosting-a-static-site-on-aws-using-s3-and-cloudfront/404-custom.webp" alt="Custom 404 page." width="720" height="277" style="max-width: 100%; height: auto; aspect-ratio: 1774 / 684;" loading="lazy" decoding="async">
<p>Congrats! Your site is now live.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Finding AWS Resources by IP Address</title>
      <link>https://nelson.cloud/finding-aws-resources-by-ip-address/?ref=rss</link>
      <pubDate>Sun, 12 Jun 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/finding-aws-resources-by-ip-address/?ref=rss</guid>
      <description>Ways to find AWS Resources by private or public IP addresses.</description><content:encoded><![CDATA[<h2 id="finding-ec2-instances-by-ip-address">Finding EC2 Instances by IP Address</h2>
<h3 id="in-the-ec2-console">In the EC2 Console</h3>
<p>In the AWS EC2 Management Console, you can search for EC2 instances using a private or public IP address. Filter by either <code>Private IP address</code> or <code>Public IPv4 address</code> in the search field:</p>
<img src="/finding-aws-resources-by-ip-address/ec2-private-ip-search.webp" alt="Results after searching for an EC2 Instance with a private IP address." width="720" height="185" style="max-width: 100%; height: auto; aspect-ratio: 1506 / 388;" loading="lazy" decoding="async">
<img src="/finding-aws-resources-by-ip-address/ec2-public-ip-search.webp" alt="Results after searching for an EC2 Instance with a public IP address." width="720" height="182" style="max-width: 100%; height: auto; aspect-ratio: 1492 / 378;" loading="lazy" decoding="async">
<h3 id="using-the-aws-cli">Using the AWS CLI</h3>
<p>The AWS CLI can be used to find EC2 instances by either private or public IP address.</p>
<h4 id="using-a-private-ip-address">Using a Private IP Address</h4>
<p>To find EC2 instances by private IP address, the command looks like this (Replace <code>--region</code> with your region if it&rsquo;s not set by default. Replace <code>Values</code> with the IP address):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">aws ec2 describe-instances --region<span class="o">=</span>us-west-1 --filter <span class="nv">Name</span><span class="o">=</span>private-ip-address,Values<span class="o">=</span>10.0.0.1
</span></span></code></pre></td></tr></table>
</blockquote><h4 id="using-a-public-ip-address">Using a Public IP Address</h4>
<p>To find EC2 instances by public IP address, the <code>Name</code> filter changes to <code>ip-address</code> but otherwise the command is the same as the one from above:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">aws ec2 describe-instances --region<span class="o">=</span>us-west-1 --filter <span class="nv">Name</span><span class="o">=</span>ip-address,Values<span class="o">=</span>54.123.45.67
</span></span></code></pre></td></tr></table>
</blockquote><h4 id="specifying-multiple-ip-addresses">Specifying Multiple IP Addresses</h4>
<p>For either of these commands, you can specify several IP addresses by adding them to the <code>Values</code> filter as such:</p>
<p>For public IP addresses:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">aws ec2 describe-instances --region<span class="o">=</span>us-west-1 --filter <span class="nv">Name</span><span class="o">=</span>ip-address,Values<span class="o">=</span>54.123.45.67,54.123.45.68,54.123.45.69
</span></span></code></pre></td></tr></table>
</blockquote><p>For private IP addresses:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">aws ec2 describe-instances --region<span class="o">=</span>us-west-1 --filter <span class="nv">Name</span><span class="o">=</span>private-ip-address,Values<span class="o">=</span>10.0.0.1,10.0.0.2,10.0.0.3
</span></span></code></pre></td></tr></table>
</blockquote><h3 id="example-output">Example Output</h3>
<p>The output for these commands will look something like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Reservations&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Groups&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Instances&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;AmiLaunchIndex&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;ImageId&#34;</span><span class="p">:</span> <span class="s2">&#34;ami-1234abcd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;InstanceId&#34;</span><span class="p">:</span> <span class="s2">&#34;i-001122334455abcd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;InstanceType&#34;</span><span class="p">:</span> <span class="s2">&#34;t3.micro&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;KeyName&#34;</span><span class="p">:</span> <span class="s2">&#34;key-name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;LaunchTime&#34;</span><span class="p">:</span> <span class="s2">&#34;2022-01-01T00:00:00.000Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Monitoring&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;State&#34;</span><span class="p">:</span> <span class="s2">&#34;enabled&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Placement&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;AvailabilityZone&#34;</span><span class="p">:</span> <span class="s2">&#34;us-west-1a&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Tenancy&#34;</span><span class="p">:</span> <span class="s2">&#34;default&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PrivateDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;ip-10-0-0-1.ec2.internal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PrivateIpAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;10.0.0.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;ProductCodes&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PublicDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;State&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Code&#34;</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Name&#34;</span><span class="p">:</span> <span class="s2">&#34;running&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;StateTransitionReason&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;SubnetId&#34;</span><span class="p">:</span> <span class="s2">&#34;subnet-1234abcd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;VpcId&#34;</span><span class="p">:</span> <span class="s2">&#34;vpc-1234abcd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Architecture&#34;</span><span class="p">:</span> <span class="s2">&#34;x86_64&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;BlockDeviceMappings&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                        <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;DeviceName&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev/xvda&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Ebs&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;AttachTime&#34;</span><span class="p">:</span> <span class="s2">&#34;2022-01-01T00:00:00.000Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;DeleteOnTermination&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;Status&#34;</span><span class="p">:</span> <span class="s2">&#34;attached&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;VolumeId&#34;</span><span class="p">:</span> <span class="s2">&#34;vol-012345abcdef&#34;</span>
</span></span><span class="line"><span class="cl">                            <span class="p">}</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;ClientToken&#34;</span><span class="p">:</span> <span class="s2">&#34;00000000&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;EbsOptimized&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Hypervisor&#34;</span><span class="p">:</span> <span class="s2">&#34;xen&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;IamInstanceProfile&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Arn&#34;</span><span class="p">:</span> <span class="s2">&#34;arn:aws:iam::000000000000:instance-profile/my-instance-profile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Id&#34;</span><span class="p">:</span> <span class="s2">&#34;ABCDEF12345&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;NetworkInterfaces&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                        <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Attachment&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;AttachTime&#34;</span><span class="p">:</span> <span class="s2">&#34;2022-01-01T00:00:00.000Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;AttachmentId&#34;</span><span class="p">:</span> <span class="s2">&#34;eni-attach-abcd1234&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;DeleteOnTermination&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;DeviceIndex&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;Status&#34;</span><span class="p">:</span> <span class="s2">&#34;attached&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="nt">&#34;NetworkCardIndex&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">                            <span class="p">},</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Description&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Groups&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;security-group-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;GroupId&#34;</span><span class="p">:</span> <span class="s2">&#34;sg-abc123&#34;</span>
</span></span><span class="line"><span class="cl">                                <span class="p">},</span>
</span></span><span class="line"><span class="cl">                                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;security-group-2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;GroupId&#34;</span><span class="p">:</span> <span class="s2">&#34;sg-def456&#34;</span>
</span></span><span class="line"><span class="cl">                                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                            <span class="p">],</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Ipv6Addresses&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;MacAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;aa:00:bb:22:cc:33&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;NetworkInterfaceId&#34;</span><span class="p">:</span> <span class="s2">&#34;eni-abcd1234&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;OwnerId&#34;</span><span class="p">:</span> <span class="s2">&#34;112233445566&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;PrivateDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;ip-10-0-0-1.ec2.internal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;PrivateIpAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;10.0.0.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;PrivateIpAddresses&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;Primary&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;PrivateDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;ip-10-0-0-1.ec2.internal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="nt">&#34;PrivateIpAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;10.0.0.1&#34;</span>
</span></span><span class="line"><span class="cl">                                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                            <span class="p">],</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;SourceDestCheck&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Status&#34;</span><span class="p">:</span> <span class="s2">&#34;in-use&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;SubnetId&#34;</span><span class="p">:</span> <span class="s2">&#34;subnet-abcd1234&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;VpcId&#34;</span><span class="p">:</span> <span class="s2">&#34;vpc-abcd1234&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;InterfaceType&#34;</span><span class="p">:</span> <span class="s2">&#34;interface&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;RootDeviceName&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev/xvda&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;RootDeviceType&#34;</span><span class="p">:</span> <span class="s2">&#34;ebs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;SecurityGroups&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                        <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;security-group-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;GroupId&#34;</span><span class="p">:</span> <span class="s2">&#34;sg-123abc&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">},</span>
</span></span><span class="line"><span class="cl">                        <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;security-group-2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;GroupId&#34;</span><span class="p">:</span> <span class="s2">&#34;sg-456def&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;SourceDestCheck&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Tags&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                        <span class="p">{</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Key&#34;</span><span class="p">:</span> <span class="s2">&#34;App&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">&#34;Value&#34;</span><span class="p">:</span> <span class="s2">&#34;Testing App&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;VirtualizationType&#34;</span><span class="p">:</span> <span class="s2">&#34;hvm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;CpuOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;CoreCount&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;ThreadsPerCore&#34;</span><span class="p">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;CapacityReservationSpecification&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;CapacityReservationPreference&#34;</span><span class="p">:</span> <span class="s2">&#34;open&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;HibernationOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Configured&#34;</span><span class="p">:</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;MetadataOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;State&#34;</span><span class="p">:</span> <span class="s2">&#34;applied&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;HttpTokens&#34;</span><span class="p">:</span> <span class="s2">&#34;optional&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;HttpPutResponseHopLimit&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;HttpEndpoint&#34;</span><span class="p">:</span> <span class="s2">&#34;enabled&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;HttpProtocolIpv6&#34;</span><span class="p">:</span> <span class="s2">&#34;disabled&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;InstanceMetadataTags&#34;</span><span class="p">:</span> <span class="s2">&#34;disabled&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;EnclaveOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;Enabled&#34;</span><span class="p">:</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">                    <span class="p">},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PlatformDetails&#34;</span><span class="p">:</span> <span class="s2">&#34;Linux/UNIX&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;UsageOperation&#34;</span><span class="p">:</span> <span class="s2">&#34;RunInstances&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;UsageOperationUpdateTime&#34;</span><span class="p">:</span> <span class="s2">&#34;2022-01-01T00:00:00.000Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PrivateDnsNameOptions&#34;</span><span class="p">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;MaintenanceOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;AutoRecovery&#34;</span><span class="p">:</span> <span class="s2">&#34;default&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;OwnerId&#34;</span><span class="p">:</span> <span class="s2">&#34;112233445566&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;RequesterId&#34;</span><span class="p">:</span> <span class="s2">&#34;112233445566&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;ReservationId&#34;</span><span class="p">:</span> <span class="s2">&#34;r-012345abcdef&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="finding-other-resources-by-ip-address">Finding Other Resources by IP Address</h2>
<h3 id="in-the-aws-console">In the AWS Console</h3>
<p>To identify other AWS resources (such as Lambdas) based on IP address, you can search for the corresponding ENI.
In the AWS Console, browse to the EC2 console and click on Network Interfaces on the left hand side. Then search by &ldquo;Primary private IPv4 address&rdquo; (or &ldquo;Public IPv4 address&rdquo; if you want to search by a public IP address).</p>
<img src="/finding-aws-resources-by-ip-address/eni-search.webp" alt="Results after searching for Elastic Network Interface using a public IP address" width="720" height="195" style="max-width: 100%; height: auto; aspect-ratio: 1312 / 356;" loading="lazy" decoding="async">
<p>You can then poke around through the ENI details to figure out what resource is associated with the IP address.</p>
<h3 id="using-the-aws-cli-1">Using the AWS CLI</h3>
<p>This can also be done using the 

<a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html" target="_blank" rel="noopener">AWS CLI</a> with the following command, replacing <code>--region</code> and <code>Values</code> as needed:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">aws ec2 describe-network-interfaces --region<span class="o">=</span>us-west-1 --filters <span class="nv">Name</span><span class="o">=</span>addresses.private-ip-address,Values<span class="o">=</span>10.0.0.1
</span></span></code></pre></td></tr></table>
</blockquote><p>Here&rsquo;s what the output looks like</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;NetworkInterfaces&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Attachment&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;AttachmentId&#34;</span><span class="p">:</span> <span class="s2">&#34;ela-attach-12345abcde&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;DeleteOnTermination&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;DeviceIndex&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;InstanceOwnerId&#34;</span><span class="p">:</span> <span class="s2">&#34;amazon-aws&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;Status&#34;</span><span class="p">:</span> <span class="s2">&#34;attached&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;AvailabilityZone&#34;</span><span class="p">:</span> <span class="s2">&#34;us-west-1a&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Description&#34;</span><span class="p">:</span> <span class="s2">&#34;Test AWS Lambda&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Groups&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;GroupName&#34;</span><span class="p">:</span> <span class="s2">&#34;test-group&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;GroupId&#34;</span><span class="p">:</span> <span class="s2">&#34;sg-01234abcde&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;InterfaceType&#34;</span><span class="p">:</span> <span class="s2">&#34;lambda&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Ipv6Addresses&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;MacAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;00:aa:11:bb:22:cc&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;NetworkInterfaceId&#34;</span><span class="p">:</span> <span class="s2">&#34;eni-012345abcdef&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;OwnerId&#34;</span><span class="p">:</span> <span class="s2">&#34;112233445566&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;PrivateDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;ip-10-0-0-1.ec2.internal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;PrivateIpAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;10.0.0.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;PrivateIpAddresses&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Primary&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PrivateDnsName&#34;</span><span class="p">:</span> <span class="s2">&#34;ip-10-0-0-1.ec2.internal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;PrivateIpAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;10.0.0.1&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;RequesterId&#34;</span><span class="p">:</span> <span class="s2">&#34;AAABBBCCCDDDEEEFFF:112233445566&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;RequesterManaged&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;SourceDestCheck&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Status&#34;</span><span class="p">:</span> <span class="s2">&#34;in-use&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;SubnetId&#34;</span><span class="p">:</span> <span class="s2">&#34;subnet-1234abcd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;TagSet&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Key&#34;</span><span class="p">:</span> <span class="s2">&#34;App&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;Value&#34;</span><span class="p">:</span> <span class="s2">&#34;Lambda Test&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;VpcId&#34;</span><span class="p">:</span> <span class="s2">&#34;vpc-1234abcd&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Check the value of <code>InterfaceType</code> for clues as to what resource is using the ENI. In this case, it&rsquo;s a Lambda function.</p>
<h2 id="references">References</h2>
<ul>
<li>

<a href="https://serverfault.com/questions/710931/is-it-possible-to-get-aws-ec2-instance-id-based-on-its-ip-address" target="_blank" rel="noopener">https://serverfault.com/questions/710931/is-it-possible-to-get-aws-ec2-instance-id-based-on-its-ip-address</a></li>
<li>

<a href="https://repost.aws/knowledge-center/vpc-find-owner-unknown-ip-addresses" target="_blank" rel="noopener">https://repost.aws/knowledge-center/vpc-find-owner-unknown-ip-addresses</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Replacing AWS ACM SSL Certificates With No Downtime</title>
      <link>https://nelson.cloud/replacing-aws-acm-ssl-certificates-with-no-downtime/?ref=rss</link>
      <pubDate>Sun, 12 Jun 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/replacing-aws-acm-ssl-certificates-with-no-downtime/?ref=rss</guid>
      <description>Updating Amazon Certificate Manager SSL certificates with no downtime</description><content:encoded><![CDATA[<p>I recently learned that it is possible to replace an existing ACM SSL certificate on any AWS resource with no downtime.
The key is that the old certificate must exist while making SSL certificate updates to a resource. AWS does not allow users to delete a certificate that has resources associated. That means there is no risk of an in-use SSL certificate from being missing.</p>
<p>Here&rsquo;s what a resource associated to an ACM SSL certificate looks like. In this case it&rsquo;s a CloudFront Distribution:</p>
<img src="/replacing-aws-acm-ssl-certificates-with-no-downtime/acm-cert-associated-resource.webp" alt="ACM SSL certificate associated to a CloudFront Distribution" width="720" height="298" style="max-width: 100%; height: auto; aspect-ratio: 864 / 358;" loading="lazy" decoding="async">
<p>If I wanted to replace the SSL certificate on the CloudFront Distribution above, I would create a new certificate and associate it with the Distribution.
The updating process is then handled automatically by AWS. Since the old certificate still exists, there would be no downtime during the update. After updates are finished, I would be free to delete the old certificate.</p>
<p>This applies to any AWS resource that can be associated with an ACM SSL certificate.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Invoking Amazon API Gateway with an API Key</title>
      <link>https://nelson.cloud/invoking-amazon-api-gateway-with-an-api-key/?ref=rss</link>
      <pubDate>Mon, 06 Jun 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/invoking-amazon-api-gateway-with-an-api-key/?ref=rss</guid>
      <description>How to pass an API key via the x-api-key header when invoking AWS API Gateway with curl and Python.</description><content:encoded><![CDATA[<p>To invoke an Amazon API Gateway with an API Key we need to pass in the API key in a <code>x-api-key</code> header.
For the following examples, assume the invoke URL is <code>https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create</code> and the API key is <code>abc123</code>.</p>
<h2 id="invoking-with-curl">Invoking with curl</h2>
<p>To invoke this API with <code>curl</code> it would look like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># GET request</span>
</span></span><span class="line"><span class="cl">curl --header <span class="s2">&#34;x-api-key: abc123&#34;</span> https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create
</span></span></code></pre></td></tr></table>
</blockquote><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># POST request with data</span>
</span></span><span class="line"><span class="cl">curl -d <span class="s2">&#34;key1=value1&amp;key2=value2&#34;</span> --header <span class="s2">&#34;x-api-key: abc123&#34;</span> -X POST https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="invoking-with-httpie">Invoking with HTTPie</h2>
<p>To invoke the API with 

<a href="https://httpie.io/" target="_blank" rel="noopener">HTTPie</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># GET request</span>
</span></span><span class="line"><span class="cl">http https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create x-api-key:abc123
</span></span></code></pre></td></tr></table>
</blockquote><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># POST request with data</span>
</span></span><span class="line"><span class="cl">http post https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create <span class="nv">key1</span><span class="o">=</span>value1 x-api-key:abc123
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="invoking-with-python">Invoking with Python</h2>
<p><code>GET</code> request:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># API key specified as a header</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Key hardcoded for demonstrational purposes. Do not push/commit plaintext keys!</span>
</span></span><span class="line"><span class="cl"><span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;x-api-key&#34;</span><span class="p">:</span> <span class="s2">&#34;abc123&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># GET request with custom header</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote><p><code>POST</code> request with data:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://12abcde45.execute-api.us-west-1.amazonaws.com/prod/create&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># API key specified as a header</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Key hardcoded for demonstrational purposes. Do not push/commit plaintext keys!</span>
</span></span><span class="line"><span class="cl"><span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;x-api-key&#34;</span><span class="p">:</span> <span class="s2">&#34;abc123&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Data to be sent</span>
</span></span><span class="line"><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;key1&#34;</span><span class="p">:</span> <span class="s2">&#34;value1&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># POST request with custom header and data</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">data</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Getting EC2 Instance Metadata Using IMDSv1</title>
      <link>https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv1/?ref=rss</link>
      <pubDate>Tue, 08 Feb 2022 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv1/?ref=rss</guid>
      <description>How to get EC2 Instance metadata using IMDSv1</description><content:encoded><![CDATA[<p>Sometimes I need to get information about an EC2 instance within a script but the 

<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html" target="_blank" rel="noopener">AWS documentation</a> doesn&rsquo;t provide many useful examples.
Instead of poking around through all the available metadata endpoints, I made this list of curl commands to retrieve commonly used EC2 information.
Run any of these commands within an EC2 instance. These commands have been tested on an Amazon Linux 2 instance.</p>
<p>These are not all the possible values you can retrieve from the metadata service, these are only the ones I found most useful.</p>
<blockquote><p><strong>Warning:</strong></p>This post covers Instance Metadata Service Version 1 (IMDSv1). For security purposes, it is recommended that you use IMDSv2. I created a separate post covering IMDSv2 here: 

<a href="https://nelson.cloud/getting-ec2-instance-metadata-using-imdsv2/" target="_blank" rel="noopener">Getting EC2 Instance Metadata Using IMDSv2</a></blockquote>

<p>If you want a bash script that you can copy and paste, scroll down to the bottom of this article.</p>
<h2 id="metadata-retrieval-with-curl">Metadata Retrieval with curl</h2>
<p>View all available categories of metadata:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ami-id
</span></span><span class="line"><span class="cl">ami-launch-index
</span></span><span class="line"><span class="cl">ami-manifest-path
</span></span><span class="line"><span class="cl">block-device-mapping/
</span></span><span class="line"><span class="cl">events/
</span></span><span class="line"><span class="cl">hibernation/
</span></span><span class="line"><span class="cl">hostname
</span></span><span class="line"><span class="cl">iam/
</span></span><span class="line"><span class="cl">identity-credentials/
</span></span><span class="line"><span class="cl">instance-action
</span></span><span class="line"><span class="cl">instance-id
</span></span><span class="line"><span class="cl">instance-life-cycle
</span></span><span class="line"><span class="cl">instance-type
</span></span><span class="line"><span class="cl">local-hostname
</span></span><span class="line"><span class="cl">local-ipv4
</span></span><span class="line"><span class="cl">mac
</span></span><span class="line"><span class="cl">metrics/
</span></span><span class="line"><span class="cl">network/
</span></span><span class="line"><span class="cl">placement/
</span></span><span class="line"><span class="cl">profile
</span></span><span class="line"><span class="cl">public-keys/
</span></span><span class="line"><span class="cl">reservation-id
</span></span><span class="line"><span class="cl">security-groups
</span></span><span class="line"><span class="cl">services/
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance AMI ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/ami-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ami-0d43f810ac49e9511
</span></span></code></pre></td></tr></table>
</blockquote><p>Get hostname:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/hostname
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ip-10-128-128-128.us-west-1.compute.internal
</span></span></code></pre></td></tr></table>
</blockquote><p>Get AWS account ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/identity-credentials/ec2/info <span class="p">|</span> grep <span class="s2">&#34;AccountId&#34;</span> <span class="p">|</span> awk -F<span class="se">\&#34;</span> <span class="s1">&#39;{print $4}&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="m">123456789012</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/instance-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">i-01234567890f1234b
</span></span></code></pre></td></tr></table>
</blockquote><p>Get instance type:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/instance-type
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">t2.medium
</span></span></code></pre></td></tr></table>
</blockquote><p>Get IPv4 address:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/local-ipv4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">10.128.128.128
</span></span></code></pre></td></tr></table>
</blockquote><p>Get MAC address:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/mac
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">05:5f:bd:1a:4c:77
</span></span></code></pre></td></tr></table>
</blockquote><p>Get availability zone:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">us-west-1a
</span></span></code></pre></td></tr></table>
</blockquote><p>Get availability zone ID:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone-id
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">usw1-az1
</span></span></code></pre></td></tr></table>
</blockquote><p>Get security groups associated with the instance:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ curl -s http://169.254.169.254/latest/meta-data/security-groups
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">my-security-group-1
</span></span><span class="line"><span class="cl">my-security-group-2
</span></span><span class="line"><span class="cl">my-security-group-3
</span></span></code></pre></td></tr></table>
</blockquote><h2 id="bash-script">Bash Script</h2>
<p>Copy and paste this bash snippet and use values as needed:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">AMI_ID</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/ami-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">HOSTNAME</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/hostname<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">ACCOUNT_ID</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/identity-credentials/ec2/info <span class="p">|</span> grep <span class="s2">&#34;AccountId&#34;</span> <span class="p">|</span> awk -F<span class="se">\&#34;</span> <span class="s1">&#39;{print $4}&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">INSTANCE_ID</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/instance-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">INSTANCE_TYPE</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/instance-type<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">LOCAL_IPV4</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/local-ipv4<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">MAC_ADDRESS</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/mac<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">AVAILABILITY_ZONE</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">AVAILABILITY_ZONE_ID</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone-id<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">SECURITY_GROUPS</span><span class="o">=</span><span class="k">$(</span>curl -s http://169.254.169.254/latest/meta-data/security-groups<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AMI_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$HOSTNAME</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$ACCOUNT_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$INSTANCE_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$INSTANCE_TYPE</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$LOCAL_IPV4</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$MAC_ADDRESS</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AVAILABILITY_ZONE</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$AVAILABILITY_ZONE_ID</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$SECURITY_GROUPS</span>
</span></span></code></pre></td></tr></table>
</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Resolving AWS CloudFront Access Denied Errors</title>
      <link>https://nelson.cloud/resolving-aws-cloudfront-access-denied-errors/?ref=rss</link>
      <pubDate>Sun, 25 Jul 2021 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/resolving-aws-cloudfront-access-denied-errors/?ref=rss</guid>
      <description>Resolving Access Denied Errors in a CloudFront Distribution that uses a Private S3 Bucket, Origin Access Identity, and Contains Multiple index.html Templates.</description><content:encoded><![CDATA[<blockquote><p><strong>Info:</strong></p>Note that Origin Access Identity has since been deprecated by AWS. The replacement is 

<a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html" target="_blank" rel="noopener">Origin Access Control</a>. I have no idea if that fixes the issue I talk about below, but it&rsquo;s something to be aware of.</blockquote>

<p>If you&rsquo;re seeing &ldquo;Access Denied&rdquo; errors on CloudFront and 

<a href="https://repost.aws/knowledge-center/s3-website-cloudfront-error-403" target="_blank" rel="noopener">the official troubleshooting docs</a> aren&rsquo;t helping, you might be running into the following issue.</p>
<p>I discovered that &ldquo;Access Denied&rdquo; errors may show up when a CloudFront Distribution is set up under the following conditions:</p>
<ul>
<li>A private S3 Bucket is being used along with a S3 Origin in the CloudFront Distribution</li>
<li>An Origin Access Identity is being used. (This is necessary if the bucket is private. When a user hits the distribution, the Origin Access Identity retrieves files from the bucket and forwards them to the distribution and to the end user. The end user never accesses the bucket directly.)</li>
<li>Multiple index.html templates exist in the bucket</li>
</ul>
<p>An Origin Access Identity is useful because this allows S3 contents to be accessible only through CloudFront. However, it doesn&rsquo;t work well when there are multiple <code>index.html</code> templates in the bucket and we end up seeing &ldquo;Access Denied&rdquo; errors when accessing any sub-pages. This is unfortunate for those like me that use Hugo to generate static sites. Hugo creates several index.html templates when building a site.</p>
<p>The solution is to either:</p>
<ul>
<li>Use a public bucket.</li>
<li>Enable static website hosting on the bucket (which means the Distribution will have a Custom Origin).</li>
<li>Avoid using an Origin Access Identity.</li>
<li>Acknowledge that users will be able to access S3 contents directly without HTTPS.</li>
</ul>
<p>Or</p>
<ul>
<li>Use a private bucket.</li>
<li>Don&rsquo;t enable static website hosting on the bucket (the Distribution will have a S3 Origin).</li>
<li>Use an Origin Access Identity so that users cannot access S3 contents directly and can only view contents via CloudFront.</li>
<li>Avoid having multiple index.html templates in the bucket, excluding the root index.html template, which could be a hurdle.</li>
</ul>
<p>I was surprised that this isn&rsquo;t documented in Amazon&rsquo;s own troubleshooting guide. I only learned about this issue through a 

<a href="https://forums.aws.amazon.com/thread.jspa?threadID=85849" target="_blank" rel="noopener">comment on a forum</a> (which has now been archived) that reads as follows:</p>
<blockquote><p><strong>Quote:</strong></p><em>&quot;&hellip;CloudFront provides default root object support as well, but not for any subdirectories. You can solve this by using a custom origin instead. When CloudFront uses the S3 static website URL as the origin, you get the desired functionality.&quot;</em></blockquote>

<p>This seems like a limitation of CloudFront. While this issue won&rsquo;t affect me too much, there are others who would not want their buckets to be publicly accessible. For security purposes, it&rsquo;s best if users are only able to access a site through the distribution and not directly from the bucket, especially since S3 static website hosting does not support HTTPS. Hopefully AWS fixes this flaw.</p>
]]></content:encoded>
    </item>
    <item>
      <title>AWS IAM: Allowing a Role to Assume Another Role</title>
      <link>https://nelson.cloud/aws-iam-allowing-a-role-to-assume-another-role/?ref=rss</link>
      <pubDate>Sat, 19 Jun 2021 00:00:00 +0000</pubDate>
      <guid>https://nelson.cloud/aws-iam-allowing-a-role-to-assume-another-role/?ref=rss</guid>
      <description>How to allow an IAM Role to assume another Role.</description><content:encoded><![CDATA[<p>To allow an IAM Role to assume another Role, we need to modify the <strong>trust relationship</strong> of the role that is to be assumed. This process varies depending if the roles exist within the same account or if they&rsquo;re in separate accounts.</p>
<h2 id="roles-in-the-same-account">Roles in the Same Account</h2>
<p>Let&rsquo;s say we have two roles, <code>Role_A</code> and <code>Role_B</code>. If we want to allow <code>Role_A</code> to assume <code>Role_B</code>, we need to modify the <strong>trust relationship</strong> of <code>Role_B</code> with the following:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Principal&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;AWS&#34;</span><span class="p">:</span> <span class="s2">&#34;arn:aws:iam::111111111111:role/Role_A&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="s2">&#34;sts:AssumeRole&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>This is all that&rsquo;s needed to allow a role to assume another role within the same account.</p>
<blockquote><p><strong>Note:</strong></p>Be mindful of the <code>Principal</code> element where we specify the role that we want to give permissions to. In general, the <code>Principal</code> element is used in policies to give users/roles/services access to other AWS resources. However, the <code>Principal</code> element cannot be used in policies attached to Roles. It can only exist in the trust relationships of roles (you&rsquo;ll get errors if you try to use the <code>Principal</code> element in an IAM Role policy). You can read more about this element in the 

<a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html" target="_blank" rel="noopener">AWS docs</a>.</blockquote>

<h2 id="roles-in-different-accounts">Roles in Different Accounts</h2>
<p>Let&rsquo;s say <code>Role_A</code> and <code>Role_B</code> are in different accounts. In this case, the process from above stays the same. <code>Role_B</code> needs to have its trust relationship modified to allow <code>Role_A</code> to assume it. The difference here is that <code>Role_A</code> will need an additional policy with <code>sts:AssumeRole</code> permissions. So the final result is as follows:</p>
<p>The <code>Role_B</code> trust relationship stays the same:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Principal&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;AWS&#34;</span><span class="p">:</span> <span class="s2">&#34;arn:aws:iam::111111111111:role/Role_A&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="s2">&#34;sts:AssumeRole&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>And <code>Role_A</code> needs the following attached as a policy:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="s2">&#34;sts:AssumeRole&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="s2">&#34;arn:aws:iam::222222222222:role/Role_B&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</blockquote><p>Now <code>Role_A</code> will be able to assume <code>Role_B</code> even if they&rsquo;re in different accounts.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
