<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Kratik's WhatNotes!💡]]></title><description><![CDATA[Making Learning Fun!]]></description><link>https://blogs.kratik.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 21 May 2026 01:49:39 GMT</lastBuildDate><atom:link href="https://blogs.kratik.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[ndots : The Hidden DNS Logic Behind resolving Kubernetes Service Names]]></title><description><![CDATA[Ever wondered how Kubernetes resolves a service name like my-service without any domain attached?In this blog, we’ll dive into the DNS concept of ndots, understand how it works, and see how it plays a crucial role in Kubernetes DNS resolution.
Before...]]></description><link>https://blogs.kratik.dev/what-is-ndots-in-dns</link><guid isPermaLink="true">https://blogs.kratik.dev/what-is-ndots-in-dns</guid><category><![CDATA[dns]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Devops]]></category><category><![CDATA[networking]]></category><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Wed, 21 Jan 2026 13:55:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769003592085/9a290e38-0233-4550-ac8b-2fd2b24cada7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ever wondered how Kubernetes resolves a service name like <code>my-service</code> without any domain attached?<br />In this blog, we’ll dive into the DNS concept of <code>ndots</code>, understand how it works, and see how it plays a crucial role in Kubernetes DNS resolution.</p>
<p>Before we dive deeper, let’s take a step back and understand how a basic DNS query and resolution actually work</p>
<p><img src="https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExNGEweGZhOHNlaXZ2cmFrb3RidHAwNHR1M2o2anZsbGFjeGtwdmd6ZiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Kehn32T6zE1vq6rxYX/giphy.gif" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-working-of-a-dns-query">Working of a DNS Query</h1>
<p>so for example - let’s understand what happens when you enter <code>blogs.kratik.dev</code> in your browser</p>
<p>(I hope somewhere someone is actually entering this to read my blogs 🫠 )</p>
<p>Your browser tries to resolve IP of the domain in following order</p>
<ol>
<li><p>check if domain exists in the <code>/etc/hosts</code> file</p>
</li>
<li><p>If the domain is not found there, the OS sends the request to the DNS servers configured on the device (from Wi-Fi, Ethernet, mobile data, or VPN).</p>
</li>
</ol>
<blockquote>
<p>On Linux, these DNS server settings are exposed through <code>/etc/resolv.conf</code>;<br />on other systems, they are managed internally, but the behaviour is the same.</p>
</blockquote>
<h2 id="heading-what-is-etchosts-file">what is /etc/hosts file ?</h2>
<blockquote>
<p><code>/etc/hosts</code> is a <strong>local, static hostname-to-IP mapping file</strong> used by the operating system <strong>before DNS is consulted</strong>. It allows you to manually define how hostnames resolve <strong>without querying any DNS server</strong>.</p>
</blockquote>
<p>in my machine, it looks something like this :</p>
<pre><code class="lang-apache"><span class="hljs-comment">##</span>
<span class="hljs-comment"># Host Database</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># localhost is used to configure the loopback interface</span>
<span class="hljs-comment"># when the system is booting.  Do not change this entry.</span>
<span class="hljs-comment">##</span>
<span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>    localhost
<span class="hljs-attribute">255</span>.<span class="hljs-number">255</span>.<span class="hljs-number">255</span>.<span class="hljs-number">255</span>    broadcasthost
::<span class="hljs-attribute">1</span>             localhost

<span class="hljs-comment"># Added by Docker Desktop</span>
<span class="hljs-comment"># To allow the same kube context to work on the host and the container:</span>
<span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span> kubernetes.docker.internal

<span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span> kratik.servers
<span class="hljs-comment"># End of section</span>
</code></pre>
<p>💡- now you also know how localhost IP resolution works :)</p>
<p>so let’s test this with a domain we have defined <code>kratik.awesome</code> - it does not exist</p>
<p><strong>let’s verify it first</strong></p>
<p>curl</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768986802183/8ec290db-5ba1-4641-84c8-467bce4203d5.png" alt class="image--center mx-auto" /></p>
<p>browser</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768987011921/66e2fe77-ed57-4fe8-9a69-f7b84ff5adc0.png" alt class="image--center mx-auto" /></p>
<p>okay, obviously it does not exist</p>
<p>(<em>I wish It did</em> )</p>
<p><img src="https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExdnNkcTA4OTdlYmd1M3VrYTh2eXM4NXF3MXZoYWF2cnU4cWhkcHZtbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/o5IxfV1v8oU1vZUeZA/giphy.gif" alt class="image--center mx-auto" /></p>
<p>Let’s add this domain in our <code>/etc/hosts</code> file</p>
<pre><code class="lang-apache"><span class="hljs-comment">##</span>
<span class="hljs-comment"># Host Database</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># localhost is used to configure the loopback interface</span>
<span class="hljs-comment"># when the system is booting.  Do not change this entry.</span>
<span class="hljs-comment">##</span>
<span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>    localhost
<span class="hljs-attribute">255</span>.<span class="hljs-number">255</span>.<span class="hljs-number">255</span>.<span class="hljs-number">255</span>    broadcasthost
::<span class="hljs-attribute">1</span>             localhost

<span class="hljs-comment"># Added by Docker Desktop</span>
<span class="hljs-comment"># To allow the same kube context to work on the host and the container:</span>
<span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span> kubernetes.docker.internal

<span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span> kratik.servers

<span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span> kratik.awesome
<span class="hljs-comment"># End of section</span>
</code></pre>
<p>now let’s run a test server in a python quickly 😎</p>
<p>(basically this commands runs a python server which serves files from the specified directory)</p>
<pre><code class="lang-bash">mkdir -p /tmp/sample

cat &lt;&lt;EOF &gt;/tmp/sample/index.html
&lt;h1&gt;Hello from DNS Blog!&lt;/h1&gt;
EOF

python3 -m http.server 80 --directory /tmp/sample
</code></pre>
<p>Ok, let’s check browser again</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768989896262/6c23c12c-be4f-4a6b-9fa0-1fe7167f843a.png" alt class="image--center mx-auto" /></p>
<p>now you can see the above webpage, because :</p>
<ol>
<li><p>browser requested the IP of domain <code>kratik.awesome</code></p>
</li>
<li><p><code>/etc/hosts</code> had an entry for above domain so replied with the IP instantly which was <code>127.0.0.1</code></p>
</li>
<li><p>we already running a Python HTTP server on port 80, so this server responded with our created HTML file.</p>
</li>
</ol>
<p>I hope that clear the basics for <code>/etc/hosts</code> file, let’s move next to <code>resolv.conf</code> file</p>
<h2 id="heading-what-is-etcresolvconf-file">what is /etc/resolv.conf file</h2>
<blockquote>
<p><code>/etc/resolv.conf</code> is a <strong>system configuration file</strong> that tells the operating system <strong>which DNS servers to use</strong> when resolving domain names.</p>
</blockquote>
<p>it look something like this on a linux machine (this is an AWS EC2 machine btw)</p>
<pre><code class="lang-nginx"><span class="hljs-comment"># This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8).</span>
<span class="hljs-comment"># Do not edit.</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># This file might be symlinked as /etc/resolv.conf. If you're looking at</span>
<span class="hljs-comment"># /etc/resolv.conf and seeing this text, you have followed the symlink.</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># This is a dynamic resolv.conf file for connecting local clients directly to</span>
<span class="hljs-comment"># all known uplink DNS servers. This file lists all configured search domains.</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># Third party programs should typically not access this file directly, but only</span>
<span class="hljs-comment"># through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a</span>
<span class="hljs-comment"># different way, replace this symlink by a static file or a different symlink.</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># See man:systemd-resolved.service(8) for details about the supported modes of</span>
<span class="hljs-comment"># operation for /etc/resolv.conf.</span>

<span class="hljs-attribute">nameserver</span> <span class="hljs-number">172.22.0.2</span>
search ap-south-<span class="hljs-number">1</span>.compute.internal
</code></pre>
<p>basic explanation of the fields in resolver files are:</p>
<ol>
<li><p><code>nameserver</code> - <strong>server to query for DNS lookups</strong>, you can define more servers by adding one more nameserver in same format in a new line.</p>
</li>
<li><p><code>search</code> - this option tells the system <strong>which domain names to automatically append</strong> when you try to resolve a short (unqualified) hostname.</p>
</li>
<li><p><code>option</code> - (it’s not present in the above file) - this field is used to <strong>configure the behaviour of the DNS lookups</strong>, for example - how long it waits, how many times it retries, and when it treats a name as fully qualified - we will understand it better further.</p>
<p> available options are <strong>ndots</strong> &amp; <strong>timeout</strong> &amp; <strong>attempts</strong></p>
</li>
</ol>
<p>now let’s move deeper and check a resolver file in Kubernetes environment</p>
<h1 id="heading-etcresolvconf-file-in-kubernetes-pods">/etc/resolv.conf file in Kubernetes Pods</h1>
<p>If you see the same file in your pods in a Kubernetes cluster, it will look something like this</p>
<pre><code class="lang-apache"><span class="hljs-attribute">search</span> kube-system.svc.cluster.local svc.cluster.local cluster.local
<span class="hljs-attribute">nameserver</span> <span class="hljs-number">10.96.0.10</span>
<span class="hljs-attribute"><span class="hljs-nomarkup">options</span></span> ndots:<span class="hljs-number">5</span>
</code></pre>
<p>This is where DNS resolution gets interesting: <strong>search</strong> domains and <code>ndots</code></p>
<p><img src="https://i.giphy.com/MP1kygLQzjCve.webp" alt class="image--center mx-auto" /></p>
<p>Let’s understand it one by one</p>
<h2 id="heading-search">search</h2>
<pre><code class="lang-nginx"><span class="hljs-attribute">search</span> kube-system.svc.cluster.local svc.cluster.local cluster.local
</code></pre>
<p><strong>What it does :</strong> If a hostname is not fully qualified, the resolver tries appending each of these domains in order until one resolves.</p>
<h2 id="heading-ndots">ndots</h2>
<pre><code class="lang-nginx"><span class="hljs-attribute">options</span> ndots:<span class="hljs-number">5</span>
</code></pre>
<p>so this actually counts the dots(.) in our DNS query, for example - <strong>api.github.com</strong> has 2 dots and <strong>my-service.my-namespace.svc.cluster.local</strong> has 4 dots</p>
<p>so basically it means, if a domain does not contain dots more than or equal to 5, <strong><em>it will not be considered as a fully qualified domain name</em></strong> and then all the domains listed in `search` will be appended in the DNS lookup one by one.</p>
<p>Let’s see this in action!</p>
<p><em>I will do a DNS query and also show you the logs of the dns server in k8s.</em></p>
<h2 id="heading-setup-of-the-k8s-cluster-for-this-test"><strong>Setup of the k8s cluster for this test</strong></h2>
<p>We have a Kubernetes cluster with <strong>two namespaces</strong>:</p>
<ul>
<li><p><code>my-namespace</code></p>
<ul>
<li>also create a nginx service here named <code>my-service</code></li>
</ul>
</li>
<li><p><code>my-other-namespace</code></p>
</li>
</ul>
<p>Each namespace has its own DNS search domain:</p>
<ul>
<li><p><code>my-namespace.svc.cluster.local</code></p>
</li>
<li><p><code>my-other-namespace.svc.cluster.local</code></p>
</li>
</ul>
<p>Kubernetes configures pods so that DNS lookups first try services in the <strong>same namespace</strong>, and then fall back to cluster-wide service discovery.</p>
<p>I will create <a target="_blank" href="https://github.com/nicolaka/netshoot">netshoot</a> pods to do DNS lookup in each of these namespace.</p>
<h2 id="heading-case-1-lookup-from-the-same-namespace-my-namespace">Case 1: Lookup from the same namespace (<code>my-namespace</code>)</h2>
<h3 id="heading-cat-etcresolvconf-file-in-1st-namespace">cat /etc/resolv.conf file in 1st namespace</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768998514105/1f29ac4c-aa79-4dcf-b964-3107066732db.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-command">Command</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768998241581/fbf8d50c-b5c0-4e13-8546-8fd5c63c48b8.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-coredns-logs">CoreDNS logs</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768998267828/5d8e74eb-98b9-4662-ae33-92a22da30f8f.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-bash"><span class="hljs-string">"A IN my-service.my-namespace.svc.cluster.local."</span> NOERROR
<span class="hljs-string">"AAAA IN my-service.my-namespace.svc.cluster.local."</span> NOERROR
</code></pre>
<h3 id="heading-what-happened">What happened</h3>
<ul>
<li><p>The pod’s resolver has this search list:</p>
<pre><code class="lang-bash">  search my-namespace.svc.cluster.local svc.cluster.local cluster.local
</code></pre>
</li>
<li><p>Since <code>my-service</code> is a short name, the resolver <strong>appends the first search domain</strong>.</p>
</li>
<li><p>The first attempt is:</p>
<pre><code class="lang-bash">  my-service.my-namespace.svc.cluster.local
</code></pre>
</li>
<li><p>This service <strong>exists</strong>, so CoreDNS returns <code>NOERROR</code>.</p>
</li>
<li><p>Resolution <strong>stops immediately</strong>.</p>
</li>
</ul>
<p>✅ Result: <code>my-service</code> resolves successfully inside its own namespace.</p>
<hr />
<h2 id="heading-case-2-lookup-from-another-namespace-my-other-namespace">Case 2: Lookup from another namespace (<code>my-other-namespace</code>)</h2>
<h3 id="heading-cat-etcresolvconf-file-in-2nd-namespace">cat /etc/resolv.conf file in 2nd namespace</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768998473352/8465cc16-557b-49a7-97a3-44d18aa4c19f.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-command-1">Command</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768998302114/cbeeb89a-4a2c-448e-bd40-80b5ba574b19.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-coredns-logs-in-order">CoreDNS logs (in order)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768998313070/6a80cf69-5cee-4282-9b06-f3d3bbb31f95.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-bash"><span class="hljs-string">"A IN my-service.my-other-namespace.svc.cluster.local."</span> NXDOMAIN
<span class="hljs-string">"A IN my-service.svc.cluster.local."</span> NXDOMAIN
<span class="hljs-string">"A IN my-service.cluster.local."</span> NXDOMAIN
<span class="hljs-string">"A IN my-service."</span> NXDOMAIN
</code></pre>
<h3 id="heading-what-happened-1">What happened</h3>
<p>From <code>my-other-namespace</code>, the resolver search list is:</p>
<pre><code class="lang-bash">search my-other-namespace.svc.cluster.local svc.cluster.local cluster.local
</code></pre>
<p>The resolver tries <strong>each option one by one</strong>:</p>
<ol>
<li><p><code>my-service.my-other-namespace.svc.cluster.local</code><br /> <strong>Service does not exist</strong> → <code>NXDOMAIN</code></p>
</li>
<li><p><code>my-service.svc.cluster.local</code><br /> <strong>No service with that name across namespaces</strong> → <code>NXDOMAIN</code></p>
</li>
<li><p><code>my-service.cluster.local</code><br /> <strong>Not a valid service zone</strong> → <code>NXDOMAIN</code></p>
</li>
<li><p><code>my-service</code> (as-is)<br /> <strong>No global DNS record</strong> → <code>NXDOMAIN</code></p>
</li>
</ol>
<p>After all attempts fail, the lookup fails.</p>
<p><strong>Result</strong>: <code>my-service</code> does <strong>not</strong> resolve from another namespace.</p>
<p>additionally, as soon as I add the namespace name in my lookup, it works! even in the other namespace. why? because now one of the search domain led to a valid fully qualified domain name.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768999391512/c9286f5f-fbaf-4f72-ac9d-d1f389050ba5.png" alt class="image--center mx-auto" /></p>
<p>CoreDNS logs</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768999485437/7866973e-efa2-4c10-b9fd-e58ba6278a21.png" alt class="image--center mx-auto" /></p>
<hr />
<p>In this blog, we walked through how DNS resolution works at a basic level and then explored how Kubernetes builds on top of it using search domains and <code>ndots</code>.</p>
<p>By understanding how these pieces fit together, you can better debug DNS issues, avoid unnecessary lookups, and design more predictable service communication inside your cluster.</p>
<p>💡 - Default value of <strong>ndots</strong> in Linux (glibc) is 1 and in k8s environments it’s 5</p>
<hr />
<h2 id="heading-bonus-tip">Bonus Tip</h2>
<h3 id="heading-how-to-skip-adding-search-domains-to-your-dns-query">How to skip adding search domains to your DNS query?</h3>
<p>You can do this by <strong>adding a dot (</strong><code>.</code>) at the end of the hostname.</p>
<p>for example :</p>
<pre><code class="lang-bash">nslookup my-service.
curl my-service.
</code></pre>
<h3 id="heading-what-the-trailing-dot-does">What the trailing dot does</h3>
<blockquote>
<p>A trailing dot tells the resolver:<br /><strong>“This name is already fully qualified — do not append search domains.”</strong></p>
</blockquote>
<h3 id="heading-where-is-the-trailing-dot-useful">Where is the trailing dot useful?</h3>
<p>Adding a trailing dot (<code>.</code>) is useful whenever you want <strong>full control over DNS resolution</strong> and want to avoid resolver-side surprises.</p>
<hr />
<p>Hope you liked this knowledge byte.</p>
<p>Tune in for more blogs. Time to clear the year old drafts.</p>
<p>see you in the next one!</p>
<p><img src="https://i.giphy.com/sDjIG2QtbXKta.webp" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[EKS IAM Access Entries -
End of aws-auth ConfigMap!]]></title><description><![CDATA[Hi People!
Hope you're happy managing your EKS clusters. There is this one thing we all do in our EKS related shenanigans - To provide an IAM entity access to your EKS Cluster. I personally find this annoying as I have to go and manually edit a Confi...]]></description><link>https://blogs.kratik.dev/how-to-use-aws-eks-iam-access-entries</link><guid isPermaLink="true">https://blogs.kratik.dev/how-to-use-aws-eks-iam-access-entries</guid><category><![CDATA[EKS]]></category><category><![CDATA[AWS]]></category><category><![CDATA[IAM]]></category><category><![CDATA[Security]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[k8s]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sat, 10 Aug 2024 04:50:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1722865681481/f6a8ce47-fb00-4d7f-8dbc-9efd20dacb5d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi People!</p>
<p>Hope you're happy managing your EKS clusters. There is this one thing we all do in our EKS related shenanigans - To <strong><em>provide an IAM entity access to your EKS Cluster</em></strong>. I personally find this annoying as I have to go and manually edit a ConfigMap called <code>aws-auth</code></p>
<p><img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExbTE0ZzkxYmNlMmUzeW5vaXdrc3JsOTJuOHNmbW54NWphNDg0ZmRldiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/wPqYQ87iJKVJzyK7QW/giphy.gif" alt="Steve Harvey Judge GIF by ABC Network" class="image--center mx-auto" /></p>
<p>from the <a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/auth-configmap.html">official docs</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723260495809/061675d3-a54c-4c68-ab9d-861f899de738.png" alt class="image--center mx-auto" /></p>
<p><img src="https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExeGFna3pjZGIyOG9lN3dsbjI2MmU2a2F4MXFsZGFyYmtuYjltMGhtdiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/3orif0P7UxBkXQJBuM/giphy.gif" alt="homer simpson episode 10 GIF" class="image--center mx-auto" /></p>
<p>One more thing is that whoever IAM entity <em>(User or Role)</em> creates the EKS cluster, automatically becomes the owner of that EKS Cluster. In some cases, we don't really want that, because it may become hard to track sometimes who created the cluster as this is not visible in any configuration in AWS.</p>
<p>In Dec 2023, <a target="_blank" href="https://aws.amazon.com/about-aws/whats-new/2023/12/amazon-eks-controls-iam-cluster-access-management/">AWS announced</a>, new feature in Elastic Kubernetes Service (EKS), which helps cluster administrators to simplify mapping and configuration of <strong>AWS Identity and Access Management (IAM)</strong> users and roles with <em>Kubernetes clusters.</em></p>
<p>But let's understand this deprecated approach first</p>
<h1 id="heading-what-is-aws-auth-configmap">What is aws-auth ConfigMap</h1>
<p>This ConfigMap resides in the <code>kube-system</code> namespace and controls the access of IAM principals who are allowed to access the cluster.</p>
<p>Internally it uses <a target="_blank" href="https://github.com/kubernetes-sigs/aws-iam-authenticator#readme">AWS IAM Authenticator</a> in Control plane which reads it's configuration from this aws-auth ConfigMap.</p>
<p>This ConfigMap looks something like below :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722766299781/99258df7-efe3-4c70-b2e9-0ec9a21782d6.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-limitations-of-aws-auth-configmap">Limitations of aws-auth ConfigMap</h1>
<p>This feature comes with a burden to manage complex ConfigMap for every IAM Principal. You may end up messing up this ConfigMap and hence, losing access to your cluster 🤷‍♂️</p>
<p>Also, It does not come with IAM audit logging.</p>
<h1 id="heading-what-is-eks-iam-access-entry">What is EKS IAM Access Entry</h1>
<p>This is a new set of APIs, which added another yet <em>native</em> way to setup authentication between your Kubernetes Cluster and IAM entities.</p>
<p>You can setup this during or after cluster creation. Also, At the time of writing this article, we have option to use either <code>aws-auth</code> ConfigMap or <strong>IAM Access Entries</strong> or both.</p>
<h1 id="heading-create-iam-access-entry-in-eks">Create IAM Access Entry in EKS</h1>
<p>You can either configure this at the time of cluster creation or afterwards for an existing EKS cluster as well.</p>
<p>Let's take example when creating a fresh cluster :</p>
<ol>
<li><p>I have created a fresh EKS Cluster, It is important to note that, In the <strong>Cluster access</strong> section you have to keep config like below:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722913981760/75540996-251a-4008-bd81-e03120d0759d.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<p>^ Here you can See I have selected <strong>Disallow cluster administrator access</strong> and In Cluster authentication mode, I have selected <strong>EKS API</strong>.</p>
<p>It ensures that whoever (IAM User or Role) creating this cluster does not get the admin/owner access by default. And when you select <strong>EKS API</strong> only in the <strong>cluster auth mode</strong>, you are opting here to utilize IAM Access Entries rather than <strong>aws-auth</strong> ConfigMap.</p>
<ol start="2">
<li>Once Cluster creation is done, try creating your first IAM access entry as show below.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722773200895/090abf80-d2d0-417f-9c5c-13389a694fcc.png" alt class="image--center mx-auto" /></p>
<ol start="3">
<li><p>We see a screen with bunch of inputs. Let's try to understand it :</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722779752985/e0554838-388d-4586-93e4-b04a069aa616.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>IAM Principal -</strong> select the IAM entity for which you want to grant access. (similar to <code>mapUsers</code> and <code>mapRoles</code> from aws-auth ConfigMap) In our case, we have selected an IAM user - <code>test-user</code></p>
</li>
<li><p><strong>Type <em>(optional)</em> -</strong> as we are creating this for our test IAM user, we can keep this as <code>Standard</code> , Other Options are : <code>EC2 Linux</code>, <code>EC2 Windows</code> and <code>FARGATE_LINUX</code><br /> note from the docs - <em>If you create an access entry with type</em><code>EC2 Linux</code><em>or</em><code>EC2 Windows</code><em>, the IAM principal creating the access entry must have the</em><code>iam:PassRole</code><em>permission.</em></p>
</li>
<li><p><strong>Username <em>(optional)</em> -</strong> username to be used while authenticating with K8s APIs.</p>
</li>
<li><p><strong>Groups <em>(optional)</em> -</strong> You can mention any group if you are using one in <strong>RoleBinding</strong> or <strong>ClusterRoleBinding</strong> subjects. I was wondering how to bind a user, and in <a target="_blank" href="https://kubernetes-tutorial.schoolofdevops.com/configuring_authentication_and_authorization/">this documentation</a>, I found out (It does not apply to EKS but just in case) :</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722916061466/482879cd-a53f-4797-9cbf-0861826ffeeb.png" alt class="image--center mx-auto" /></p>
<p> also, FYI</p>
<p> You don't need to create an access entry for an IAM role used by a <strong><em>managed node group</em></strong> or a <code>Fargate</code> profile. Amazon EKS automatically adds entries for these roles to the <code>aws-auth</code> ConfigMap, regardless of your cluster's platform version.</p>
<p> For more info on these inputs, check the table below from AWS Docs.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722915123769/c8f053f4-62f3-47e7-8333-1f7cc26aa021.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
</li>
<li><p>Choose <strong>Access Policy.</strong> This will define your access level in your EKS Cluster.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722779821525/cbb81c67-17ca-4e7d-91f2-e1f27c591dc9.png" alt class="image--center mx-auto" /></p>
<p> Here, you can select some of the pre-defined access policies.</p>
<ol>
<li><p>AmazonEKSAdminPolicy</p>
</li>
<li><p>AmazonEKSClusterAdminPolicy</p>
</li>
<li><p>AmazonEKSAdminViewPolicy</p>
</li>
<li><p>AmazonEKSEditPolicy</p>
</li>
<li><p>AmazonEKSViewPolicy</p>
</li>
<li><p>AmazonEMRJobPolicy</p>
</li>
</ol>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722779842551/dd9bf428-17d6-4a78-9a43-2a0dce50f03a.png" alt class="image--center mx-auto" /></p>
<p>Either you can grant the access using policies at cluster level or you can bind a namespace rather than whole cluster for more fine-grained controls. (as shown below)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722779929479/778ec8c7-690a-4ded-baf7-e7b73a672594.png" alt class="image--center mx-auto" /></p>
<p>Select your policy and choose scope and then you have to click on <code>Add Policy</code> to add all the policies one by one. (shown below)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722780115058/c7ca531c-3732-49fd-9ffc-f0fb08baddfb.png" alt class="image--center mx-auto" /></p>
<ol start="5">
<li>Coming back to our example, For our <code>test-user</code> I am selecting <strong>AmazonEKSAdminPolicy</strong> policy.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722788724088/b5590437-b703-4fbe-8098-317877980aef.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722792356103/850ad912-7ffc-434c-a0aa-f5d60035b84c.png" alt class="image--center mx-auto" /></p>
<ol start="6">
<li><p>To test this, I logged in as <code>test-user</code> in AWS CLI.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722793663188/a534e52e-10e8-46d0-a45a-6ee9b97faf24.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Also I downloaded the <strong>kubeconfig</strong> using following command.</p>
<pre><code class="lang-bash"> aws eks update-kubeconfig --name kratik-testing-iam-access-entries --<span class="hljs-built_in">alias</span> kratik-testing-iam-access-entries
</code></pre>
</li>
<li><p>Before creating the IAM access Entry, I used to see this.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722793186396/c19cb084-bd68-43b0-8ea0-49fd99fbc08e.png" alt class="image--center mx-auto" /></p>
<ol start="9">
<li>After creating the <strong>IAM Access Entry</strong> (as we did in Step #5)</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722793200707/0f06f73f-4732-40a2-8a86-e8745380b2ae.png" alt class="image--center mx-auto" /></p>
<p>woohoo! we were able to delegate IAM permissions to our <code>test-user</code> using IAM Access Entry.</p>
<p><img src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExMmxuamk4YTJueTdmZThycDBuZGNyeGU2b3dzd3BrOXJqdDd5dmRrMSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/tkApIfibjeWt1ufWwj/giphy.gif" alt="The Office gif. Actor Steve Carell as Michael in The Office hangs his mouth open in happy shock and is blown back by surprise. " class="image--center mx-auto" /></p>
<h1 id="heading-advantages">Advantages</h1>
<ol>
<li><p>Visibility in Console about the access about the IAM Principals having the access.</p>
</li>
<li><p>Saves you from messing up the EKS Access by not managing complex entries in AWS ConfigMap.</p>
</li>
</ol>
<p>and anyways, as <code>aws-auth</code> is deprecated you should migrate to EKS IAM Access Entries.</p>
<hr />
<p>Thank you for reading this article 🌻</p>
<p>Let me know if you have any feedback or suggestions.</p>
<p>Peace out!</p>
<h1 id="heading-references">References</h1>
<ol>
<li><p><a target="_blank" href="https://aws.github.io/aws-eks-best-practices/security/docs/iam/">https://aws.github.io/aws-eks-best-practices/security/docs/iam/</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/creating-access-entries.html">https://docs.aws.amazon.com/eks/latest/userguide/creating-access-entries.html</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/access-entries.html">https://docs.aws.amazon.com/eks/latest/userguide/access-entries.html</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/access-policies.html">https://docs.aws.amazon.com/eks/latest/userguide/access-policies.html</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/migrating-access-entries.html">https://docs.aws.amazon.com/eks/latest/userguide/migrating-access-entries.html</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/auth-configmap.html">https://docs.aws.amazon.com/eks/latest/userguide/auth-configmap.html</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[EKS Pod Identity : A better way to delegate IAM access to pods]]></title><description><![CDATA[Hello Beautiful People of Internet! 👋
Hope you guys are doing well tinkering with your clusters :)


The Question
Let me ask you folks a question:
"How do you provide IAM access to your pods in your EKS Cluster?"
Possible Answers

Hardcoding AWS Acc...]]></description><link>https://blogs.kratik.dev/eks-pod-identity-a-better-way-to-delegate-iam-access-to-pods</link><guid isPermaLink="true">https://blogs.kratik.dev/eks-pod-identity-a-better-way-to-delegate-iam-access-to-pods</guid><category><![CDATA[AWS]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[IAM]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Security]]></category><category><![CDATA[SRE]]></category><category><![CDATA[infrastructure]]></category><category><![CDATA[Amazon Web Services]]></category><category><![CDATA[EKS]]></category><category><![CDATA[k8s]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sat, 03 Aug 2024 14:32:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1722694259975/bfb52a8a-d13a-41a4-9109-bc54fb5b499e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello Beautiful People of Internet! 👋</p>
<p>Hope you guys are doing well tinkering with your clusters :)</p>
<p><img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExZzlicmF6d3ozbTNrMHFkZmVhcnd4NmIxMXAwb3E2YTA5bnh2YWFqbSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Anyd8oskpTJuq9OVMP/giphy.gif" alt="Cedric The Entertainer Reaction GIF by CBS" class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-the-question">The Question</h1>
<p>Let me ask you folks a question:</p>
<p><em>"How do you provide IAM access to your pods in your EKS Cluster?"</em></p>
<h3 id="heading-possible-answers">Possible Answers</h3>
<ol>
<li><h4 id="heading-hardcoding-aws-access-and-secret-access-key">Hardcoding AWS Access and Secret Access Key</h4>
</li>
</ol>
<p>for real? is someone really doing this in EKS in 2024? It can be the worst way to do this. Not Recommended at all. Please don't even think about it. You can imagine all the security risks involved.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722692979525/01f72618-a5f0-4aa3-a50a-2a573c9b4f28.webp" alt class="image--center mx-auto" /></p>
<ol start="2">
<li><h4 id="heading-using-open-source-tools-like-kube2iam-or-kiam">Using Open Source Tools Like kube2iam or kiam</h4>
</li>
</ol>
<p>While these tools are better than previous method, but they are not without their drawbacks. This approach intercepts your AWS requests and injects temporary STS credentials <strong><em>using a dedicated agent</em></strong> which generally runs as a <strong><em>deamonset</em></strong>. However, it requires granting your worker nodes access to the roles intended for your pods. This setup potentially grants nodes more permissions than they typically need, which can introduce security concerns. So let's throw this approach as well right out of the window!</p>
<ol start="3">
<li><h4 id="heading-leveraging-irsa-good-option">Leveraging IRSA (Good Option)</h4>
</li>
</ol>
<p>You can use <strong>IAM Roles for Service Accounts(IRSA)</strong> which natively connect IAM roles from AWS to Service Accounts of Kubernetes.</p>
<p>For this to work, you have to follow a certain process.</p>
<p>You have to mention all the details of target service account name, namespace and cluster details in the IAM Role's trust policy. for example :</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
  <span class="hljs-attr">"Statement"</span>: [
    {
      <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
      <span class="hljs-attr">"Principal"</span>: {
        <span class="hljs-attr">"Federated"</span>: <span class="hljs-string">"$PROVIDER_ARN"</span>
      },
      <span class="hljs-attr">"Action"</span>: <span class="hljs-string">"sts:AssumeRoleWithWebIdentity"</span>,
      <span class="hljs-attr">"Condition"</span>: {
        <span class="hljs-attr">"StringEquals"</span>: {
          <span class="hljs-attr">"${ISSUER_HOSTPATH}:sub"</span>: <span class="hljs-string">"system:serviceaccount:default:my-serviceaccount"</span>
        }
      }
    }
  ]
}
</code></pre>
<p>It's definitely one of the best option but I didn't like how we needed to pollute the IAM role trust policy principals for all the namespace and service account in which we want to use that role. For example, if you have 5 service account in different namespaces but they have to use the same IAM role then in the trust policy, <strong><em>you have to repeat above written code 5 times for every principal (namespace + Service Account).</em></strong></p>
<p>Also, you have to annotate your service account to let it know about the role it have to assume. Too many manual steps and misery of adding principals in Trust Policy :')</p>
<p><img src="https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExZTk5cWd4eWhmdHg5c21vazdzMW9mN290cTZ5cDB6YTBpNGVybW85dyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/tmQrpA8zpG4a16SSxm/giphy.gif" alt="Wildlife gif. Sleepy monkey rests on a tree branch and opens its mouth in a huge yawn, dark hand scratching its eyes and head. " class="image--center mx-auto" /></p>
<p>Also, A while back (&gt; 2 years), I already discussed IRSA in one of the blogs, do check that out <a target="_blank" href="https://kratik.hashnode.dev/how-to-attach-iam-roles-to-pods-in-aws-eks-cluster">here</a> or image below for more understanding :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722659579410/e3725d22-893b-4537-a405-0cc993a027d1.png" alt class="image--center mx-auto" /></p>
<hr />
<ol start="4">
<li><h4 id="heading-new-eks-pod-identity">New! - EKS Pod Identity</h4>
<p> Enter <a target="_blank" href="https://aws.amazon.com/about-aws/whats-new/2023/11/amazon-eks-pod-identity/">Nov 2023</a>, AWS released a feature called EKS Pod Identity which made it more easier to delegate IAM access to pods running in your EKS Cluster.</p>
<p> This blog is also about this specific feature. Let's dive into it.</p>
<p> <img src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExa3d5b3kxYno4OW0xaTZuenVtejlqejhvbDdoMHV0bmd3Zm85bWt1NyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/oF5oUYTOhvFnO/200.gif" alt="SpongeBob gif. SpongeBob pumps his arms up and down excitedly, biting his little yellow lip." class="image--center mx-auto" /></p>
</li>
</ol>
<h1 id="heading-what-is-eks-pod-identity">What is EKS Pod Identity</h1>
<p>In the ever-evolving world of cloud computing, one of the most critical challenges cluster administrators face is securely managing access to AWS services from within Kubernetes pods. Traditionally, this has been a daunting task, often involving complex configurations and potential security risks.</p>
<p>However, with AWS's introduction of <strong>EKS Pod Identities</strong>, cluster administrators now have a robust and simplified way to handle IAM access for their pods.</p>
<h1 id="heading-how-to-create-and-use-eks-pod-identity">How to Create and Use EKS Pod Identity</h1>
<ol>
<li><p>Make sure you have installed EKS Pod Identity EKS Agent <a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/pod-id-agent-setup.html">Add-on</a>.</p>
</li>
<li><p>Now, Create your desired IAM Role.</p>
</li>
<li><p>In the trust policy, add the following code :</p>
<pre><code class="lang-json"> {
     <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
     <span class="hljs-attr">"Statement"</span>: [
         {
             <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
             <span class="hljs-attr">"Principal"</span>: {
                 <span class="hljs-attr">"Service"</span>: <span class="hljs-string">"pods.eks.amazonaws.com"</span>
             },
             <span class="hljs-attr">"Action"</span>: [
                 <span class="hljs-string">"sts:TagSession"</span>,
                 <span class="hljs-string">"sts:AssumeRole"</span>
             ]
         }
     ]
 }
</code></pre>
<p> (it will remain same for all EKS Pod Identities and for a role which is being used by any number of EKS Pod Identities, you don't have to change it ever. That's one of the advantages of using this approach.)</p>
</li>
<li><p>In your EKS console, as shown below, Create a new <strong>Pod Identity Association</strong></p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722670876963/b74888ba-d8db-4ab6-968d-bdb489061702.png" alt class="image--center mx-auto" /></p>
<ol start="4">
<li>Enter all the details, <em>Select your newly created role</em> with pod identity, your <em>namespace</em> and then name of <em>ServiceAccount</em>.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722674393241/2b4bb0ae-a344-48c9-84b1-6c70466f4a16.png" alt class="image--center mx-auto" /></p>
<p><strong><em>Observation</em></strong>*- you can enter name of namespace and ServiceAccount which are not yet created. It will be apply automatically once they are created. Shown in below screenshot.*</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722695185001/f7848deb-6232-40f1-a289-90e75523fd30.png" alt class="image--center mx-auto" /></p>
<p><img src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExcHdlcGkyejhuY3Z6dHJnemYwcnBxMWx6Nmh0ejBiYThkczA4MmJyZCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/406ZMCwmTzYfo4E9cS/giphy.gif" alt="strange beck bennett GIF by Saturday Night Live" class="image--center mx-auto" /></p>
<ol start="5">
<li><p>Now, Test above configuration :</p>
<ol>
<li><p>We created EKS Pod Identity Association as shown in above screenshot.</p>
</li>
<li><p>Created a pod, which run <code>aws-cli</code> &amp; checks IAM access using <code>aws sts get-caller-identity</code> command.</p>
</li>
<li><p>Make sure it uses ServiceAccount what we have configured in EKS Pod Identity.</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
 <span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
 <span class="hljs-attr">metadata:</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">aws-cli-pod</span>
   <span class="hljs-attr">namespace:</span> <span class="hljs-string">kube-system</span> <span class="hljs-comment"># as per Pod Identity Association</span>
 <span class="hljs-attr">spec:</span>
   <span class="hljs-attr">serviceAccountName:</span> <span class="hljs-string">metrics-server</span> <span class="hljs-comment"># as per Pod Identity Association</span>
   <span class="hljs-attr">containers:</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">aws-cli-container</span>
       <span class="hljs-attr">image:</span> <span class="hljs-string">amazon/aws-cli:latest</span>
       <span class="hljs-attr">command:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-string">/bin/sh</span>
         <span class="hljs-bullet">-</span> <span class="hljs-string">-c</span>
         <span class="hljs-bullet">-</span> <span class="hljs-string">|
           # Command to check IAM identity
           aws sts get-caller-identity
</span>       <span class="hljs-attr">env:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_REGION</span>
           <span class="hljs-attr">value:</span> <span class="hljs-string">ap-south-1</span>
</code></pre>
</li>
<li><p>Create this pod and check logs.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722675340442/5e44a83a-40fa-4487-bab6-a1be96b95268.png" alt class="image--center mx-auto" /></p>
<p> Nice! We can see our pod have assumed the IAM role which we created earlier and mapped in EKS Pod Identity.</p>
<p> <img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExc2czbnNtN3hrY2pkeDI1dDZrZ3FreGRodnMyczVnOXlzcjh1ZTRjMyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/xT5LMHxhOfscxPfIfm/giphy.gif" alt="The Simpsons gif. Homer Simpson throws his fists into the air jubilantly, saying, &quot;Whoo-hoo!&quot;" class="image--center mx-auto" /></p>
<p> <strong>note -</strong> This was manual creation approach, if you want, you can use your favourite IaC tool like <a target="_blank" href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_pod_identity_association">Terraform</a>, <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-podidentityassociation.html">CloudFormation</a>, etc.</p>
</li>
</ol>
</li>
</ol>
<h4 id="heading-but-the-question-is">But the question is...</h4>
<p><img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExY2FhNTV2bmJ0YmtoMnBic2tkeXRzZGZyeXZleG5lb2pycTZtY3I3byZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/xT9KVgi6Qkc5bPL4dO/giphy.gif" alt="season 20 20x3 GIF by South Park " class="image--center mx-auto" /></p>
<p>from the docs "If a pod uses a <strong><em>service account</em></strong> that has an <strong><em>pod identity association</em></strong>, Amazon <strong><em>EKS sets environment variables</em></strong> in the containers of the pod. The environment variables <strong><em>configure the AWS SDKs</em></strong>, including the <strong><em>AWS CLI</em></strong>, to <strong><em>use the EKS Pod Identity credentials</em></strong>"</p>
<pre><code class="lang-yaml">  <span class="hljs-bullet">-</span> <span class="hljs-attr">env:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_STS_REGIONAL_ENDPOINTS</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">regional</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_DEFAULT_REGION</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">ap-south-1</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_REGION</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">ap-south-1</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_CONTAINER_CREDENTIALS_FULL_URI</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">http://169.254.170.23/v1/credentials</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token</span>
</code></pre>
<p>in simple terms, When you configure EKS Pod Identity for a workload, the pods get injected some useful env vars and <code>eks-pod-identity-token</code> volume by the EKS Pod Identity Agent. And all <a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/pod-id-minimum-sdk.html">supported SDKs and AWS CLI versions</a> already have support of using these variable for AWS authentication automatically.</p>
<p>I hope clears that doubt! (read this for more info : <a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/pod-id-how-it-works.html">https://docs.aws.amazon.com/eks/latest/userguide/pod-id-how-it-works.html</a>)</p>
<p><img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExMGc2cHk1NGljY3N6eDdraDIxZXkxamdiOHc3Z3BobTVkYW9rYzlxbiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/LKTTAzGboJGzC/giphy.gif" alt="The Office I Give Up GIF" class="image--center mx-auto" /></p>
<p>Also, Keep in mind that AWS has a priority order to pick credentials if it's available at multiple places. For example - In JavaScript SDKs, it follows as attached below :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722689870726/4aea01ea-518d-4e67-8edb-06388b989a3b.png" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-advantages">Advantages</h1>
<ol>
<li><p><strong>Least Privilege</strong> - EKS Pod Identities enable scoping IAM permissions to a specific service account, ensuring that only pods using that account can access the associated permissions.</p>
</li>
<li><p><strong>Credential Isolation</strong> - Pods can only access credentials for the IAM role linked to their service account, ensuring containers cannot access credentials from other pods.</p>
</li>
<li><p><strong>Audit</strong> - AWS CloudTrail provides access and event logging which makes it easier to audit operations related operations in the cluster.</p>
</li>
<li><p><strong>No Need of creating OIDC Provider</strong> unlike in IRSA setup.</p>
</li>
<li><p><strong>Non-polluting IAM Role Trust Policy -</strong> single IAM principal instead of the separate principals for each cluster/namespace/ServiceAccount which we had to do in IRSA setup.</p>
</li>
<li><p><strong>Scalability -</strong> The EKS Auth service in EKS Pod Identity assumes temporary credentials once per node, rather than for each pod's AWS SDK. The Amazon EKS Pod Identity Agent on each node then distributes these credentials to the SDKs, reducing the load by preventing duplication across pods.</p>
<p> <img src="https://media.tenor.com/VhCWjJwTXNAAAAAi/happy-happy-happy.gif" alt="Happy Happy Happy Sticker" class="image--center mx-auto" /></p>
</li>
</ol>
<hr />
<p>You know what When I was working with some JavaScript microservice at <a target="_blank" href="https://tech.kutumb.app/">Kutumb</a> which was using <strong><em>JavaScript SDK v2</em></strong>, We found that <strong><em>this new feature is not yet supported by latest v2 SDK!</em></strong></p>
<p>We were doing a POC <strong><em>migrating our services from kube2iam to EKS Pod Identity</em></strong>, and with <strong><em>JavaScript v2 AWS SDK</em></strong>, it was failing to get credentials.</p>
<p>So felt like digging into it, with help of awesome genius colleagues I was able to find the root cause and fixed that :)</p>
<p><img src="https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExdWNrZm9haXJ6bzY5enp5YTBidjF6OXQyaDFpMnJ2MWh5ODhtNHBvdCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/mC7VjtF9sYofs9DUa5/giphy.gif" alt="Dog Snl GIF by Saturday Night Live" class="image--center mx-auto" /></p>
<p>also, raised the PR of the fix, check here : <a target="_blank" href="https://github.com/aws/aws-sdk-js/pull/4565">https://github.com/aws/aws-sdk-js/pull/4565</a></p>
<hr />
<p>Thank you for reading this piece of information, wanted to share it from past couple of month, finally got the time to wrote and publish it :)</p>
<p>If you have any suggestions and/or feedback, please let me know.</p>
<p>Until we meet next time 👋</p>
<p><img src="https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExN3NhZDRtZ3ZscGZqa2xnaXFweGI4eHJxdng2cDZtam0wbnhoem1oOSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/BPJmthQ3YRwD6QqcVD/giphy.gif" alt="Movie gif. Leonardo DiCaprio as Jay in the Great Gatsby dons a tux and slick hair. He smiles and tips his head forward as he lifts a glass of wine, fireworks erupting in the background." class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-references">References</h1>
<ol>
<li><p><a target="_blank" href="https://maturitymodel.security.aws.dev/en/2.-foundational/dont-store-secrets-in-code/">https://maturitymodel.security.aws.dev/en/2.-foundational/dont-store-secrets-in-code/</a></p>
</li>
<li><p><a target="_blank" href="https://aws.amazon.com/about-aws/whats-new/2023/11/amazon-eks-pod-identity/">https://aws.amazon.com/about-aws/whats-new/2023/11/amazon-eks-pod-identity/</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html">https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html</a></p>
</li>
<li><p><a target="_blank" href="https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/">https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/</a></p>
</li>
<li><p><a target="_blank" href="https://www.reddit.com/r/aws/comments/1853wbh/eks_pod_identity_vs_irsa/">https://www.reddit.com/r/aws/comments/1853wbh/eks_pod_identity_vs_irsa/</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Step by Step Guide to Create a Custom GitHub Action and Publish it to the GitHub Marketplace]]></title><description><![CDATA[Hello beautiful people of the internet ☘️Guess who decided to break the hiatus with some insightful blog. Hope you find value in this piece. Your feedback is appreciated.

Intro to GitHub Actions
I assume you already have some knowledge about GitHub ...]]></description><link>https://blogs.kratik.dev/step-by-step-guide-to-create-a-custom-github-action</link><guid isPermaLink="true">https://blogs.kratik.dev/step-by-step-guide-to-create-a-custom-github-action</guid><category><![CDATA[github-actions]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Wed, 24 Apr 2024 19:13:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713975211873/3f18671d-403a-4a73-b5a0-1125b4dc6cdb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello beautiful people of the internet ☘️<br />Guess who decided to break the hiatus with some insightful blog. Hope you find value in this piece. Your feedback is appreciated.</p>
<hr />
<h1 id="heading-intro-to-github-actions">Intro to GitHub Actions</h1>
<p>I assume you already have some knowledge about GitHub actions, and in case if you don't : This is <strong>an automation tool provided by GitHub</strong>*(by Microsoft) -* as most of us have our code stored up in GitHub repos, many people use it for CI/CD pipelines.</p>
<p>Not just CI/CD, you can do a lot of thing with GitHub Actions, possibillites are limitless and depends on your creativity how you use it.</p>
<p>By Default, GitHub runs your workflow on their own servers aka <em>runners</em>. You can also host your CI/CD workers called <a target="_blank" href="https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners">self-hosted runners</a>.</p>
<h2 id="heading-how-does-it-work">How does it work ?</h2>
<p>Well, it's simple - you have to define a <strong>workflow</strong> file in YAML following the <a target="_blank" href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions">syntax</a> and place this file in <code>.github/workflows/</code> directory. You also have to define a trigger in this workflow and whenever that trigger event occurs, boom! your workflow is started. This is fun right ?</p>
<p><img src="https://i.giphy.com/PxpS6EKcm4bDK23VCI.webp" alt class="image--center mx-auto" /></p>
<p>P.S. - I am using GitHub Actions from past couple of years and yes, and it still surprises me with some bugs (or features maybe?) but lately I have learned how to deal with it.</p>
<hr />
<h2 id="heading-types-of-custom-github-action">Types of Custom GitHub Action</h2>
<p>From the docs <a target="_blank" href="https://docs.github.com/en/actions/creating-actions/about-custom-actions">https://docs.github.com/en/actions/creating-actions/about-custom-actions</a>.</p>
<ol>
<li><p><strong>Docker</strong> - <a target="_blank" href="https://docs.github.com/en/actions/creating-actions/creating-a-docker-container-action">Docs</a> - In this, you can containerize your action so it can run anywhere. We will be using this approach to create our custom action.</p>
</li>
<li><p><strong>Javascript</strong> - <a target="_blank" href="https://docs.github.com/en/actions/creating-actions/creating-a-javascript-action">Docs</a> - GitHub runners already have node installed, so you can write your custom action in javascript to run it directly on the runner during workflow execution. It's fastest among other approaches.</p>
</li>
<li><p><strong>Composite -</strong><a target="_blank" href="https://docs.github.com/en/actions/creating-actions/creating-a-composite-action">Docs</a><strong>-</strong> In this, you combine multiple steps in a GitHub Action, and later you can use them as a composite workflow. I think it's superseded by <a target="_blank" href="https://docs.github.com/en/actions/using-workflows/reusing-workflows#creating-a-reusable-workflow">Reusable Workflows</a> and is a better alternative.</p>
</li>
</ol>
<hr />
<p>Enough with the types, let's dig into creating one.</p>
<h1 id="heading-building-a-custom-github-action-from-scratch">Building a Custom GitHub Action from scratch</h1>
<p><img src="https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExY3RhaHNwaHppdTQyZjVpbXNwcDFiZW5yb2tlYjVwazhzNGhpMXI0ZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/mFGRvxZl9UEr0BsSIP/giphy.gif" alt="Feeling Good Love GIF by The Bachelor" class="image--center mx-auto" /></p>
<p>for the demo we will build a custom action whose job would be -</p>
<p><strong>To detect if a Container image with given tag exists in a container registry (DockerHub or AWS ECR) or not, if exists, output some flag so build/push step can be skipped.</strong></p>
<p>sometimes when we run the same CI/CD pipeline again <em>(for whatever reason)</em>, we would like to skip the build/push stage so we can save some time as that image is already build and pushed, so why do it again ?</p>
<p>Cool, Assuming you understood the problem statement, So now we know what our custom GitHub Action will solve.</p>
<p><strong>Let's Start.</strong></p>
<h3 id="heading-step-0-create-and-init-an-empty-git-repo">Step 0 : Create and Init an empty git repo</h3>
<pre><code class="lang-bash">mkdir container-image-check-custom-action
<span class="hljs-built_in">cd</span> container-image-check-custom-action
git init
</code></pre>
<h3 id="heading-step-1-write-your-script-which-solves-your-problem-statement">Step 1 : Write your script which solves your problem statement</h3>
<p>We don't want to go deep into the development of below script but want to make sure it solves our problem statement.</p>
<p><img src="https://www.discoverforce5.com/wp-content/uploads/2017/07/image.jpeg" alt="Why Everyone Needs to KISS | Force 5" class="image--center mx-auto" /></p>
<p>let's write our script</p>
<p><code>script.sh</code></p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>

CONTAINER_REPO_NAME=<span class="hljs-string">"<span class="hljs-variable">$2</span>"</span>
CONTAINER_IMAGE_TAG=<span class="hljs-string">"<span class="hljs-variable">$3</span>"</span>

<span class="hljs-comment"># in case if it is a Docker image</span>
DOCKER_HUB_USERNAME=<span class="hljs-string">"<span class="hljs-variable">$4</span>"</span>
DOCKER_HUB_PAT=<span class="hljs-string">"<span class="hljs-variable">$5</span>"</span>

<span class="hljs-keyword">function</span> <span class="hljs-function"><span class="hljs-title">check_ecr_image_tag_if_exists</span></span>() {

    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Started Image &amp; Tag checking for repo <span class="hljs-variable">$CONTAINER_REPO_NAME</span> for tag <span class="hljs-variable">$IMAGE_TAG</span>"</span>

    IMAGE_META=<span class="hljs-string">"<span class="hljs-subst">$(aws ecr describe-images --repository-name=$CONTAINER_REPO_NAME --image-ids=imageTag=$CONTAINER_IMAGE_TAG 2&gt;/dev/null)</span>"</span>

    <span class="hljs-keyword">if</span> [[ $? == 0 ]]; <span class="hljs-keyword">then</span>
        IMAGE_TAGS=<span class="hljs-string">"<span class="hljs-subst">$(echo ${IMAGE_META} | jq '.imageDetails[0].imageTags[0]' -r)</span>"</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-variable">$IMAGE_TAGS</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"image_exists=true"</span> &gt;&gt;<span class="hljs-variable">$GITHUB_OUTPUT</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"Image <span class="hljs-variable">$1</span>:<span class="hljs-variable">$2</span> exists on ecr."</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-variable">$IMAGE_META</span>
    <span class="hljs-keyword">else</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$1</span>:<span class="hljs-variable">$2</span> does not exist on ecr."</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"image_exists=false"</span> &gt;&gt;<span class="hljs-variable">$GITHUB_OUTPUT</span>
    <span class="hljs-keyword">fi</span>

}

<span class="hljs-keyword">function</span> <span class="hljs-function"><span class="hljs-title">check_docker_image_tag_if_exists</span></span>() {

    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Started Image &amp; Tag checking for repo <span class="hljs-variable">$CONTAINER_REPO_NAME</span> by user <span class="hljs-variable">$DOCKER_HUB_USERNAME</span> for tag <span class="hljs-variable">$CONTAINER_IMAGE_TAG</span>"</span>

    TOKEN=$(curl -sSLd <span class="hljs-string">"username=<span class="hljs-variable">${DOCKER_HUB_USERNAME}</span>&amp;password=<span class="hljs-variable">${DOCKER_HUB_PAT}</span>"</span> https://hub.docker.com/v2/users/login | jq -r <span class="hljs-string">".token"</span>)
    REPO_RESPONSE=$(curl -sH <span class="hljs-string">"Authorization: JWT <span class="hljs-variable">$TOKEN</span>"</span> <span class="hljs-string">"https://hub.docker.com/v2/repositories/<span class="hljs-variable">${DOCKER_HUB_USERNAME}</span>/<span class="hljs-variable">${CONTAINER_REPO_NAME}</span>/tags/<span class="hljs-variable">${CONTAINER_IMAGE_TAG}</span>/"</span>)

    <span class="hljs-built_in">echo</span>
    <span class="hljs-built_in">echo</span> Response is:
    <span class="hljs-comment"># echo $REPO_RESPONSE | jq .</span>
    <span class="hljs-built_in">echo</span>
    <span class="hljs-built_in">echo</span> Tag status is:
    TAG_STATUS=$(<span class="hljs-built_in">echo</span> <span class="hljs-variable">$REPO_RESPONSE</span> | jq .tag_status | tr -d <span class="hljs-string">'"'</span>)
    <span class="hljs-built_in">echo</span> <span class="hljs-variable">$TAG_STATUS</span>
    <span class="hljs-built_in">echo</span>

    <span class="hljs-keyword">if</span> [[ <span class="hljs-variable">$TAG_STATUS</span> == *<span class="hljs-string">"active"</span>* ]]; <span class="hljs-keyword">then</span>
        <span class="hljs-built_in">echo</span> Docker Image <span class="hljs-variable">$CONTAINER_REPO_NAME</span> exists with Tag <span class="hljs-variable">$CONTAINER_IMAGE_TAG</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"image_exists=true"</span> &gt;&gt;<span class="hljs-variable">$GITHUB_OUTPUT</span>
    <span class="hljs-keyword">else</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"Docker Image Does Not Exist"</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"image_exists=false"</span> &gt;&gt;<span class="hljs-variable">$GITHUB_OUTPUT</span>
    <span class="hljs-keyword">fi</span>
}

<span class="hljs-comment"># -------------------------------------------------------------------------</span>

<span class="hljs-keyword">if</span> [[ <span class="hljs-variable">$1</span> == <span class="hljs-string">"ecr"</span> ]]; <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"got ecr: <span class="hljs-variable">$1</span>"</span>
    check_ecr_image_tag_if_exists
<span class="hljs-keyword">elif</span> [[ <span class="hljs-variable">$1</span> == <span class="hljs-string">"dockerhub"</span> ]]; <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"got dockerhub: <span class="hljs-variable">$1</span>"</span>
    check_docker_image_tag_if_exists
<span class="hljs-keyword">else</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Unsupported Registry: <span class="hljs-variable">$1</span>"</span>
    <span class="hljs-built_in">exit</span> 1
<span class="hljs-keyword">fi</span>
</code></pre>
<h4 id="heading-flow-of-the-script">Flow of the script</h4>
<ol>
<li><p>The script when executed, will determine if a <strong>Docker Hub</strong> or <strong>ECR image</strong> exists or not based on the given repo name and tag.</p>
</li>
<li><p>The first argument is used for setting the target registry type - <code>ecr</code> or <code>dockerhub</code></p>
</li>
<li><p>The Second and third argument is for name of the container repo and the image tag, respectively.</p>
</li>
<li><p>For Docker Hub, you need to provide your <strong>Docker Hub username</strong> and <strong>Docker Hub Personal Access Token</strong>, that's what 4th and 5th args are for.</p>
</li>
<li><p>Based on the run, if image exists or not, it will update the GitHub Actions output variable by updating <code>GITHUB_OUTPUT</code> file. (<a target="_blank" href="https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter">Read - Setting Output variables</a>)</p>
</li>
</ol>
<h4 id="heading-example-ecr">Example - ECR</h4>
<pre><code class="lang-bash">bash container-image-check-custom-action/script.sh ecr python-server v1
</code></pre>
<h4 id="heading-example-docker-hub">Example - Docker Hub</h4>
<pre><code class="lang-bash">bash container-image-check-custom-action/script.sh dockerhub python-server v1 docker_user dockerhub_my_personal_access_token_1234
</code></pre>
<h3 id="heading-step-2-test-locally">Step 2 : Test Locally</h3>
<p>Using above commands, first of all check in your local env that is this script is working as expected or not.</p>
<p><strong>ECR</strong>:<br /><strong>args</strong> <em>= Repo and tag</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713837047325/58d62986-e790-4455-b87a-77f6523911ff.png" alt class="image--center mx-auto" /></p>
<p><strong>DockerHub</strong>:<br /><em>notice all the args</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713837485600/f92badd2-d8e6-4a7e-a882-0972b03a2ccc.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-step-31-build-docker-image-to-test">Step 3.1 : Build Docker Image to Test</h3>
<p>As we are using <code>docker</code> type of our custom action, we should build the docker image and test is locally before pushing.</p>
<p>As this is going to run as a container, we should verify it locally before pushing, so we can avoid excuses like :</p>
<p><img src="https://img.devrant.com/devrant/rant/r_2341310_va2vS.jpg" alt="rant - Always works on my machine - devRant" /></p>
<p><code>Dockerfile</code></p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> amazon/aws-cli:<span class="hljs-number">2.15</span>.<span class="hljs-number">40</span>
<span class="hljs-keyword">RUN</span><span class="bash"> yum update &amp;&amp; yum install -y jq</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /scripts</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> chmod +x /scripts/docker-entrypoint.sh</span>
<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [<span class="hljs-string">"/scripts/docker-entrypoint.sh"</span>]</span>
</code></pre>
<p>now, build a docker image out of this</p>
<pre><code class="lang-dockerfile">docker build . -t container-image-check-custom-action:v0
</code></pre>
<h3 id="heading-step-32-testrun-using-docker">Step 3.2: Test/Run Using Docker</h3>
<pre><code class="lang-dockerfile">docker <span class="hljs-keyword">run</span><span class="bash"> -it container-image-check-custom-action:v0 dockerhub shorty latest k4kratik dckr_pat_1234</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713856817233/75517bb0-8254-40e1-a023-0fd1b6af27da.png" alt class="image--center mx-auto" /></p>
<p>we can see it give us outputs as expected. <em>(Yes, I know. I just tested it for DockerHub and not for AWS ECR.)</em></p>
<p><img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExYWZzZ2k5Y2FycWxwNGg5dmJpcWc4NHcwOG9qYXJ4anoyY3lwYmFuZCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/CuMiNoTRz2bYc/giphy.gif" alt="GIF by Giphy QA" class="image--center mx-auto" /></p>
<h3 id="heading-step-4-define-the-action-definition-in-actionyaml">Step 4 : Define the Action definition in action.yaml</h3>
<p>Now, we will create <code>action.yaml</code> so we can specify GitHub how to use our script in other workflows as a custom action.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Container</span> <span class="hljs-string">Image</span> <span class="hljs-string">Existence</span> <span class="hljs-string">Checker</span>

<span class="hljs-attr">description:</span> <span class="hljs-string">Check</span> <span class="hljs-string">if</span> <span class="hljs-string">a</span> <span class="hljs-string">given</span> <span class="hljs-string">ECR</span> <span class="hljs-string">or</span> <span class="hljs-string">Docker</span> <span class="hljs-string">Hub</span> <span class="hljs-string">image</span> <span class="hljs-string">exists</span> <span class="hljs-string">or</span> <span class="hljs-string">not</span> <span class="hljs-string">in</span> <span class="hljs-string">the</span> <span class="hljs-string">registry.</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-attr">type:</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">default:</span> <span class="hljs-string">"ecr"</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">Type</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">registry,</span> <span class="hljs-string">allowed</span> <span class="hljs-string">values</span> <span class="hljs-string">are</span> <span class="hljs-string">"ecr"</span> <span class="hljs-string">and</span> <span class="hljs-string">"dockerhub"</span>
  <span class="hljs-attr">container_repo_name:</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">Name</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">Container</span> <span class="hljs-string">Repository</span>
  <span class="hljs-attr">image_tag:</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">Image</span> <span class="hljs-string">Tag</span> <span class="hljs-string">for</span> <span class="hljs-string">which</span> <span class="hljs-string">you</span> <span class="hljs-string">want</span> <span class="hljs-string">to</span> <span class="hljs-string">check.</span>
  <span class="hljs-attr">dockerhub_username:</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">Docker</span> <span class="hljs-string">Hub</span> <span class="hljs-string">username</span> <span class="hljs-string">for</span> <span class="hljs-string">authentication</span> <span class="hljs-string">for</span> <span class="hljs-string">private</span> <span class="hljs-string">repositories.</span>
  <span class="hljs-attr">dockerhub_token:</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">Docker</span> <span class="hljs-string">Hub</span> <span class="hljs-string">Personal</span> <span class="hljs-string">Access</span> <span class="hljs-string">Token</span> <span class="hljs-string">for</span> <span class="hljs-string">authentication</span> <span class="hljs-string">for</span> <span class="hljs-string">private</span> <span class="hljs-string">repositories.</span>

<span class="hljs-attr">outputs:</span>
  <span class="hljs-attr">image_exists:</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"A boolean value to indicate if the image exists"</span>

<span class="hljs-attr">runs:</span>
  <span class="hljs-attr">using:</span> <span class="hljs-string">"docker"</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">"Dockerfile"</span>
  <span class="hljs-attr">args:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">${{</span> <span class="hljs-string">inputs.type</span> <span class="hljs-string">}}</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">${{</span> <span class="hljs-string">inputs.container_repo_name</span> <span class="hljs-string">}}</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">${{</span> <span class="hljs-string">inputs.image_tag</span> <span class="hljs-string">}}</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">${{</span> <span class="hljs-string">inputs.dockerhub_username</span> <span class="hljs-string">}}</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">${{</span> <span class="hljs-string">inputs.dockerhub_token</span> <span class="hljs-string">}}</span>
</code></pre>
<h4 id="heading-explaining-actionyaml">Explaining <code>action.yaml</code></h4>
<p>This is the file which stays in root of our project dir and GitHub uses this to understand your custom action.</p>
<ol>
<li><p><code>inputs</code> : we have to define what inputs we want from user when he/she uses our custom action.</p>
</li>
<li><p><code>runs</code> : we have to define the runtime behaviour of our custom action.</p>
<ol>
<li><p><code>using</code> : we define what we want to use for running our code. you can use node versions like <code>node20</code> , <code>docker</code> or <code>composite</code></p>
</li>
<li><p><code>image</code>: either you can give direct image from docker hub e.g. <code>docker://k4kratik/container-image-existence-checker:latest</code> or <code>Dockerfile</code> path to build image on the go. For now we are using <code>Dockerfile</code>.</p>
</li>
<li><p><code>args</code> : all the arguments to our code.</p>
</li>
</ol>
</li>
</ol>
<h2 id="heading-step-5-publish-to-github-marketplace">Step 5 : Publish To GitHub Marketplace</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713857042163/e7836d98-d255-436c-9313-dc0c56092c75.png" alt class="image--center mx-auto" /></p>
<p>Create a release - named <strong>v1</strong> (You may have to accept agreement.), Check box which says <em>Publish this Action to the GitHub Marketplace :</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713857471630/39925f72-0380-4157-a83b-e8b802600e9b.png" alt class="image--center mx-auto" /></p>
<p><em>(FYI - you will only see</em> <strong><em>Publish this Action to the GitHub Marketplace</em></strong> <em>if your repo is public.)</em></p>
<p>We got it published here : <a target="_blank" href="https://github.com/marketplace/actions/container-image-existence-checker">https://github.com/marketplace/actions/container-image-existence-checker</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713941252393/cc69b4e1-7e90-439b-9f9f-3214c3a71b7d.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-step-6-use-this-newly-created-github-action-in-some-other-repository">Step 6 : Use this newly created GitHub Action in some other repository</h2>
<p>I have created a sample repository to test our new custom action : <a target="_blank" href="https://github.com/k4kratik/workflow-testing/">https://github.com/k4kratik/workflow-testing/</a></p>
<p>Basically this is sample repository where some code is hosted, every time there is a commit on main branch it will trigger a GitHub Action workflow where code will be built and pushed to DockerHub.</p>
<h3 id="heading-workflow-for-ecr">Workflow for ECR :</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">ECR</span> <span class="hljs-string">CI/CD</span> <span class="hljs-string">Workflow</span> <span class="hljs-string">for</span> <span class="hljs-string">Service</span>

<span class="hljs-attr">env:</span>
  <span class="hljs-attr">SERVICE_NAME:</span> <span class="hljs-string">my-backend-service</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">main</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">ci:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Job</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configure</span> <span class="hljs-string">AWS</span> <span class="hljs-string">credentials</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">aws-actions/configure-aws-credentials@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">aws-access-key-id:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ACCESS_KEY_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">aws-secret-access-key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_SECRET_ACCESS_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">aws-region:</span> <span class="hljs-string">ap-south-1</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Login</span> <span class="hljs-string">to</span> <span class="hljs-string">Amazon</span> <span class="hljs-string">ECR</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">login-ecr</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">aws-actions/amazon-ecr-login@v2</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Check</span> <span class="hljs-string">if</span> <span class="hljs-string">the</span> <span class="hljs-string">image</span> <span class="hljs-string">exists</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">k4kratik/container-image-check-custom-action@v4</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">check_image</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">ecr</span>
          <span class="hljs-attr">container_repo_name:</span> <span class="hljs-string">${{env.SERVICE_NAME}}</span>
          <span class="hljs-attr">image_tag:</span> <span class="hljs-string">${{</span> <span class="hljs-string">github.sha</span> <span class="hljs-string">}}</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Docker</span> <span class="hljs-string">build</span> <span class="hljs-string">and</span> <span class="hljs-string">push</span> <span class="hljs-string">to</span> <span class="hljs-string">ECR</span> <span class="hljs-string">(Skip</span> <span class="hljs-string">this</span> <span class="hljs-string">step</span> <span class="hljs-string">if</span> <span class="hljs-string">image</span> <span class="hljs-string">exists)</span>
        <span class="hljs-attr">if:</span> <span class="hljs-string">steps.check_image.outputs.image_exists</span> <span class="hljs-type">!=</span> <span class="hljs-string">'true'</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          docker build -t ${{ secrets.ECR_REGISTRY }}/${{env.SERVICE_NAME}}:${{ github.sha }} .
          docker push ${{ secrets.ECR_REGISTRY }}/${{env.SERVICE_NAME}}:${{ github.sha }}</span>
</code></pre>
<p>Notice that I am using some values as GitHub Secrets.</p>
<h3 id="heading-workflow-for-dockerhub">Workflow for DockerHub</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Docker</span> <span class="hljs-string">Hub</span> <span class="hljs-string">CI/CD</span> <span class="hljs-string">Workflow</span> <span class="hljs-string">for</span> <span class="hljs-string">Service</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">main</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">ci:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Job</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Check</span> <span class="hljs-string">if</span> <span class="hljs-string">the</span> <span class="hljs-string">image</span> <span class="hljs-string">exists</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">check_image</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">k4kratik/container-image-check-custom-action@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">dockerhub</span>
          <span class="hljs-attr">container_repo_name:</span> <span class="hljs-string">shorty</span>
          <span class="hljs-attr">image_tag:</span> <span class="hljs-string">${{</span> <span class="hljs-string">github.sha</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">dockerhub_username:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.DOCKERHUB_USER</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">dockerhub_token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.DOCKERHUB_TOKEN</span> <span class="hljs-string">}}</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Login</span> <span class="hljs-string">to</span> <span class="hljs-string">Docker</span> <span class="hljs-string">Hub</span>
        <span class="hljs-attr">if:</span> <span class="hljs-string">steps.check_image.outputs.image_exists</span> <span class="hljs-type">!=</span> <span class="hljs-string">'true'</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">docker/login-action@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">username:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.DOCKERHUB_USER</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">password:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.DOCKERHUB_TOKEN</span> <span class="hljs-string">}}</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Docker</span> <span class="hljs-string">build</span> <span class="hljs-string">and</span> <span class="hljs-string">push</span> <span class="hljs-string">(Skip</span> <span class="hljs-string">this</span> <span class="hljs-string">step</span> <span class="hljs-string">if</span> <span class="hljs-string">image</span> <span class="hljs-string">exists)</span>
        <span class="hljs-attr">if:</span> <span class="hljs-string">steps.check_image.outputs.image_exists</span> <span class="hljs-type">!=</span> <span class="hljs-string">'true'</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          docker build -t k4kratik/shorty:${{ github.sha }} .
          docker push k4kratik/shorty:${{ github.sha }}</span>
</code></pre>
<h2 id="heading-step-7-demo-time-verify-if-the-custom-action-work">Step 7 : Demo Time : Verify If the custom Action Work</h2>
<p>We pushed some code in your sample repo called <a target="_blank" href="https://github.com/k4kratik/workflow-testing">workflow-testing</a> to start the workflow.</p>
<h4 id="heading-the-first-workflow-run">The first workflow run</h4>
<p>we can see it executed all the steps.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713946971100/dced6e86-80b8-415a-a7b0-1b5892c610a1.png" alt class="image--center mx-auto" /></p>
<p>Let's re-run it.</p>
<h4 id="heading-re-run-ecr">Re-Run : ECR</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713947041132/7cc415dc-dfe3-49f7-b836-763a81a0de9b.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-re-run-docker-hub">Re-Run: Docker Hub</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713947074396/70268e49-0c2c-4b2c-8895-7ec4cd968d58.png" alt class="image--center mx-auto" /></p>
<p>We can see in these re-runs that how beautifully it skipped the build and push image as images were already existing in relevant registries.</p>
<p><img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExNWZxOGQ3cDdwZG14dHR1enFvdG16eGwwaHBzcjBlZXk5NWkxMDIyciZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/l2Sqhu2KqzXuUxNdu/giphy.gif" alt="That Makes Me So Happy GIF by America's Got Talent" class="image--center mx-auto" /></p>
<p>That was it.</p>
<p>Thanks for reading. 🌻</p>
<p>Links:</p>
<ol>
<li><p><a target="_blank" href="https://github.com/marketplace/actions/container-image-existence-checker">GitHub Marketplace Link</a> (to our custom action)</p>
</li>
<li><p><a target="_blank" href="https://github.com/k4kratik/container-image-check-custom-action">GitHub Repo Link</a> (codebase of our custom action)</p>
</li>
<li><p><a target="_blank" href="https://github.com/k4kratik/workflow-testing">Workflow testing Repo</a> (where we used this custom action)</p>
</li>
</ol>
<p>Let me know if you have any suggestions.</p>
<p>-- Kratik</p>
]]></content:encoded></item><item><title><![CDATA[How to use Vault with External Secrets for Kubernetes in Production?]]></title><description><![CDATA[Hello, beautiful people on the Internet! 🌻
Today we are going to discuss how can we optimize the flow of storing secrets in Kubernetes and Also, learn to empower our developers to View/Modify secrets deployed in our Kubernetes cluster. We'll also de...]]></description><link>https://blogs.kratik.dev/how-to-use-vault-with-external-secrets-for-kubernetes-in-production</link><guid isPermaLink="true">https://blogs.kratik.dev/how-to-use-vault-with-external-secrets-for-kubernetes-in-production</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[hashicorp-vault]]></category><category><![CDATA[Devops]]></category><category><![CDATA[cloud native]]></category><category><![CDATA[external-secrets]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Fri, 20 Oct 2023 10:15:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695465399599/42659819-e8b7-435b-b2c0-4ab5a4b55a68.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello, beautiful people on the Internet! 🌻</p>
<p>Today we are going to discuss how can we optimize the flow of storing secrets in Kubernetes and Also, learn to empower our developers to View/Modify secrets deployed in our Kubernetes cluster. We'll also delegate access to our devs based on the GitHub team they belong to.</p>
<p>So let me ask you a basic question, how do you store secrets in your K8s cluster?</p>
<p>If you answer that <strong>you are using plain base64 encoded manifest files and managing them manually</strong>, then :</p>
<p><img src="https://i.giphy.com/media/wHE6Dd6RCVHQfjK5dy/giphy.webp" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-issues-in-the-current-approach">Issues in the current approach</h3>
<ol>
<li><p>No easy visibility - No GUI to easily manage secrets</p>
</li>
<li><p>No Version Controlling</p>
</li>
<li><p>Need to expose K8s creds to devs if you want to share it with them. (You can create and set appropriate RBAC permissions for them though but it can not be used in some use cases)</p>
</li>
<li><p>No Support for any 3rd Party Authentication, for example - Using GitHub Team/Org to grant developers a predefined level of access.</p>
</li>
</ol>
<hr />
<h3 id="heading-simple-solution-use-vault-with-external-secrets">Simple Solution - Use Vault with External Secrets</h3>
<p>Yes, you heard it right, once this is set, delegating access to users would be a breeze :)</p>
<p><img src="https://media4.giphy.com/media/xjQfDCSRr2jkH3SPab/giphy.gif?cid=ecf05e470rx4qrqb4wgs6x6rvggqr7445wr9rel3iswhemi7&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g" alt="Season 1 Episode 3 GIF by BET Plus" class="image--center mx-auto" /></p>
<p>Action Items for us ⚡️</p>
<ul>
<li><p>Install External Secrets Operator</p>
</li>
<li><p>Install Vault</p>
</li>
<li><p>Configure Vault to Allow login using GitHub PAT</p>
</li>
<li><p>Configure Vault auth for External Secrets</p>
</li>
<li><p>Configure External Secrets to create secrets from Vault</p>
</li>
<li><p>Create a sample secret in Vault, and see that being created in K8s</p>
</li>
</ul>
<hr />
<h2 id="heading-install-external-secrets-operator">Install External Secrets Operator</h2>
<p><a target="_blank" href="https://external-secrets.io/latest/introduction/overview/">https://external-secrets.io/latest/introduction/overview/</a></p>
<p>It has three components - <strong>External Secrets Controller, Webhook, Cert-Controller</strong></p>
<p>You can install external secrets quickly with Helm!</p>
<p><a target="_blank" href="https://external-secrets.io/latest/introduction/getting-started/">https://external-secrets.io/latest/introduction/getting-started/</a></p>
<p><code>values.yaml</code></p>
<pre><code class="lang-yaml"><span class="hljs-comment">#? count of the controller pods for HA</span>
<span class="hljs-attr">replicaCount:</span> <span class="hljs-number">3</span>

<span class="hljs-comment">#? enable/disable the leader election - at any time, only one active controller is recommended</span>
<span class="hljs-attr">leaderElect:</span> <span class="hljs-literal">true</span>

<span class="hljs-comment">#? how many secrets to update concurrently</span>
<span class="hljs-attr">concurrent:</span> <span class="hljs-number">3</span>

<span class="hljs-comment">#? PDB configuration - helpful when doing maintenance tasks</span>
<span class="hljs-attr">podDisruptionBudget:</span>
  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">minAvailable:</span> <span class="hljs-number">1</span>

<span class="hljs-comment">#? Recommended - Exposes metrics!</span>
<span class="hljs-attr">serviceMonitor:</span>
  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
<span class="hljs-attr">webhook:</span>
  <span class="hljs-attr">serviceMonitor:</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
<span class="hljs-attr">certController:</span>
  <span class="hljs-attr">serviceMonitor:</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
</code></pre>
<pre><code class="lang-bash">helm repo add external-secrets https://charts.external-secrets.io

helm upgrade --install external-secrets external-secrets/external-secrets -f values.yaml -n external-secrets --create-namespace
</code></pre>
<p>once deployed, It will look something like below :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695553611814/7cf45cac-f55a-4568-9e7f-6c30b0274457.png" alt class="image--center mx-auto" /></p>
<p><strong>Note</strong> - You can see three deployments of <code>external-secrets</code> but as we have enabled <code>leaderElect</code> only one of them will be active and hold the lease, other replicas will be just waiting if the primary one goes down and then one of the standby replicas will be the leader. Isn't it cool?</p>
<p><img src="https://media3.giphy.com/media/1LFycEz3YJQHhxa9Qv/giphy.gif?cid=ecf05e47bwj9pxf9414upzjbexojv9h829fv7hh9gbhvsl7r&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g" alt class="image--center mx-auto" /></p>
<p><strong>Bonus!</strong> - More on K8s leader election : <a target="_blank" href="https://carlosbecker.com/posts/k8s-leader-election/">https://carlosbecker.com/posts/k8s-leader-election/</a></p>
<hr />
<h2 id="heading-install-vault">Install Vault</h2>
<h4 id="heading-working">Working</h4>
<p><em>The internal working of Vault is quite interesting and a little complex to sum up in few words, we will need another blog to explain it in more detail. If you are completely clueless, then you can read more about this here:</em></p>
<ol>
<li><p><a target="_blank" href="https://developer.hashicorp.com/vault/docs/what-is-vault#what-is-vault-1">https://developer.hashicorp.com/vault/docs/what-is-vault#what-is-vault-1</a></p>
</li>
<li><p><a target="_blank" href="https://developer.hashicorp.com/vault/docs/internals/architecture">https://developer.hashicorp.com/vault/docs/internals/architecture</a></p>
</li>
</ol>
<p>To install Vault, We will be using <a target="_blank" href="https://github.com/bank-vaults/bank-vaults">Banzaicloud</a>'s Vault Operator aka <a target="_blank" href="https://github.com/banzaicloud/bank-vaults">Bank Vaults</a>!</p>
<p><em>To deploy anything in the Kubernetes Environment and to ensure its reliability and HA,</em> <strong><em>Operators</em></strong> <em>are becoming de-facto standards.</em></p>
<p>💡 <strong>Do you know :</strong> <em>A Kubernetes operator is an application-specific controller that extends the functionality of the Kubernetes API to create, configure, and manage instances of complex applications on behalf of a Kubernetes user.</em><br />Check more here : <a target="_blank" href="https://operatorframework.io/what/">https://operatorframework.io/what/</a></p>
<h3 id="heading-install-vault-operator-using-helm">Install Vault-Operator Using Helm</h3>
<pre><code class="lang-bash">helm repo add banzaicloud-stable https://kubernetes-charts.banzaicloud.com
helm repo update
helm upgrade --install vault-operator banzaicloud-stable/vault-operator -n vault-operator
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697653951779/984560b7-055b-4586-bad6-46a5069c75d6.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-setup-rbac">Setup RBAC</h3>
<p><code>rbac.yaml</code> - need to apply this to give needed RBAC access to our setup.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="5864d652a384601cb4a2718a902ba20b"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/k4kratik/5864d652a384601cb4a2718a902ba20b" class="embed-card">https://gist.github.com/k4kratik/5864d652a384601cb4a2718a902ba20b</a></div><p> </p>
<pre><code class="lang-bash"> kubectl apply -f vault/rbac.yml -n vault-operator
</code></pre>
<p><strong>💡 Explanation -</strong> Here, We are creating a Secret <strong>vault-sa-token-manual</strong> which will hold Kubernetes access token for ServiceAccount named <strong>vault <em>(Before Kubernetes v1.24 we didn't have to create the secret manually, see - https://stackoverflow.com/a/72258300)</em>.</strong> This ServiceAccount has access to read/update pods and all access to secrets.<br /><strong>Note</strong> - You can also fine-tune the access by adding only required verbs/resource names/namespaces.</p>
<p>Also, did you notice we are binding a default <code>ClusterRole</code> named <code>system:auth-delegator</code> to <code>Vault</code> ServiceAccount? <a target="_blank" href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/#other-component-roles">From the official docs</a> - <em>Allows delegated authentication and authorization checks.</em> The <code>system:auth-delegator</code> ClusterRole has the permissions to call the <strong>Token Review</strong> API.</p>
<p><img src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExYTJ6MDhnY2F5cWpseGJ6eTB6MXdsemg3eHdiNnRyenZrMXFzdXlmYSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/eKNrUbDJuFuaQ1A37p/giphy.gif" alt="Christian Bale GIF by PeacockTV" class="image--center mx-auto" /></p>
<h3 id="heading-deploying-vault-with-crd">Deploying Vault with CRD</h3>
<p>Now, as we have powered our cluster with Vault Operator, we can deploy Vault with a simple YAML file.</p>
<p><code>vault-deploy.yaml</code></p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="8a18b1d0a4b40debe06fc0614efa3508"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/k4kratik/8a18b1d0a4b40debe06fc0614efa3508" class="embed-card">https://gist.github.com/k4kratik/8a18b1d0a4b40debe06fc0614efa3508</a></div><p> </p>
<h2 id="heading-authentication-andamp-authorization">Authentication &amp; Authorization</h2>
<p>Here we are authenticating our users using <a target="_blank" href="https://developer.hashicorp.com/vault/docs/auth/github">GitHub auth backend</a>. And their access level is defined by the GitHub teams they belong to.</p>
<p>For Example, my account (<a target="_blank" href="https://github.com/k4kratik">k4kratik</a>) is assigned the admin policy <code>vault_admin</code>. Similarly, I have created a GitHub Team <code>DEVS_RW</code> in my organization which will grant its members access defined in the policy <code>rw_access_policy</code> and Also created a team <code>DEVS_RO</code> for devs for which I only want to configure read-only access, this team will be assigned with the <code>ro_access_policy</code>.</p>
<p>The above is visualized below :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697713087784/5ea7d446-fa3e-4ba7-a9a4-68d69d4a91b3.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-config-file-explanation">Config file Explanation :</h2>
<p>From <a target="_blank" href="https://gist.github.com/k4kratik/8a18b1d0a4b40debe06fc0614efa3508#file-vault-deploy-yml-L1-L102">Line #1 to #102</a>, I think it's pretty much self-explanatory. Let's Start with Line#106. (where <strong><em>externalConfig</em></strong> starts)</p>
<h4 id="heading-externalconfigpolicies">externalConfig.policies</h4>
<p>Just like any other setup, we are defining IAM policies here, these will be assigned to users/teams.</p>
<ul>
<li><p><code>ro_access_policy</code> : only <code>read</code> permissions.</p>
</li>
<li><p><code>rw_access_policy</code> : RW permissions. ["read", "list", "update"]</p>
</li>
<li><p><code>vault_admin</code> : Just defining <code>*</code> with verbs does not work to grant all permissions. So, we have defined all the admin-level access explicitly.</p>
<p>  <strong>Why ?</strong> : more info : <a target="_blank" href="https://discuss.hashicorp.com/t/vault-admin-policy/39803/2">https://discuss.hashicorp.com/t/vault-admin-policy/39803/2</a></p>
<p>  admin required access: <a target="_blank" href="https://developer.hashicorp.com/vault/tutorials/policies/policies#write-a-policy">https://developer.hashicorp.com/vault/tutorials/policies/policies#write-a-policy</a></p>
</li>
</ul>
<h4 id="heading-externalconfigauthkubernetes">externalConfig.auth:kubernetes</h4>
<ul>
<li><p><code>type</code> : As we are setting this up for <code>external-secrets</code> running on Kubernetes, its value should be <code>kubernetes</code></p>
</li>
<li><p><code>path</code> : the identifier for this auth entry in the vault.</p>
</li>
<li><p><code>config</code></p>
<ul>
<li><p><code>token_reviewer_jwt</code> : The JWT Token for <code>vault</code> ServiceAccount because it has the <code>system:auth-delegator</code> role, which can help us to use K8s' <code>TokenReview</code> API to validate other JWTs. To fetch its value, run the following command as in the earlier section of the blog, we have already set up the required RBAC.</p>
<p>  <strong>Command</strong> (copy this and replace with <code>TOKEN_REVIEWER_JWT_TOKEN_HERE</code> )</p>
<pre><code class="lang-bash">  kubectl get secret vault-sa-token-manual -n vault-operator -o go-template=<span class="hljs-string">'{{.data.token}}'</span> | base64 --decode
</code></pre>
</li>
<li><p><code>kubernetes_ca_cert</code> : PEM encoded <strong>CA cert</strong> for use by the TLS client used to talk with the <strong>Kubernetes API</strong>.</p>
<p>  <strong>Command</strong></p>
<pre><code class="lang-bash">  kubectl get secret vault-sa-token-manual -n vault-operator -o go-template=<span class="hljs-string">'{{index .data "ca.crt"}}'</span> | base64 --decode
</code></pre>
</li>
<li><p><code>kubernetes_host</code> : Endpoint for <code>Kubernetes API server</code>. Make sure it's reachable from vault pods.</p>
<pre><code class="lang-bash">  kubectl config view --minify --output jsonpath=<span class="hljs-string">"{.clusters[*].cluster.server}"</span>
</code></pre>
</li>
<li><p><code>disable_issuer_verification</code> : <code>true</code> - To disable the issuer verification.</p>
</li>
</ul>
</li>
<li><p><code>roles</code> - <em>define roles attached to this auth mechanism</em></p>
<ul>
<li><p><code>name</code>: name of the role</p>
</li>
<li><p><code>bound_service_account_names</code> : <code>["external-secrets"]</code> - We just want to allow external secrets SA, so adding that only.</p>
</li>
<li><p><code>bound_service_account_namespaces</code> <code>["external-secrets"]</code> - We just want to allow external secrets namespace, so adding that only.</p>
</li>
<li><p><code>token_policies</code> : <code>ro_access_policy</code> - Important! - the name of the policy to attach, Our <strong>external-secrets</strong> just need read access to read the secrets.</p>
</li>
<li><p><code>token_max_ttl</code> : token expiry time.</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-externalconfigauthgithub">externalConfig.auth:github</h4>
<ul>
<li><p><code>type</code> : As we are setting this up for auth via GitHub, its value should be <code>github</code></p>
</li>
<li><p><code>path</code> : the identifier for this auth entry in the vault.</p>
</li>
<li><p><code>config</code></p>
<ul>
<li><p><code>organization</code>: name of your GitHub organization. (<em>Replace GITHUB_ORGANIZATION_NAME_HERE with this)</em></p>
<p>  <strong>Note!</strong> - <strong><em>You must be part of this organization</em></strong>, or else you will not be able to log in, and you'll get the error : <code>Configuration is not set!</code></p>
</li>
<li><p><code>token_ttl</code>: lifetime for generated tokens.</p>
</li>
</ul>
</li>
</ul>
<p><code>map</code> (mapping for GitHub)</p>
<ul>
<li><p><code>teams</code> (mapping for GitHub Teams with Vault policies)</p>
<ul>
<li><p><strong>format</strong>:</p>
<ul>
<li>TEAM_NAME : POLICY_NAME</li>
</ul>
</li>
<li><p><strong>e.g.</strong></p>
<ul>
<li><p><code>DEV_RW</code>: <code>rw_access_policy</code></p>
</li>
<li><p><code>DEV_RO</code>: <code>ro_access_policy</code></p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><code>users</code> (mapping for GitHub Users with Vault policies)</p>
<ul>
<li>e.g. <code>k4kratik</code>: <code>vault_admin</code></li>
</ul>
</li>
</ul>
<h4 id="heading-externalconfigsecrets">externalConfig.secrets</h4>
<ul>
<li><p><code>path</code> : <code>secret</code> - path where the Vault will mount this secret engine.</p>
</li>
<li><p><code>type</code> : <code>kv</code> - Secret Engine type : Key/Value</p>
</li>
<li><p><code>options</code> :</p>
<ul>
<li><code>version</code> : 2 (recommended)</li>
</ul>
</li>
</ul>
<h4 id="heading-startupsecrets">startupSecrets</h4>
<ul>
<li>A test secret, which will be created at the startup.</li>
</ul>
<h4 id="heading-audit">audit</h4>
<ul>
<li><p>Audit devices are the components in Vault that collectively keep a detailed log of all requests to Vault.</p>
</li>
<li><p>The file audit device writes audit logs to a file. <a target="_blank" href="https://developer.hashicorp.com/vault/docs/audit/file#configuration">https://developer.hashicorp.com/vault/docs/audit/file#configuration</a></p>
</li>
</ul>
<hr />
<h2 id="heading-deploy-vault">Deploy Vault</h2>
<pre><code class="lang-bash">kubectl apply -f vault/vault-deploy.yml
</code></pre>
<p>it will deploy pods as shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697654333940/10e46cc8-6d29-47e3-9d02-a1b8d23b2f33.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-login-to-vault-ui">Login To Vault UI</h2>
<ul>
<li><p>Let's expose the Vault UI for login</p>
<pre><code class="lang-bash">  kubectl port-forward svc/vault -n vault-operator 8200:8200
</code></pre>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697654921736/55e15d2e-4018-4cf4-983d-616f4aa5188d.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>You will be welcome with the below screen :</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697654480467/f8fa9853-50c3-4cf9-9d43-770b41cac92b.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>Login with GitHub PAT (Personal Access Token)</strong></p>
<ul>
<li><p>Generate a GitHub PAT with at least <code>read:org</code> permission (<a target="_blank" href="https://docs.github.com/en/enterprise-server@3.6/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token">guide here</a>)</p>
</li>
<li><p>We will use that PAT to login into Vault</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697651896103/4d03a720-5f3b-4fb8-9cc9-1b8ce3625bfb.png" alt class="image--center mx-auto" /></p>
<p>  <strong>Note</strong> - Remember the auth policy we created, according to our GitHub username, org or Team, We will be getting relevant access.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697652699101/979a2bcc-9cdc-4750-a456-5788e4407cad.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Check the <code>secret</code> engine, you will find our test secret : <code>TEST_PROJECT_ONE/test</code></p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697652842671/2975c798-7a0b-4e25-8f49-57daa34fd646.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
<li><p><strong>Login with Root Token [Not Recommended]</strong></p>
<ul>
<li><p>Get the root token</p>
<pre><code class="lang-bash">  kubectl get secrets vault-unseal-keys -o jsonpath={.data.vault-root} | base64 --decode
</code></pre>
</li>
<li><p>Login with this token as shown below</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697655513417/76942d2a-db36-40b5-9e6b-4a28cef5811f.png" alt class="image--center mx-auto" /></p>
</li>
<li><p><strong>Check What we configured :</strong></p>
<ul>
<li><p>Check the auth we defined - `github` and `k8s-one`</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697652052543/e9aa79e3-3124-4c3a-b07a-4542057d318c.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Let's check in <code>k8s-one</code> auth method, if our role <code>k8s-one-external-secrets-role</code> exists or not</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697653247859/260e40d6-0595-408f-a90f-f3f4ff1766f6.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr />
<p>This sums up the basic vault authentication and authorization setup + external secrets.</p>
<p><img src="https://media3.giphy.com/media/eTxBDDpyjgF4wUVDmM/giphy.gif?cid=ecf05e47licwcjwb48e61n0x24usdbth9j7xkmps3ttukclo&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g" alt="Jimmy Fallon GIF by The Tonight Show Starring Jimmy Fallon" class="image--center mx-auto" /></p>
<p>but hold your horses! we still need to create secrets from the Vault using External Secrets. Apologies if the blog seems too long, but trust me, it will be all worth it in the end.</p>
<p><img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExY2w4YTV2YTd0YnZoODAyN3JxZWllaGNxYTRwdWMxdHI3bndyMmw3NCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/eMsBUFTW96Y5kzcwMy/giphy.gif" alt="Season 1 GIF by Showtime" class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-setup-external-secrets">Setup External Secrets</h2>
<p>External Secrets have two important types of objects</p>
<ol>
<li><p><code>SecretStore</code>(or <code>ClusterSecretStore</code>) - It defines how to access the external API secret provider. (e.g. Vault, AWS Secrets Manager)</p>
</li>
<li><p><code>ExternalSecret</code> (or <code>ClusterExternalSecret</code>) - It describes what data should be fetched, how the data should be transformed and saved as a K8s <code>Secret</code>.</p>
</li>
</ol>
<ul>
<li><p>Let's <strong>create a Cluster-scoped Secret Store</strong> to connect it with the Vault.</p>
</li>
<li><div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="9b8583d155423490cee0cd823d017da2"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/k4kratik/9b8583d155423490cee0cd823d017da2" class="embed-card">https://gist.github.com/k4kratik/9b8583d155423490cee0cd823d017da2</a></div><p> </p>
<pre><code class="lang-bash">  kubectl apply -f external-secrets/secret-store.yml
</code></pre>
</li>
<li><p>It will look something like below</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697705007783/4f045495-3d9d-455e-9fba-557da8b24877.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Before creating the <code>ExternalSecret</code>, let's create some test secrets in Vault</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697710464666/55f06d6d-f8a5-4afa-b064-30e267483a47.png" alt class="image--center mx-auto" /></p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697710598124/d59acec7-d619-426c-b734-50849b384721.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Now, Let's create an <code>ExternalSecret</code> to use our <code>ClusterSecretStore</code> and create a k8s secret using it.</p>
<p>  <code>sample-external-secret.yaml</code> (<a target="_blank" href="https://github.com/k4kratik/vault-with-external-secrets-k8s/blob/main/external-secrets/sample-external-secret.yaml">GitHub</a>)</p>
</li>
<li><div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="1df82010dd5f7263608b03adaecd5069"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/k4kratik/1df82010dd5f7263608b03adaecd5069" class="embed-card">https://gist.github.com/k4kratik/1df82010dd5f7263608b03adaecd5069</a></div><p> </p>
<pre><code class="lang-bash">  k apply -f external-secrets/sample-external-secret.yaml
</code></pre>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697705904475/ec846bf3-422d-49c8-bdc4-fb6f5dca8155.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Let's Verify it by checking the secret</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697706041068/cbf68135-c517-48f6-8ea9-e27eafa8b1dc.png" alt class="image--center mx-auto" /></p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697706301266/90b6edf1-583f-4aae-987a-41162904b9c3.png" alt class="image--center mx-auto" /></p>
<p>  Voila! we can see our secret is created and we can also see our values from Vault went through 🥳 🎉</p>
<p>  <img src="https://media.tenor.com/JVie7tHp0NAAAAAi/that-really-did-the-trick-kyle-broflovski.gif" alt="That Really Did The Trick Kyle Broflovski Sticker - That Really Did The Trick Kyle Broflovski South Park Stickers" class="image--center mx-auto" /></p>
<p>  <em>I had to decide between these two GIFs to place here, couldn't resist myself from using both</em> 😛 😅</p>
<p>  <img src="https://media.tenor.com/Z_z9W1qlVAQAAAAC/the-office-office.gif" alt="The Office Office GIF - The Office Office David GIFs" class="image--center mx-auto" /></p>
</li>
</ul>
<p><strong>Setup Alternatives (or Future Improvements?)</strong></p>
<ol>
<li><p>Instead of Vault, you can also use AWS Secrets-manager. (<a target="_blank" href="https://external-secrets.io/main/provider/aws-secrets-manager/">Official Provider</a>)</p>
</li>
<li><p>Instead of a self-signed cert, you can also use certs created by some reputed CA.</p>
</li>
<li><p>You can also use your domain and point the vault there with SSL. (ex. - AWS ALB Ingress w/ ACM SSL Cert)</p>
</li>
<li><p>You can create more fine-grained policies as per your use case.</p>
</li>
</ol>
<hr />
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://developer.hashicorp.com/vault/docs/auth/kubernetes">https://developer.hashicorp.com/vault/docs/auth/kubernetes</a></p>
</li>
<li><p><a target="_blank" href="https://developer.hashicorp.com/vault/docs/configuration">https://developer.hashicorp.com/vault/docs/configuration</a></p>
</li>
<li><p><a target="_blank" href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/">https://kubernetes.io/docs/reference/access-authn-authz/authentication/</a></p>
</li>
<li><p><a target="_blank" href="https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/">https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/</a></p>
</li>
<li><p><a target="_blank" href="https://learnk8s.io/microservices-authentication-kubernetes">https://learnk8s.io/microservices-authentication-kubernetes</a></p>
</li>
<li><p><a target="_blank" href="https://developer.hashicorp.com/vault/tutorials/policies/policies#write-a-policy">https://developer.hashicorp.com/vault/tutorials/policies/policies#write-a-policy</a></p>
</li>
<li><p><a target="_blank" href="https://developer.hashicorp.com/vault/docs/auth/github">https://developer.hashicorp.com/vault/docs/auth/github</a></p>
</li>
<li><p><a target="_blank" href="https://external-secrets.io/latest/api/externalsecret/">https://external-secrets.io/latest/api/externalsecret/</a></p>
</li>
<li><p><a target="_blank" href="https://external-secrets.io/latest/api/secretstore/">https://external-secrets.io/latest/api/secretstore/</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Optimizing Your Container Registry: Pushing Helm Charts to AWS ECR]]></title><description><![CDATA[We have always used container registries like AWS ECR, Docker Hub, etc. to host our container images but did you know you can also push other artifacts like Helm Charts to your favorite registry?

Let's learn how can we do that with AWS ECR.
Bonus! -...]]></description><link>https://blogs.kratik.dev/pushing-helm-charts-to-aws-ecr-and-docker-hub</link><guid isPermaLink="true">https://blogs.kratik.dev/pushing-helm-charts-to-aws-ecr-and-docker-hub</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Devops]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Helm]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sat, 03 Jun 2023 13:47:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1685799200921/7c26cafc-896c-4353-a5c1-ff7270b37e7f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We have always used container registries like AWS ECR, Docker Hub, etc. to host our container images but did you know you can also <strong>push</strong> other artifacts like <strong>Helm Charts</strong> to your favorite registry?</p>
<p><img src="https://media4.giphy.com/media/XXdiedv0abxEIZxkQz/giphy.gif?cid=ecf05e47x1vgnfady1hjlw1kk44j0b3o0b7m42r3wd4uzohy&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g" alt="Continue Season 9 GIF by The Office" class="image--center mx-auto" /></p>
<p>Let's learn how can we do that with AWS ECR.</p>
<p><strong>Bonus</strong>! - Along with AWS ECR, we will also explore how we can use Docker Hub to host our helm charts.</p>
<hr />
<h2 id="heading-containers-and-oci">Containers and OCI</h2>
<p>Containers have transformed the landscape of modern software development and deployment, enabling faster and more reliable shipping of software. Organizations are running containers at a massive scale.</p>
<p>Back when containerization was new, evolving, and getting popular following Docker's release, a large community emerged and started developing new container runtimes to solve their needs.</p>
<p>To solve the issue of incompatibility between different runtimes and image formats, the community formed <em>Open Container Initiative(OCI).</em></p>
<p><em>"The Open Container Initiative (OCI) is a lightweight, open governance structure (project), formed under the auspices of the Linux Foundation, for the express purpose of creating open industry standards around container formats and runtimes. The OCI was launched on June 22nd 2015 by</em> <strong><em>Docker</em></strong>*,* <strong><em>CoreOS</em></strong> <em>and other leaders in the container industry."</em></p>
<p>Currently, it defines three specifications:</p>
<h3 id="heading-oci-runtime-specification">OCI Runtime Specification</h3>
<ul>
<li><p>A technical specification developed by the Open Container Initiative (OCI) that defines the configuration and lifecycle of containers at the runtime level.</p>
</li>
<li><p>Includes various aspects like :</p>
<ul>
<li><p>Container Filesystem</p>
</li>
<li><p>Container Lifecycle</p>
</li>
<li><p>Container Processes</p>
</li>
<li><p>Container Configuration</p>
</li>
<li><p>Container Security</p>
</li>
</ul>
</li>
<li><p>More info here: <a target="_blank" href="https://github.com/opencontainers/runtime-spec">https://github.com/opencontainers/runtime-spec</a></p>
</li>
</ul>
<h3 id="heading-oci-image-specification">OCI Image Specification</h3>
<ul>
<li><p>A technical specification developed by the Open Container Initiative (OCI) that defines the format and structure of container images.</p>
</li>
<li><p>It includes various aspects like :</p>
<ul>
<li><p>Image Layout</p>
</li>
<li><p>Layers</p>
</li>
<li><p>Image Manifest</p>
</li>
<li><p>Image Configuration</p>
</li>
<li><p>Image References</p>
</li>
</ul>
</li>
<li><p>More info here: <a target="_blank" href="https://github.com/opencontainers/image-spec">https://github.com/opencontainers/image-spec</a></p>
</li>
</ul>
<h3 id="heading-oci-distribution-specification">OCI Distribution Specification</h3>
<ul>
<li><p>It defines an API protocol to facilitate and standardize the distribution of content.</p>
</li>
<li><p>More info here: <a target="_blank" href="https://github.com/opencontainers/distribution-spec">https://github.com/opencontainers/distribution*-spec*</a></p>
</li>
<li><p><em>"This is designed generically enough to be leveraged as a distribution mechanism for any type of content. The format of uploaded manifests, for example, need not necessarily adhere to the OCI Image Format Specification so long as it references the blobs which comprise a given artifact."</em></p>
</li>
</ul>
<h3 id="heading-adoption">Adoption</h3>
<ul>
<li><p>You can find various popular container runtimes that have adopted OCI standards, for example:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/opencontainers/runc">runC</a> <em>(BTW Docker donated its runtime engine called</em> <code>runc</code> <em>to OCI)</em></p>
</li>
<li><p><a target="_blank" href="https://github.com/containers/crun">crun</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/kata-containers/kata-containers">Kata</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/google/gvisor">gVisor</a></p>
</li>
</ul>
</li>
<li><p>One of the benefits of having a standard like OCI is that it ensures that containers created using different container runtimes and tools can be easily shared and executed across different environments.</p>
</li>
</ul>
<p>Enough with the theories and blah blah blah...Let's jump right into the action!</p>
<p><img src="https://media4.giphy.com/media/26tjZkBbRU5QHB5Ys/giphy.gif?cid=ecf05e47m7xnvwqlc81u4smy5b3djg6jxzfbe17xh9awh9yz&amp;ep=v1_gifs_related&amp;rid=giphy.gif&amp;ct=g" alt="homer simpson GIF" class="image--center mx-auto" /></p>
<h2 id="heading-push-your-helm-chart-to-ecr">Push your Helm chart to ECR</h2>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li><p><a target="_blank" href="https://helm.sh/docs/intro/install/">Helm CLI</a> (v3.8.0+)</p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html">AWS CLI</a></p>
</li>
</ul>
<h3 id="heading-creating-a-helm-chart">Creating a Helm Chart</h3>
<ol>
<li><p>Let's create a helm chart from scratch using helm cli</p>
<pre><code class="lang-bash"> helm create my-chart-for-ecr
</code></pre>
<p> it's a standard command which will generate a basic helm chart (which basically deploys an NGINX server.)</p>
</li>
<li><p>Let's read the content of <code>Chart.yaml</code></p>
<p> <code>cat my-chart-for-ecr/Chart.yaml</code></p>
<pre><code class="lang-yaml"> <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v2</span>
 <span class="hljs-attr">name:</span> <span class="hljs-string">my-chart-for-ecr</span>
 <span class="hljs-attr">description:</span> <span class="hljs-string">A</span> <span class="hljs-string">Helm</span> <span class="hljs-string">chart</span> <span class="hljs-string">for</span> <span class="hljs-string">Kubernetes</span>

 <span class="hljs-comment"># A chart can be either an 'application' or a 'library' chart.</span>
 <span class="hljs-comment">#</span>
 <span class="hljs-comment"># Application charts are a collection of templates that can be packaged into versioned archives</span>
 <span class="hljs-comment"># to be deployed.</span>
 <span class="hljs-comment">#</span>
 <span class="hljs-comment"># Library charts provide useful utilities or functions for the chart developer. They're included as</span>
 <span class="hljs-comment"># a dependency of application charts to inject those utilities and functions into the rendering</span>
 <span class="hljs-comment"># pipeline. Library charts do not define any templates and therefore cannot be deployed.</span>
 <span class="hljs-attr">type:</span> <span class="hljs-string">application</span>

 <span class="hljs-comment"># This is the chart version. This version number should be incremented each time you make changes</span>
 <span class="hljs-comment"># to the chart and its templates, including the app version.</span>
 <span class="hljs-comment"># Versions are expected to follow Semantic Versioning (https://semver.org/)</span>
 <span class="hljs-attr">version:</span> <span class="hljs-number">0.1</span><span class="hljs-number">.0</span>

 <span class="hljs-comment"># This is the version number of the application being deployed. This version number should be</span>
 <span class="hljs-comment"># incremented each time you make changes to the application. Versions are not expected to</span>
 <span class="hljs-comment"># follow Semantic Versioning. They should reflect the version the application is using.</span>
 <span class="hljs-comment"># It is recommended to use it with quotes.</span>
 <span class="hljs-attr">appVersion:</span> <span class="hljs-string">"1.16.0"</span>
</code></pre>
<p> Note that version of this helm chart is <code>0.1.0</code></p>
</li>
<li><p>Let's package (<em>packages a chart into a versioned chart archive file</em>) the chart so we can push it.</p>
<pre><code class="lang-bash"> helm package ./my-chart-for-ecr/
</code></pre>
</li>
</ol>
<p>Output :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685714244696/9b2c52c5-9e33-4634-8e7a-102a6dea13a3.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-preparing-ecr">Preparing ECR</h3>
<ol>
<li><p>Create a Repo in ECR for our use</p>
<pre><code class="lang-bash"> aws ecr create-repository \
      --repository-name my-chart-for-ecr \
      --region us-east-1 
 <span class="hljs-comment"># change region as per your need</span>
</code></pre>
</li>
</ol>
<h3 id="heading-getting-creds-for-ecr-authorization">Getting Creds for ECR Authorization</h3>
<ol>
<li><p>Authenticate your Helm client for our ECR</p>
<pre><code class="lang-bash"> aws ecr get-login-password \
      --region us-east-1 | helm registry login \
      --username AWS \
      --password-stdin AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com
</code></pre>
</li>
</ol>
<h3 id="heading-pushing-helm-chart-to-the-ecr">Pushing Helm Chart to the ECR</h3>
<ol>
<li>Push the chart using helm CLI.</li>
</ol>
<pre><code class="lang-bash">helm push my-chart-for-ecr-0.1.0.tgz oci://AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/
</code></pre>
<ol>
<li>Describe your Helm chart.</li>
</ol>
<pre><code class="lang-bash">aws ecr describe-images \
     --repository-name my-chart-for-ecr \
     --region us-east-1
</code></pre>
<p>Output :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685716796305/f4bba686-e3f5-45f1-81e6-da6bdd2f331d.png" alt class="image--center mx-auto" /></p>
<p>let's observe it in AWS Console &gt; ECR</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685717139790/37b6f368-4573-45c7-808c-5b90db2f2305.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-using-helm-chart-hosted-on-aws-ecr">Using Helm Chart hosted on AWS ECR</h3>
<ol>
<li><p>Login to AWS ECR and Authenticate your helm client. (Just like we did in the above step.)</p>
<pre><code class="lang-bash"> aws ecr get-login-password \
      --region us-east-1 | helm registry login \
      --username AWS \
      --password-stdin AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com
</code></pre>
</li>
<li><p>Install the helm chart ( <code>--dry-run</code> to see the output)</p>
<pre><code class="lang-bash"> helm install chart-from-ecr oci://AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/my-chart-for-ecr --version 0.1.0 --dry-run
</code></pre>
</li>
</ol>
<p>Output:</p>
<pre><code class="lang-bash">Pulled: 333333333333.dkr.ecr.us-east-1.amazonaws.com/my-chart-for-ecr:0.1.0
Digest: sha256:5b14b2094ab4581a74ed2bc036993da6cdee10c06cd463027a50f2b227ebf7da
NAME: chart-from-ecr
LAST DEPLOYED: Sat Jun  3 12:04:42 2023
NAMESPACE: crafto
STATUS: pending-install
REVISION: 1
HOOKS:
---
<span class="hljs-comment"># Source: my-chart-for-ecr/templates/tests/test-connection.yaml</span>
apiVersion: v1
kind: Pod
metadata:
  name: <span class="hljs-string">"chart-from-ecr-my-chart-for-ecr-test-connection"</span>
  labels:
    helm.sh/chart: my-chart-for-ecr-0.1.0
    app.kubernetes.io/name: my-chart-for-ecr
    app.kubernetes.io/instance: chart-from-ecr
    app.kubernetes.io/version: <span class="hljs-string">"1.16.0"</span>
    app.kubernetes.io/managed-by: Helm
  annotations:
    <span class="hljs-string">"helm.sh/hook"</span>: <span class="hljs-built_in">test</span>
spec:
  containers:
    - name: wget
      image: busybox
      <span class="hljs-built_in">command</span>: [<span class="hljs-string">'wget'</span>]
      args: [<span class="hljs-string">'chart-from-ecr-my-chart-for-ecr:80'</span>]
  restartPolicy: Never
MANIFEST:
---
<span class="hljs-comment"># Source: my-chart-for-ecr/templates/serviceaccount.yaml</span>
apiVersion: v1
kind: ServiceAccount
metadata:
  name: chart-from-ecr-my-chart-for-ecr
  labels:
    helm.sh/chart: my-chart-for-ecr-0.1.0
    app.kubernetes.io/name: my-chart-for-ecr
    app.kubernetes.io/instance: chart-from-ecr
    app.kubernetes.io/version: <span class="hljs-string">"1.16.0"</span>
    app.kubernetes.io/managed-by: Helm
---
<span class="hljs-comment"># Source: my-chart-for-ecr/templates/service.yaml</span>
apiVersion: v1
kind: Service
metadata:
  name: chart-from-ecr-my-chart-for-ecr
  labels:
    helm.sh/chart: my-chart-for-ecr-0.1.0
    app.kubernetes.io/name: my-chart-for-ecr
    app.kubernetes.io/instance: chart-from-ecr
    app.kubernetes.io/version: <span class="hljs-string">"1.16.0"</span>
    app.kubernetes.io/managed-by: Helm
spec:
  <span class="hljs-built_in">type</span>: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: my-chart-for-ecr
    app.kubernetes.io/instance: chart-from-ecr
---
<span class="hljs-comment"># Source: my-chart-for-ecr/templates/deployment.yaml</span>
apiVersion: apps/v1
kind: Deployment
metadata:
  name: chart-from-ecr-my-chart-for-ecr
  labels:
    helm.sh/chart: my-chart-for-ecr-0.1.0
    app.kubernetes.io/name: my-chart-for-ecr
    app.kubernetes.io/instance: chart-from-ecr
    app.kubernetes.io/version: <span class="hljs-string">"1.16.0"</span>
    app.kubernetes.io/managed-by: Helm
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: my-chart-for-ecr
      app.kubernetes.io/instance: chart-from-ecr
  template:
    metadata:
      labels:
        app.kubernetes.io/name: my-chart-for-ecr
        app.kubernetes.io/instance: chart-from-ecr
    spec:
      serviceAccountName: chart-from-ecr-my-chart-for-ecr
      securityContext:
        {}
      containers:
        - name: my-chart-for-ecr
          securityContext:
            {}
          image: <span class="hljs-string">"nginx:1.16.0"</span>
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {}

NOTES:
1. Get the application URL by running these commands:
  <span class="hljs-built_in">export</span> POD_NAME=$(kubectl get pods --namespace crafto -l <span class="hljs-string">"app.kubernetes.io/name=my-chart-for-ecr,app.kubernetes.io/instance=chart-from-ecr"</span> -o jsonpath=<span class="hljs-string">"{.items[0].metadata.name}"</span>)
  <span class="hljs-built_in">export</span> CONTAINER_PORT=$(kubectl get pod --namespace crafto <span class="hljs-variable">$POD_NAME</span> -o jsonpath=<span class="hljs-string">"{.spec.containers[0].ports[0].containerPort}"</span>)
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"Visit http://127.0.0.1:8080 to use your application"</span>
  kubectl --namespace crafto port-forward <span class="hljs-variable">$POD_NAME</span> 8080:<span class="hljs-variable">$CONTAINER_PORT</span>
</code></pre>
<p>So Yes! 🎉 🎉 🎉 We were able to see all the manifest this helm chart was going to install.</p>
<p><img src="https://media0.giphy.com/media/oF5oUYTOhvFnO/giphy.gif?cid=ecf05e47a218c93erv7ze9rcba8jp2tz7qfwjicfcdxifwig&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g" alt="SpongeBob gif. SpongeBob pumps his arms up and down excitedly, biting his little yellow lip." class="image--center mx-auto" /></p>
<h2 id="heading-bonus-push-your-helm-chart-to-dockerhub">[Bonus] Push your Helm chart to DockerHub</h2>
<ol>
<li><p>Let's continue from the part where we ran the <code>helm package</code></p>
</li>
<li><p>Log in to Docker Hub using your Helm client.</p>
<pre><code class="lang-bash"> helm registry login registry-1.docker.io -u DOCKERHUB_USERNAME
</code></pre>
</li>
<li><p>Push your chart to a DockerHub.</p>
<pre><code class="lang-bash"> helm push my-chart-for-ecr-0.1.0.tgz oci://registry-1.docker.io/DOCKERHUB_USERNAME
</code></pre>
</li>
<li><p>This will create a docker repository called <code>my-chart-for-ecr</code> in your DockerHub account. (Image would be <code>DOCKERHUB_USERNAME/my-chart-for-ecr</code>)</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685799367310/4a97ea57-b324-4e13-bba0-fc6fc71f2c03.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<p><strong>Note</strong> - we are able to push/host our helm charts to our same old container registries because Helm Chart is another <strong>OCI artifact</strong>! <em>(see reference links for more info)</em></p>
<hr />
<h2 id="heading-final-words">Final words</h2>
<p>This was just a way to store your helm charts on ECR / Docker Hub or any other OCI-compliant repositories. You can <strong>version control</strong> this (either with your services or in a separate repo) and also <strong>set up some CI/CD pipeline</strong> with some <strong>automated testing</strong>(maybe using <a target="_blank" href="https://github.com/k3s-io/k3s">k3s</a>/<a target="_blank" href="https://github.com/kubernetes-sigs/kind">kind</a> to spin <strong>a temp local k8s cluster</strong> and to test deploying your helm chart there) to achieve a complete lifecycle of your helm chart, just like a regular software project.</p>
<p>Thank you so much for being here! Have a nice one and see you in the next blog.</p>
<p><img src="https://media0.giphy.com/media/xaLjIV9qDtnvrh76NF/giphy.gif?cid=ecf05e47x1vgnfady1hjlw1kk44j0b3o0b7m42r3wd4uzohy&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g" alt="Season 9 Nbc GIF by The Office" class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-references">References</h2>
<ol>
<li><p><a target="_blank" href="https://docs.docker.com/docker-hub/oci-artifacts/#using-oci-artifacts-with-docker-hub">https://docs.docker.com/docker-hub/oci-artifacts/#using-oci-artifacts-with-docker-hub</a></p>
</li>
<li><p><a target="_blank" href="https://helm.sh/docs/topics/registries/#using-an-oci-based-registry">https://helm.sh/docs/topics/registries/#using-an-oci-based-registry</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/push-oci-artifact.html">https://docs.aws.amazon.com/AmazonECR/latest/userguide/push-oci-artifact.html</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/ECR_on_EKS.html#using-helm-charts-eks">https://docs.aws.amazon.com/AmazonECR/latest/userguide/ECR_on_EKS.html#using-helm-charts-eks</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Notification Anxiety - How to reduce it?]]></title><description><![CDATA[It will be a little different than the usual blogs but bear with me, I want to share something I tried and I think can be useful for others as well.
We all are addicted to our phones, checking notifications, getting distracted, and end up replying to...]]></description><link>https://blogs.kratik.dev/dealing-with-notification-anxiety</link><guid isPermaLink="true">https://blogs.kratik.dev/dealing-with-notification-anxiety</guid><category><![CDATA[anxiety]]></category><category><![CDATA[Mental Health]]></category><category><![CDATA[wellness]]></category><category><![CDATA[developers]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Tue, 21 Mar 2023 04:38:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/LPZy4da9aRo/upload/024dea22152d2270642efa354a78790f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It will be a little different than the usual blogs but bear with me, I want to share something I tried and I think can be useful for others as well.</p>
<p>We all are addicted to our phones, checking notifications, getting distracted, and end up replying to some of those notifications and the cycle repeats!</p>
<p><img src="https://media0.giphy.com/media/II5Wq8ZGjxGVWoFCnw/giphy.gif?cid=ecf05e47cdrvy0yzqxt87v6zr25xn4v144dzpuz7dnfpcttc&amp;rid=giphy.gif&amp;ct=g" alt class="image--center mx-auto" /></p>
<p>There is no denying that notifications are an integral part of our life and the apps we all have on our phone constantly bombards us with numerous notifications.</p>
<p>Definitely, they help us by keeping us informed and connected but at times, it can bring anxiety, stress, and feeling of being overwhelmed.</p>
<p>Through this blog, I hope to offer practical tips, tools, and resources to help you reduce the impact of notifications on your life, improve your productivity and focus, and achieve a better balance between your digital and offline worlds.</p>
<p><img src="https://media0.giphy.com/media/XXF1SoukZcmP5JBbXE/giphy.gif?cid=ecf05e47ltq4odusvw7uaxwbhuu2nbzopbl0b00do48ougnq&amp;rid=giphy.gif&amp;ct=g" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-what-is-it">What is it?</h2>
<p>Notification anxiety is a feeling of stress, worry, or pressure that arises from constantly checking and responding to notifications from electronic devices such as smartphones, laptops, or tablets.</p>
<p>The constant barrage of notifications can create a sense of urgency and a fear of missing out (FOMO), leading to a compulsive need to check and respond to notifications even if they are not critical or important.</p>
<p>This can result in a range of negative emotions such as anxiety, distraction, and decreased productivity. It can also lead to a lack of focus, poor sleep, and other negative impacts on mental health and well-being.</p>
<hr />
<h2 id="heading-solutions">Solutions?</h2>
<ol>
<li><h3 id="heading-differentiate-notifications-with-sounds">Differentiate Notifications with sounds</h3>
<p> You can <strong>set different notification sounds</strong> for different purposes.</p>
<p> For Example - in WhatsApp, you can set custom notification tones for specific contacts. You can implement this to isolate notification sounds for your favorite people (parents, siblings, partner, etc.) - Sometimes we are excited about some people, and from the alert sound if we are able to figure out about the sender then I think it would keep you calmer.</p>
<p> Also, For different apps, set different notification tones, so for the frequently used apps, you'll have an instant idea when you receive a notification and hear the sound. That way you can decide quickly if you should check your phone or not.</p>
</li>
<li><h3 id="heading-save-notifications-for-later-to-reduce-noise">Save notifications for later to reduce noise</h3>
<p> I use an app on my Android called <strong>Notisave</strong> (<a target="_blank" href="https://play.google.com/store/apps/details?id=com.tenqube.notisave&amp;hl=en&amp;gl=US">Play Store link</a>) - what it does is simple but super efficient for me.</p>
<p> So what we will be doing is transferring all our notifications from Notification Panel to this app so we can read them later.</p>
<p> It also saves notifications so we won't be missing anything + it supports the grouping of notifications on the basis of apps so we can make some groups for the office, social media, games, delivery apps, etc.</p>
<p> To get started, It's quite easy-peasy.</p>
<p> - First I need to select some apps for which I want to block the notifications.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679371192469/2f68873d-eb94-4796-b628-b175b8d73aac.jpeg" alt class="image--center mx-auto" /></p>
<p> - From now, every new notification for these blocked apps, won't bother you from the notification bar, instead, it will go to the Notisave for you to read it later.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679371208129/7f3756a8-bb77-43f5-8cd6-d0d0be7c17d8.jpeg" alt class="image--center mx-auto" /></p>
<p> So these are some of the ways I use to keep my mind calm and relaxed.</p>
</li>
</ol>
<p>Within a short span of time, you will see how many notifications get bombarded to you so frequently but as we don't have any count or estimate of the number we ignore the impact of this on us.</p>
<p>For Example - You can see I got around 9.2k notifications which is the number of times my phone would have vibrated/alerted/distracted me.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679371930197/50aa5d49-57b2-4a28-96c1-23111eb11b74.png" alt class="image--center mx-auto" /></p>
<p>In conclusion, while it's natural to feel anxious about notifications, it's important to remember that they don't define us. By setting boundaries and practicing mindfulness, we can learn to manage our notification anxiety and live more peaceful, present lives.</p>
<p>Hope this helps you. See you in the next one!</p>
<p><img src="https://media1.giphy.com/media/6uIqPGAUYFztGBjxEi/giphy.gif?cid=ecf05e474zc5ohfmfbnw4oxe0aj6ap2z3elrxffg39dqd70s&amp;rid=giphy.gif&amp;ct=g" alt class="image--center mx-auto" /></p>
<p>Thanks for reading 🌻</p>
]]></content:encoded></item><item><title><![CDATA[Build multi-CPU architecture compatible Container Images]]></title><description><![CDATA[Today, we are going to learn how we can build docker images for containers that will be compatible with the amd64 and arm64 CPU architecture types.
I chose these two CPU types because amd64 is already widely used with our Linux/Windows/Mac Computers ...]]></description><link>https://blogs.kratik.dev/build-multi-cpu-architecture-compatible-container-images</link><guid isPermaLink="true">https://blogs.kratik.dev/build-multi-cpu-architecture-compatible-container-images</guid><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[containers]]></category><category><![CDATA[Linux]]></category><category><![CDATA[docker images]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sun, 11 Sep 2022 08:15:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/MPbECMMCyiw/upload/v1660368121647/o6AvWeKvL.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://i.giphy.com/media/IcZhFmufozDCij3p22/giphy.webp" /> </p>
<p>Today, we are going to learn how we can build docker images for containers that will be compatible with the amd64 and arm64 CPU architecture types.</p>
<p>I chose these two CPU types because <strong>amd64</strong> is already widely used with our Linux/Windows/Mac Computers and <strong>arm64</strong> is getting popular nowadays, for example - new Mac computers are coming with Apple's in-house M1 processor which is arm64 based and if you talk about Cloud, AWS is offering various VMs which are powered by Graviton processors which are again arm64 based, So if your container images are able to run on these two kinds of CPUs, you will be able to utilize most of it. </p>
<p>Along with that what I want is that we understand each and every word that we will be using.</p>
<hr />
<h3 id="heading-what-is-cpu">What is CPU?</h3>
<p>CPU is short for Central Processing Unit, which is generally called processor(or microprocessor).</p>
<p>Also called the heart/brain of the computer. It performs all arithmetic and logical operations. </p>
<p>Inside the CPU, there are transistors, which control the flow of electricity. You know, a CPU can perform millions of instructions per second but can only do one instruction at any given point in time. </p>
<p>Then we have multi-core CPUs, each core is a sort of separate CPU, which can help the CPU to do multiple tasks simultaneously. </p>
<p>Read these for more details on:</p>
<ol>
<li>What is CPU - https://www.freecodecamp.org/news/what-is-cpu-meaning-definition-and-what-cpu-stands-for/</li>
<li>How does a CPU work - https://www.freecodecamp.org/news/how-does-a-cpu-work/</li>
</ol>
<hr />
<h3 id="heading-what-are-isa-andamp-micro-architecture">What are ISA &amp; Micro-architecture?</h3>
<p>An <strong>instruction set</strong> is a group of commands for a central processing unit (CPU) in machine language.</p>
<p>Now, ISA (instruction set architecture) is a part of processor architecture. It works as an interface(communication rules) between hardware and software. It's a group of commands which are implemented in the processor's circuitry also called <strong>microarchitecture</strong>. </p>
<p>Few examples of what ISA defines: </p>
<ol>
<li>How binary instructions are formatted</li>
<li>How RAM/ROM is accessed</li>
<li>Maximum bit length for all instructions</li>
</ol>
<p>It helps us to separate hardware and software development without worrying about the other. </p>
<p>ISA has two major classifications : </p>
<ol>
<li><strong>CISC - Complex instruction set computer</strong></li>
</ol>
<p>It includes many specialized instructions. It will use fewer instructions but each instruction will take multiple machine cycles. </p>
<ol>
<li><strong>RISC - Reduced instruction set computer</strong></li>
</ol>
<p>It uses a smaller, optimized set of generalized, simple instructions. It will use more number of instructions but each instruction will take one clock cycle. </p>
<hr />
<h3 id="heading-what-is-amd64">What is amd64?</h3>
<p>Before that let's see what is 32-bit/64-bit architecture - 
It simply defines that in a single <strong>instruction cycle</strong> (<em>fetch-decode-execute</em>) how much data a microprocessor can process.</p>
<p>32-bit means it can process 4 bytes of data and for 64-bit it is 8 bytes. </p>
<p><strong>x86</strong> is a family of instruction set architectures (ISA) for computer processors initially developed by Intel. (<strong>86</strong> comes from the microprocessor name which was <strong>8086</strong> )</p>
<p>AMD took this x86 architecture which could only support 32-bit and then added 64-bit computing capabilities, that's why it is known as <strong>amd64</strong>. Also referred to as, Intel 64, x86-64, x64.</p>
<hr />
<h3 id="heading-what-is-arm64-then">What is arm64, then?</h3>
<p>Let's understand what are ARM Processors first. </p>
<p>ARM stands for <strong>Advance RISC Machine</strong> - It is an ISA developed by a company called ARM Ltd. </p>
<p>So, ARM Licenses the architecture to other companies so they can develop their custom processors. Example - Apple's Silicon processors. </p>
<p>ARM Processors are generally used in smartphones, tablets, IoT devices, etc. due to their low power consumption and high-performance nature.</p>
<p><strong>So ARM64 is the 64-Bit version of ARM processors(aka ARMv8-A).</strong></p>
<hr />
<h3 id="heading-back-to-docker-image-building">Back to Docker Image Building</h3>
<p>So now as we have some sort of idea about CPUs, we can guess that when we build docker images on a machine with specific architecture(amd64 or arm64), it is compiled in a way that it can run only on that specific type of platforms. </p>
<p>So what we will try to solve here is to build universal docker images, which can run on these platforms, without us worrying about the rebuilding of images.   </p>
<hr />
<h3 id="heading-hands-on-running-docker-image-cross-platform">Hands-On: Running Docker Image cross-platform</h3>
<p>I built a docker image on my Macbook with an M1 Pro chip (arm64) and then tried to run it on an EC2 with an amd64 processor, this was the output : </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660454463254/Q9ydqlFga.png" alt="image.png" /></p>
<pre><code><span class="hljs-attribute">WARNING</span>: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64) and no specific platform was requested.

<span class="solidity">exec <span class="hljs-operator">/</span>docker<span class="hljs-operator">-</span>entrypoint.sh: exec format <span class="hljs-function"><span class="hljs-keyword">error</span></span></span>
</code></pre><hr />
<h3 id="heading-docker-buildx-for-the-rescue">Docker Buildx for the rescue!!!</h3>
<p><img src="https://i.giphy.com/media/7QxG5242BM12HQk1KR/giphy.webp" /></p>
<p><code>docker buildx</code> is a CLI plugin that enhances the existing docker build capabilities. (which uses another open-source project called <a target="_blank" href="https://github.com/moby/buildkit">buildkit</a>.)</p>
<p><em>BuildKit is a toolkit for converting source code to <strong>build artifacts</strong> in an efficient, expressive, and repeatable manner.</em></p>
<p>If you want to know more about <code>moby/buildkit</code> you can read this Introductory blog post: https://blog.mobyproject.org/introducing-buildkit-17e056cc5317</p>
<p>You can refer to this <a target="_blank" href="https://github.com/docker/buildx#installing">Installation guide</a> on how to install <code>buildx</code> into your system, It comes pre-installed with Docker Desktop for Windows/macOS.</p>
<h3 id="heading-lets-use-buildx-to-build-our-docker-images">Let's use buildx to build our docker images</h3>
<h4 id="heading-step-1-list-existing-builders">Step-1: List existing builders</h4>
<pre><code class="lang-bash">docker buildx ls
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660479961330/2cQQhEfZa.png" alt="image.png" /></p>
<h4 id="heading-step-2-create-a-new-builder-to-support-multi-architecture">Step-2: Create a new builder to support multi-architecture</h4>
<pre><code class="lang-bash">docker buildx create --name mybuilder --driver docker-container --use --bootstrap
</code></pre>
<p>We are instructing <code>docker</code> to create a new <strong>buildx builder</strong> named <code>mybuilder</code> which will use the <code>docker-container</code> driver (which supports multi-architecture builds), also instructing it to use this new builder, also instructing this to boot this builder which means it will pull necessary docker image to run this builder and run the builder container. (as our <code>driver</code> is <strong>docker-container</strong>) </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661830894788/ME-e1tWnF.png" alt="image.png" /></p>
<h4 id="heading-step-3-create-a-sample-dockerfile">Step-3: Create a Sample Dockerfile</h4>
<p>Consider this simple Dockerfile, which intends to run an NGINX server </p>
<p><strong>Dockerfile</strong></p>
<pre><code class="lang-Dockerfile">FROM nginx:stable-alpine

LABEL MAINTAINER="KRATIK JAIN"

COPY --chmod=0755 custom-web-page.sh /docker-entrypoint.d/
</code></pre>
<p><strong>custom-web-page<span>.</span>sh</strong></p>
<pre><code class="lang-sh"><span class="hljs-meta">#!/bin/sh</span>
uname -a &gt; /usr/share/nginx/html/index.html
</code></pre>
<p><em>what this script does is replaces default HTML with the output of <code>uname -a</code>, which prints out the system info like kernel info, Architecture, etc.</em></p>
<h4 id="heading-step-4-build-the-container-image-using-the-buildx">Step-4: Build the container image using the buildx</h4>
<pre><code>docker buildx build <span class="hljs-operator">-</span><span class="hljs-operator">-</span>platform linux<span class="hljs-operator">/</span>amd64,linux<span class="hljs-operator">/</span>arm64,linux<span class="hljs-operator">/</span>arm<span class="hljs-operator">/</span>v7 <span class="hljs-operator">-</span>t k4kratik<span class="hljs-operator">/</span>multi<span class="hljs-operator">-</span>platform:v1 <span class="hljs-operator">-</span><span class="hljs-operator">-</span>push .
</code></pre><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662869733025/Ao1lvLez3.png" alt="image.png" /></p>
<p>Here you can notice the flag <code>--platform</code> &amp; <code>--push</code>: here you are specifying the platforms for which you want to build the image, and you also wish to push the image once done. That's amazing as build and push commands are in just one command. Output also seems to be more verbose. </p>
<h4 id="heading-step-5-run-this-docker-image-on-various-platforms">Step-5: Run this docker image on various platforms</h4>
<h4 id="heading-step-51-running-on-ec2-with-amd64-arch-ubuntu-machine">Step-5.1: Running on EC2 with <code>amd64</code> arch Ubuntu machine</h4>
<pre><code class="lang-bash">docker run --rm -itd -p 30000:80 k4kratik/multi-platform:v1
</code></pre>
<p>Now hit the server endpoint and check the response </p>
<pre><code class="lang-bash">curl localhost:30000
</code></pre>
<p><strong>Output</strong></p>
<pre><code><span class="hljs-attribute">Linux</span> e<span class="hljs-number">14</span>cc<span class="hljs-number">6</span>b<span class="hljs-number">4</span>eb<span class="hljs-number">86</span> <span class="hljs-number">5</span>.<span class="hljs-number">15</span>.<span class="hljs-number">0</span>-<span class="hljs-number">1011</span>-aws #<span class="hljs-number">14</span>-Ubuntu SMP Wed Jun <span class="hljs-number">1</span> <span class="hljs-number">20</span>:<span class="hljs-number">54</span>:<span class="hljs-number">22</span> UTC <span class="hljs-number">2022</span> x<span class="hljs-number">86</span>_<span class="hljs-number">64</span> Linux
</code></pre><h4 id="heading-step-52-running-on-macbook-pro-with-arm64-arch-m1-chip">Step-5.2: Running on Macbook Pro with <code>arm64</code> arch M1 Chip</h4>
<pre><code class="lang-bash">docker run --rm -itd -p 30000:80 k4kratik/multi-platform:v1
</code></pre>
<p>Now hit the server endpoint and check the response</p>
<pre><code class="lang-bash">curl localhost:30000
</code></pre>
<p><strong>Output</strong></p>
<pre><code><span class="hljs-attribute">Linux</span> <span class="hljs-number">595316994</span>de<span class="hljs-number">1</span> <span class="hljs-number">5</span>.<span class="hljs-number">10</span>.<span class="hljs-number">104</span>-linuxkit #<span class="hljs-number">1</span> SMP PREEMPT Thu Mar <span class="hljs-number">17</span> <span class="hljs-number">17</span>:<span class="hljs-number">05</span>:<span class="hljs-number">54</span> UTC <span class="hljs-number">2022</span> aarch<span class="hljs-number">64</span> Linux
</code></pre><p>Notice the output from both of the machines and observe the second last string, for EC2, it was: <code>x86_64</code> and for Macbook it was <code>aarch64</code>
which means it pulled the image according to its CPU architecture.</p>
<h4 id="heading-bonus-inspect-images">Bonus: Inspect Images</h4>
<p>Using the below command you can inspect the image to know more details about it and to know the platforms available:</p>
<pre><code class="lang-bash">docker buildx imagetools inspect k4kratik/multi-platform:v1
</code></pre>
<p><strong>Output</strong></p>
<pre><code class="lang-YAML"><span class="hljs-attr">Name:</span>      <span class="hljs-string">docker.io/k4kratik/multi-platform:v1</span>
<span class="hljs-attr">MediaType:</span> <span class="hljs-string">application/vnd.docker.distribution.manifest.list.v2+json</span>
<span class="hljs-attr">Digest:</span>    <span class="hljs-string">sha256:c534475bb099316622a719407a17e2da21d79d147e9f607b4a1262c2a6f3bb1a</span>

<span class="hljs-attr">Manifests:</span> 
  <span class="hljs-attr">Name:</span>      <span class="hljs-string">docker.io/k4kratik/multi-platform:v1@sha256:1d321fb1d14afe61e6f5c9ea2fc7e1b20bddd347c98a1b09f9367a6c446898b9</span>
  <span class="hljs-attr">MediaType:</span> <span class="hljs-string">application/vnd.docker.distribution.manifest.v2+json</span>
  <span class="hljs-attr">Platform:</span>  <span class="hljs-string">linux/amd64</span>

  <span class="hljs-attr">Name:</span>      <span class="hljs-string">docker.io/k4kratik/multi-platform:v1@sha256:47b6ffc133acac83fc70dd176606de8b033ae735d386160e6d68ee7d472119b3</span>
  <span class="hljs-attr">MediaType:</span> <span class="hljs-string">application/vnd.docker.distribution.manifest.v2+json</span>
  <span class="hljs-attr">Platform:</span>  <span class="hljs-string">linux/arm64</span>

  <span class="hljs-attr">Name:</span>      <span class="hljs-string">docker.io/k4kratik/multi-platform:v1@sha256:89f7b675b538e2aafe060dda535a48311bd090c63d0e3bb0184a440a32079f7d</span>
  <span class="hljs-attr">MediaType:</span> <span class="hljs-string">application/vnd.docker.distribution.manifest.v2+json</span>
  <span class="hljs-attr">Platform:</span>  <span class="hljs-string">linux/arm/v7</span>
</code></pre>
<h4 id="heading-bonus-pullrun-image-of-a-specific-platform">Bonus: Pull/Run Image of a specific platform</h4>
<p>You can use the following flag to pull or run an image of a specific platform</p>
<pre><code class="lang-bash">docker pull --platform linux/arm64 alpine:latest
</code></pre>
<pre><code class="lang-bash">docker run -itd --platform linux/arm64 nginx-alpine:latest
</code></pre>
<p>
<img src="https://i.giphy.com/media/l2QEdoFAgf1zmhEK4/giphy.webp" />
</p>


<p>Well, it was just scratching the surface. I hope it was helpful enough for you to get started on your feet so you can research and dig down further. </p>
<p>See you in the next blog! 
:)</p>
<p><strong>Update</strong>: I can not express my happiness when I got to know that many people are reacting to this blog and this blog got featured on <a target="_blank" href="https://www.hashnode.com">Hashnode</a></p>
<p>Thank you so much 🫶🏻 everyone! You just made my day 🥂</p>
<p>🛣</p>
]]></content:encoded></item><item><title><![CDATA[Securing your Logging Solution for Docker [with NGINX Reverse Proxy]]]></title><description><![CDATA[The Issue
From our past blog Best Logging Solution for Docker [Basic Version],
https://kratik.hashnode.dev/best-logging-solution-for-docker
We saw how to make logging a piece of cake using Loki & Grafana.
But I think there is one minor issue If your ...]]></description><link>https://blogs.kratik.dev/securing-loki-with-basic-auth</link><guid isPermaLink="true">https://blogs.kratik.dev/securing-loki-with-basic-auth</guid><category><![CDATA[Docker]]></category><category><![CDATA[logging]]></category><category><![CDATA[Devops]]></category><category><![CDATA[loki]]></category><category><![CDATA[nginx]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Fri, 12 Aug 2022 13:23:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660320741107/RRonXGBVU.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-issue">The Issue</h2>
<p>From our past blog <a target="_blank" href="https://kratik.hashnode.dev/best-logging-solution-for-docker">Best Logging Solution for Docker [Basic Version]</a>,</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://kratik.hashnode.dev/best-logging-solution-for-docker">https://kratik.hashnode.dev/best-logging-solution-for-docker</a></div>
<p>We saw how to make logging a piece of cake using Loki &amp; Grafana.</p>
<p>But I think there is one minor issue If your servers are on the public internet and you have enabled port forwarding for your use case, then anyone using your Loki URL, can add it as a data source in their Grafana Installation and will be able to see all of your sensitive logs, which is the last thing on earth we want. </p>
<p><img src="https://media2.giphy.com/media/zaxSiNlNNdvQEAGiit/giphy.gif" /></p>
<p>It's simple right?</p>
<p>When you run Loki, out of the box, it does not come with any kind of additional security layer.  So basically anyone who can access that URL, can access and see your logs, simple as that! </p>
<hr />
<p>Loki Official Documentation says that <em>Grafana Loki does not come with any included authentication layer.</em></p>
<p>https://grafana.com/docs/loki/latest/operations/authentication/</p>
<hr />
<h2 id="heading-the-solutions">The Solution(s)</h2>
<p>Now as I am thinking about it, feels like we can use some simple tactics to enhance security. Those are :</p>
<ol>
<li><p>If not required, <strong>don't launch your servers on the public subnet</strong>. (hence reducing the possibility of someone connecting from outside your network)</p>
</li>
<li><p>Do not use port-forwarding, instead use DNS which is supported by the docker network. (for example, instead of <code>http://Server_IP:3100</code> we can use <code>http://loki:3100</code>)</p>
</li>
<li><p>Use some firewall/SecurityGroups to whitelist services/users to allow limited access to required ports. </p>
</li>
<li><p><strong>Advance</strong> - You can use a reverse proxy(NGINX) with Basic Auth. So even if someone has connectivity to the endpoint, they need to enter basic auth credentials to connect :) </p>
</li>
</ol>
<hr />
<h2 id="heading-the-implementation-of-the-advanced-solution-just-for-fun">The Implementation of the advanced solution (Just for fun!)</h2>
<p><img src="https://media4.giphy.com/media/Cp1VukgdTkz7sJdlc3/giphy.gif" /></p>
<h3 id="heading-step-1-keep-your-environment-ready">Step-1: Keep your environment ready</h3>
<p>I am assuming, you already have a docker environment ready that is already using Loki without any auth method. </p>
<p>You can take a reference from <a target="_blank" href="https://kratik.hashnode.dev/best-logging-solution-for-docker">my previous blog</a> as well.</p>
<p>Do you know what I did?</p>
<p>I referred to this <a target="_blank" href="https://raw.githubusercontent.com/grafana/loki/main/production/docker-compose.yaml">official docker-compose file</a> to get started in a few seconds. </p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">loki:</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">loki:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">grafana/loki:2.6.1</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3100:3100"</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">-config.file=/etc/loki/local-config.yaml</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">loki</span>

  <span class="hljs-attr">promtail:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">grafana/promtail:2.6.1</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/var/log:/var/log</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">-config.file=/etc/promtail/config.yml</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">loki</span>

  <span class="hljs-attr">grafana:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">grafana/grafana:latest</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3000:3000"</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">loki</span>
</code></pre>
<h3 id="heading-step-2-prepare-the-nginx-reverse-proxy-with-basic-auth">Step-2: Prepare the NGINX reverse proxy with Basic Auth</h3>
<p>Luckily I stumbled upon this cool open-source project which does the job for you : </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/dtan4/nginx-basic-auth-proxy">https://github.com/dtan4/nginx-basic-auth-proxy</a></div>
<p>So now what? How to use this?</p>
<p>You just have to give the required env vars and it will launch NGINX accordingly.</p>
<p>Those are: </p>
<ol>
<li><strong>BASIC_AUTH_USERNAME</strong> &amp; <strong>BASIC_AUTH_PASSWORD</strong> =&gt; Basic Auth Creds</li>
<li><strong>PROXY_PASS</strong> =&gt; where you want to forward your request from NGINX reverse proxy</li>
<li><strong>PORT</strong> =&gt; The port, on which you want NGINX to listen. </li>
</ol>
<p><strong>Note</strong>: <em>Here we will add our Loki URL in the <strong>PROXY_PASS</strong> section so it can forward our request to Loki.</em></p>
<h3 id="heading-step-3-combining-all-the-pieces-together">Step-3: Combining all the pieces together</h3>
<p>So Now, as we know how can we secure our Loki Installation, let's add this reverse proxy in our <code>docker-compose.yaml</code> file. </p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">loki:</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">loki:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">grafana/loki:2.6.1</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">3100</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">-config.file=/etc/loki/local-config.yaml</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">loki</span>

  <span class="hljs-attr">promtail:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">grafana/promtail:2.6.1</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/var/log:/var/log</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">-config.file=/etc/promtail/config.yml</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">loki</span>

  <span class="hljs-attr">grafana:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">grafana/grafana:latest</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3000:3000"</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">loki</span>

  <span class="hljs-attr">nginx:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">quay.io/dtan4/nginx-basic-auth-proxy</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">BASIC_AUTH_USERNAME=loki_user</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">BASIC_AUTH_PASSWORD=loki_super_secret_password</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">PROXY_PASS=http://loki:3100</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">PORT=3200</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">SERVER_NAME=_</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">3200</span><span class="hljs-string">:3200</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">8090</span><span class="hljs-string">:8090</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">loki</span>
</code></pre>
<p>Here I also disabled port-forwarding for Loki, because that is not required. Also, notice that in the <strong>PROXY_PASS</strong> env var, I gave <code>loki:3100</code> which is the <strong>service name</strong> in the <code>docker-compose</code> file and is internally resolvable by the containers in the same docker network. </p>
<p>Here, we are running this NGINX server on port 3200 + we did port forwarding on the same port, so we can use localhost:3200 to connect to the NGINX server.</p>
<p>Also, port 8090 is used for NGINX metrics. You can check that on -&gt; <code>:8090/nginx_status</code></p>
<h3 id="heading-step-4-see-it-in-action">Step-4: See it in action!</h3>
<p>Now, up this docker-compose file and see what happens at the Grafana end. </p>
<p>So, let's try to add a data source, just like before. </p>
<h4 id="heading-trial-1-on-old-port-3100">Trial-1: On old port 3100</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660269994601/eMkxuOtvg.png" alt="image.png" /></p>
<p>As expected, we have disabled the port forwarding, it should fail.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660270106123/SDyure1DU.png" alt="image.png" /></p>
<h4 id="heading-trial-2-with-nginx-port-witho-creds">Trial-2: With NGINX Port (w/o creds)</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660270206347/QIPDZrbdJ.png" alt="image.png" /></p>
<p>Again, this also fails as above.</p>
<p>Just for curiosity, let's <code>curl</code> and see.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660270302091/WK8gUBdP7.png" alt="image.png" /></p>
<p>Oh ok, let's try with the creds which we supplied to NGINX in docker-compose.</p>
<pre><code>      <span class="hljs-operator">-</span> BASIC_AUTH_USERNAME<span class="hljs-operator">=</span>loki_user
      <span class="hljs-operator">-</span> BASIC_AUTH_PASSWORD<span class="hljs-operator">=</span>loki_super_secret_password
</code></pre><h4 id="heading-trial-3-nginx-with-basic-auth-creds">Trial-3: NGINX with Basic Auth Creds</h4>
<p>Now using the same endpoint, Enable the <strong>Basic Auth</strong>, and add those basic auth credentials</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660270435292/MBjS0w4te.png" alt="image.png" /></p>
<p>hmm...
<img src="https://media2.giphy.com/media/a5viI92PAF89q/giphy.gif" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660270547448/k81ZjucFs.png" alt="image.png" /></p>
<p>Voilà! - we were able to connect to our Loki Data source with an additional layer of security i.e. Basic Auth. </p>
<p>I know most of you are using Kubernetes out there and only a small set of users can relate to this(and implement it) but it is a really small fun exercise on how to use <strong>NGINX reverse proxy</strong>, setup <strong>Basic Auth</strong>, <strong>docker-compose</strong>(or docker swarm) <strong>inter-service communication</strong> and <strong>securing Loki</strong> installation. 😄</p>
<p>and the other reason to be happy is: </p>
<p><img src="https://media4.giphy.com/media/sTczweWUTxLqg/giphy.gif" /></p>
<hr />
<p>That was it! Let me know what you have to say in the comment box. </p>
<p>Thanks for reading :) </p>
<hr />
<h3 id="heading-reads">Reads:</h3>
<ol>
<li>https://grafana.com/docs/loki/latest/operations/authentication/</li>
<li>https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/</li>
<li>bash script used in the NGINX docker image: https://github.com/dtan4/nginx-basic-auth-proxy/blob/master/files/run.sh</li>
<li>Also, the Dockerfile: https://github.com/dtan4/nginx-basic-auth-proxy/blob/master/Dockerfile</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[How to attach IAM roles to Pods in AWS EKS Cluster]]></title><description><![CDATA[Hello World! 🌎 I hope everyone is doing well and learning new things.

When you are running an EKS cluster, you may have encountered some situations where you wanted to authorize your PODs to access AWS services. For Ex - Read/Write a file to/from S...]]></description><link>https://blogs.kratik.dev/how-to-attach-iam-roles-to-pods-in-aws-eks-cluster</link><guid isPermaLink="true">https://blogs.kratik.dev/how-to-attach-iam-roles-to-pods-in-aws-eks-cluster</guid><category><![CDATA[AWS]]></category><category><![CDATA[AWS Certified Solutions Architect Associate]]></category><category><![CDATA[Amazon Web Services]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Thu, 05 May 2022 03:47:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1650555179369/PAo4kpBQP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello World! 🌎 I hope everyone is doing well and learning new things.</p>
<p><img src="https://i.giphy.com/media/zOvBKUUEERdNm/giphy.webp" alt /></p>
<p>When you are running an <strong>EKS cluster</strong>, you may have encountered some situations where you wanted to authorize your <strong>PODs</strong> to access AWS services. For Ex - Read/Write a file to/from S3, Trigger lambda functions, etc.</p>
<p><strong>So How do you do it?</strong></p>
<p>There are multiple ways to do it -</p>
<ol>
<li><p><a target="_blank" href="https://github.com/uswitch/kiam"><strong>Kiam</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/jtblin/kube2iam"><strong>kube2iam</strong></a></p>
</li>
<li><p>Associate an IAM role with a <strong>Kubernetes service account</strong></p>
</li>
</ol>
<p>In this blog, we will be focusing on point <strong>#3</strong></p>
<hr />
<p>Out of Curiosity, Do you know what access your Pods get by default in an EKS Cluster?</p>
<p>I assume you don't know, so let's check :</p>
<p>First of all, Create a pod that has AWS CLI Installed so you can run the <code>aws sts get-caller-identity</code> command. For that apply below YAML file :</p>
<pre><code class="lang-bash">apiVersion: v1
kind: Pod
metadata:
  name: aws-cli
spec:
  containers:
    - image: amazon/aws-cli:latest
      name: aws-cli
      <span class="hljs-built_in">command</span>: [<span class="hljs-string">"sleep"</span>, <span class="hljs-string">"36000"</span>]
</code></pre>
<pre><code class="lang-bash">kubectl apply -f aws-cli-pod.yml
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651669752760/o8kfaC4ey.png" alt="image.png" /></p>
<p>Now, let's <a target="_blank" href="https://jamesdefabia.github.io/docs/user-guide/kubectl/kubectl_exec/">exec</a> into that pod and run the aws cli command to know what IAM identity is currently logged in.</p>
<pre><code class="lang-bash">kubectl <span class="hljs-built_in">exec</span> -it aws-cli -- aws sts get-caller-identity
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651670161639/yBL9wHLvt.png" alt="image.png" /></p>
<p>Oh Okay! Do you know what role this is?</p>
<p>Let's observe the <code>UserId</code></p>
<p>According to <strong>official AWS Documentation</strong> <a target="_blank" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html#policy-vars-infotouse">here</a> :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651671180241/I_1l4wuwJ.png" alt="image.png" /></p>
<p>This means this is a role assigned to an EC2. Digging it further (<a target="_blank" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids">documentation reference</a>), I got to know that the first part in the <code>UserId</code> (<strong>AROASPNKLQGHF35FHTBBB</strong>:i-0f066b892b87973af) is a unique identifier for a role. <strong>AROA</strong> <em>represents that this particular identity is a role.</em></p>
<hr />
<p>Ok, So the next question, From where this role is getting assigned and being assumed?</p>
<p><strong>Answer</strong> - When we create <strong>EKS cluster</strong>/<strong>Node Groups</strong>, either using CloudFormation, Terraform, or AWS Console, we have to assign a role to the EKS Worker Nodes, and as our Pods run on those nodes they inherit(or assume) those permissions of that role, which assigned to EKS Worker nodes.</p>
<p>AWS CloudFormation <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-nodegroup.html#cfn-eks-nodegroup-noderole">Reference</a></p>
<p>AWS Console Reference (check image below)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651670257625/Nsojd4wSq.png" alt="image.png" /></p>
<p>Terraform <a target="_blank" href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_node_group#node_role_arn">Reference</a></p>
<p>You can check more about the purpose of the EKS Node Role <a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/create-node-role.html">here</a>.</p>
<hr />
<p>Now as we saw earlier, by default, every pod will <strong>assume</strong> the IAM role which is assigned to the node, on which the pod is running.</p>
<p>Coming back to the point, I can see a way now using which any Pod can have required IAM access: I just need to <strong>add those policies to the EKS Node IAM Role</strong>, right?</p>
<p>But wait, it can work for you but NOT A GOOD PRACTICE!!! (All of your pods will be getting that access, even though they don't need that access. [ ⚠️ Violation of the <a target="_blank" href="https://en.wikipedia.org/wiki/Principle_of_least_privilege">Principle of least privilege</a>])</p>
<p>🤔 Then what should we do now to assign pod a specific role and we won't have to mess with the IAM role of EKS Nodes?</p>
<h2 id="heading-service-account-for-the-rescue">Service Account for the Rescue 😉</h2>
<p><img src="https://media2.giphy.com/media/6DJr4v3f6AXx6cfdiz/giphy.gif?cid=790b7611a04963b504d10c0fb59c2422f7793395063bcffd&amp;rid=giphy.gif" alt /></p>
<h3 id="heading-1-create-an-oidc-provider-for-your-eks-cluster">1. Create an OIDC provider for your EKS cluster</h3>
<p>I am using <code>eksctl</code>(<a target="_blank" href="https://github.com/weaveworks/eksctl">link</a>) utility here for managing and performing operations on my EKS Cluster.</p>
<pre><code class="lang-bash">eksctl utils associate-iam-oidc-provider --cluster NAME_OF_YOUR_CLUSTER --approve
</code></pre>
<p><strong>Sidenote</strong> - also check <a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html">this AWS article</a> which also includes one more step to check for existing OIDC Issuer for your cluster.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651719978275/cRuUKSipp.png" alt="image.png" /></p>
<p>*<em>Open image in New Tab if not clear!</em></p>
<h3 id="heading-2-create-the-role-you-want-to-attach-to-your-pod">2. Create the role you want to attach to your Pod</h3>
<h4 id="heading-21-create-the-json-policy-document">2.1 Create the JSON policy document</h4>
<p><code>IAM-Policy-for-listing-buckets.json</code></p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
  <span class="hljs-string">"Statement"</span>: [
    {
      <span class="hljs-string">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
      <span class="hljs-string">"Action"</span>: [
        <span class="hljs-string">"s3:ListAllMyBuckets"</span>
      ],
      <span class="hljs-string">"Resource"</span>: [
        <span class="hljs-string">"*"</span>
      ]
    }
  ]
}
</code></pre>
<h4 id="heading-22-create-the-iam-policy">2.2 Create the IAM policy</h4>
<p>Now fire up the AWS CLI Command to create your policy :</p>
<pre><code class="lang-bash">aws iam create-policy --policy-name s3-list-buckets-policy --policy-document file://IAM-Policy-for-listing-buckets.json
</code></pre>
<p>You will see something like below output :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651720212183/5mDMh1kes.png" alt="image.png" /></p>
<p>Copy the ARN of this new policy here.</p>
<h4 id="heading-23-create-an-iam-role-for-a-service-account">2.3 Create an IAM role for a service account</h4>
<p>let's run eksctl command as below and create IAM role and service account</p>
<pre><code class="lang-bash">eksctl create iamserviceaccount \
    --name NAME_OF_YOUR_SERVICE_ACCOUNT \
    --namespace NAME_OF_YOUR_K8S_NAMESPACE \
    --cluster NAME_OF_EKS_CLUSTER \
    --role-name NAME_OF_YOUR_IAM_ROLE \
    --attach-policy-arn ARN_OF_POLICY_YOU_JUST_CREATED \
    --approve \
    --override-existing-serviceaccounts
</code></pre>
<p><strong>Output</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651720955482/zQRCsz7--.png" alt="image.png" /></p>
<p>This will create a CloudFormation Stack (which will create an IAM role) and our service account which we defined in the command args.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651721223007/Wm0tKIdaZ.png" alt="image.png" /></p>
<p><em>I encourage you all to go and check the above CF template, check how the ROLE was created, and most importantly: How the role's</em> <strong><em>AssumeRolePolicyDocument</em></strong> <em>is defined.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651721655459/oXO2nhmQf.png" alt="image.png" /></p>
<p>To check what happened in our EKS Cluster :</p>
<pre><code class="lang-bash">kubectl get serviceaccounts
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651721169665/PHiEJjclb.png" alt="image.png" /></p>
<pre><code class="lang-bash">kubectl get serviceaccounts s3-service-account -o yaml
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651721201846/NYxduNkA_.png" alt="image.png" /></p>
<h3 id="heading-3-associate-the-service-account-with-your-pod-so-it-can-have-access-to-your-role">3. Associate the service account with your Pod so it can have access to your Role</h3>
<p>Do you remember, at the start of our blog, we created an <code>aws-cli</code> pod, lets tweak it a bit and see what happens now :</p>
<p>delete the old pod</p>
<pre><code class="lang-bash">kubectl delete po aws-cli
</code></pre>
<p>Update the file <code>aws-cli-pod.yml</code> as follows :</p>
<pre><code class="lang-bash">apiVersion: v1
kind: Pod
metadata:
  name: aws-cli
spec:
  containers:
    - image: amazon/aws-cli:latest
      name: aws-cli
      <span class="hljs-built_in">command</span>: [<span class="hljs-string">"sleep"</span>, <span class="hljs-string">"36000"</span>]
  serviceAccountName: s3-service-account
</code></pre>
<pre><code class="lang-bash">kubectl apply -f aws-cli-pod.yml
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651722093983/aYJ9Y7Bfh.png" alt="image.png" /></p>
<p>Now check the current logged in IAM entity in our POD :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651722160215/-qPPEmghq.png" alt="image.png" /></p>
<p>Wait, this time it is different, Also our new role is there and it is being assumed by our Pod.</p>
<p>Let's verify the access by listing out all S3 buckets :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651722192346/wG5UIHY8I.png" alt="image.png" /></p>
<p>Woohoo! That works!!!</p>
<p><img src="https://media4.giphy.com/media/l0MYMPis1gRhiYNk4/giphy.gif" alt /></p>
<p>wait let me add more excitement</p>
<p><img src="https://i.giphy.com/media/K3RxMSrERT8iI/giphy.webp" alt /></p>
<p>What do you think about this whole IAM &amp; Service Account thing? Did you learn something new?</p>
<p>Let me know in the comments. Thanks for reading.</p>
]]></content:encoded></item><item><title><![CDATA[How to set up users in Kubernetes?]]></title><description><![CDATA[When we set up Kubernetes the default config file (aka kubeconfig file) has admin privileges. This is fine when you are the only one who is going to access the cluster(still not a good practice tho!) but what if there are multiple teams/devs involved...]]></description><link>https://blogs.kratik.dev/how-to-set-up-users-in-kubernetes</link><guid isPermaLink="true">https://blogs.kratik.dev/how-to-set-up-users-in-kubernetes</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Cloud]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sat, 16 Apr 2022 06:47:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1649650210123/maqssvRSy.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When we set up <strong>Kubernetes</strong> the default config file (aka <em>kubeconfig</em> file) has admin privileges. This is fine when you are the only one who is going to access the cluster<em>(still not a good practice tho!)</em> but what if there are multiple teams/devs involved and they also need to access the cluster for some use case, obviously, they don't need the full access, So now what? Will you give them your kubeconfig file(or the access) which has full permissions? Absolutely not!</p>
<p>Have you heard about the <strong>Principle of least privilege</strong>? It dictates - <em>A subject should be given only those privileges needed for it to complete its task. If a subject does not need an access right, the subject should not have that right.</em></p>
<p>So we should create users as per the requirements and assign them the minimum permissions to function properly, right? but how do we do it?</p>
<p><img src="https://media1.giphy.com/media/xZsfgxuBCMdLU9I6Hl/giphy.gif?cid=790b7611f0c72134d804b126f6683ccfd57c0ebc86dd14d1&amp;rid=giphy.gif&amp;ct=g" /></p>
<p>Just to be clear,  Kubernetes does not have the support for users natively. And from the <a target="_blank" href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/#users-in-kubernetes">documentation</a> - <em>"Kubernetes does not have objects which represent normal user accounts. Normal users cannot be added to a cluster through an API call."</em></p>
<h3 id="heading-ways-to-set-up-users-in-k8s">Ways to set up users in K8s</h3>
<p>There are multiple ways to create/manage users(basically the AUTHENTICATION part) in Kubernetes, for example -  </p>
<ol>
<li><strong>Bearer Token</strong> - Read K8s doc <a target="_blank" href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/#static-token-file">here</a> for more details.</li>
<li><strong>Basic Auth (Username/Password)</strong> - For this, you need to start kube-apiserver with the <code>--basic-auth-file=name_of_auth_file</code> argument. This file contains list of users in <code>password</code>, <code>user-name</code>, <code>user-id</code> format. </li>
<li><strong>Auth using x509 certs</strong> - We can leverage Client-Side x509 certs to authenticate ourselves. And using RBAC we can authorize/restrict our users' access. FYI - this is what today's blog is all about.</li>
</ol>
<p><strong>Note</strong> - There are a bunch of other methods also available for the authentication in a k8s cluster. Check <a target="_blank" href="https://kubernetes.io/docs/reference/access-authn-authz/authentication">this</a> for the same.</p>
<h3 id="heading-flow">Flow</h3>
<ol>
<li>Generate a set of x509 certificate/key for a user.</li>
<li>Update the current kubeconfig file to add new user details.</li>
<li>Create an appropriate role for the user. (Role/ClusterRole)</li>
<li>Attach that role to the user. (RoleBinding/ClusterRoleBinding)</li>
</ol>
<h3 id="heading-requirements">Requirements</h3>
<ol>
<li>Working K8s Cluster (for sake of our blog post, I used <strong>(<a target="_blank" href="https://k3s.io/">k3s</a>)</strong> to quickly launch a fast &amp; lightweight k8s cluster, must check!)</li>
<li>Certificate Authority Credentials (CA Cert and CA Key to approve CSR)</li>
<li>Minimal access to apply Role &amp; Role Bindings. </li>
</ol>
<h2 id="heading-tutorial-create-a-user-in-k8s">Tutorial - Create a user in K8s</h2>
<p>We will stick to the flow which we defined earlier. Suppose we want to create a user for <strong>Kratik Jain</strong> which is a newly joined Intern in XYZ Technologies. We don't want him to make any changes in our cluster which can mess things up, so we will be giving him read-only access. Let's Start -
<img src="https://i.giphy.com/media/f6z5TkrTIBZILYOd1t/giphy.webp" /></p>
<h3 id="heading-step-1-generare-x509-certs-or-credentials">Step-1 Generare x509 Certs (or Credentials)</h3>
<p>We will be using <a target="_blank" href="https://www.openssl.org/">OpenSSL</a> for the generation and analysis of the certificates.</p>
<h4 id="heading-11-create-a-private-key-for-our-user">1.1 Create a private key for our user.</h4>
<p>Private keys are meant to be kept private and used in encryption, so only ones who have this private key can decrypt the data. but here, it is used only to identify a user.</p>
<pre><code>openssl genrsa <span class="hljs-operator">-</span>out kratik.key <span class="hljs-number">2048</span>
</code></pre><h4 id="heading-12-create-a-certificate-sign-request">1.2 Create a certificate sign request</h4>
<p>We will create a CSR for our user with our private key. Basically, it's encoded info requesting a certificate. Later we will use our Certificate Authority's credentials to create a valid certificate. Read more about <a target="_blank" href="https://www.sslshopper.com/what-is-a-csr-certificate-signing-request.html">CSRs</a> here. </p>
<p>A CSR contains various properties but for our use case, one property is most important - <strong>Common Name</strong> aka CN.  </p>
<p>Remember! using this common name, our K8s cluster will identify us. <strong>Whatever CN we 
define here, becomes our username in K8s.</strong> </p>
<p>So let's fire the below command to create a CSR -</p>
<pre><code>openssl req <span class="hljs-operator">-</span><span class="hljs-keyword">new</span> <span class="hljs-operator">-</span>key kratik.key <span class="hljs-operator">-</span>out kratik.csr <span class="hljs-operator">-</span>subj <span class="hljs-string">"/CN=kratik/O=XYZ-Technologies"</span>
</code></pre><h4 id="heading-13-use-ca-credentials-and-approve-the-csr-to-generate-the-final-certificate">1.3 Use CA Credentials and approve the CSR to generate the final Certificate</h4>
<p><strong>!Important</strong> - Locate your CA Credentials - the CA Cert and CA Key. Depending on your installation procedure, it may be stored at different locations.</p>
<p>For example, If you are using  - </p>
<ol>
<li><p><strong>K3s</strong>, you'll find your creds at <code>/var/lib/rancher/k3s/server/tls/client-ca.crt</code> &amp; <code>/var/lib/rancher/k3s/server/tls/client-ca.key</code> </p>
</li>
<li><p><a target="_blank" href="https://kind.sigs.k8s.io/"><strong>Kind</strong></a> - As kind is <em>Kubernetes in docker</em> - you need to find the control-plane Docker container and there in the path <code>/etc/kubernetes/pki/</code> you will find the <code>ca.crt</code> &amp; <code>ca.key</code>. 
<strong>Tip</strong> - Use <code>docker cp</code> command to copy those files to your local machine.</p>
</li>
<li><p><strong><a target="_blank" href="https://kubernetes.io/docs/reference/setup-tools/kubeadm/">Kubeadm</a></strong> - you will find your CA creds in <code>/etc/kubernetes/pki/</code> directory. </p>
</li>
</ol>
<p>Assuming you have these creds in the current working directory as <code>ca.crt</code> &amp; <code>ca.key</code>, fire the below command to use your CA Creds and approve the CSR, and generate the certificates.</p>
<pre><code>openssl x509 <span class="hljs-operator">-</span>req <span class="hljs-operator">-</span>in kratik.csr <span class="hljs-operator">-</span>CA ca.crt <span class="hljs-operator">-</span>CAkey ca.key <span class="hljs-operator">-</span>CAcreateserial <span class="hljs-operator">-</span>out kratik.crt <span class="hljs-operator">-</span><span class="hljs-literal">days</span> <span class="hljs-number">365</span>
</code></pre><p>Awesome! your key and cert are ready to be used! </p>
<p><img src="https://i.giphy.com/media/BPJmthQ3YRwD6QqcVD/giphy.webp" /></p>
<h3 id="heading-step-2-update-kubeconfig-to-use-new-user-creds">Step-2 Update Kubeconfig to use new user creds</h3>
<p><code>kubectl</code> uses <strong>kubeconfig</strong> files to organize information about clusters, users, namespaces, and authentication mechanisms.</p>
<h4 id="heading-21-adding-user-credentials">2.1 Adding user credentials</h4>
<pre><code>kubectl config set<span class="hljs-operator">-</span>credentials kratik <span class="hljs-operator">-</span><span class="hljs-operator">-</span>client<span class="hljs-operator">-</span>certificate<span class="hljs-operator">=</span>kratik.crt  <span class="hljs-operator">-</span><span class="hljs-operator">-</span>client<span class="hljs-operator">-</span>key<span class="hljs-operator">=</span>kratik.key
</code></pre><h4 id="heading-22-setting-a-new-context">2.2 Setting a new Context</h4>
<pre><code>kubectl config set<span class="hljs-operator">-</span>context kratik<span class="hljs-operator">-</span>context <span class="hljs-operator">-</span><span class="hljs-operator">-</span>cluster<span class="hljs-operator">=</span>default <span class="hljs-operator">-</span><span class="hljs-operator">-</span>user<span class="hljs-operator">=</span>kratik
</code></pre><p><strong>Context in Kubeconfig file</strong> - 
From the official docs, <em>"A <strong>context</strong> element in a kubeconfig file is used to group access parameters under a convenient name. Each context has three parameters: <strong>cluster, namespace, and user</strong>. By default, the kubectl command-line tool uses parameters from the <strong>current context</strong> to communicate with the cluster."</em></p>
<h4 id="heading-23-testing-kubectl-commands-to-verify-that-it-detects-our-user-and-fails">2.3 Testing kubectl commands to verify that it detects our user and fails</h4>
<p><em>this will fail because we haven't assigned any role to it yet. (Which is EXPECTED!)</em>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650089520702/6h5W0ESyn.png" alt="image.png" /></p>
<p>Notice that I am using a newly created context using the <code>--context</code> flag in the kubectl command. (BTW I am using k3s and k3 in my terminal is an alias for that )</p>
<h3 id="heading-step-3-create-a-role-that-has-fine-tuned-permissions-outlined">Step-3 Create a Role that has fine-tuned permissions outlined</h3>
<pre><code><span class="hljs-attribute">kind</span>: ClusterRole
<span class="hljs-attribute">apiVersion</span>: rbac.authorization.k8s.io/v1
<span class="hljs-attribute">metadata</span>:
  <span class="hljs-attribute">name</span>: role-for-read-only-access
<span class="hljs-attribute">rules</span>:
  - <span class="hljs-attribute">apiGroups</span>: [<span class="hljs-string">"extensions"</span>, <span class="hljs-string">"apps"</span>, <span class="hljs-string">""</span>] # <span class="hljs-string">""</span> indicates the core API group
    <span class="hljs-attribute">resources</span>: [<span class="hljs-string">"*"</span>]
    <span class="hljs-attribute">verbs</span>: [<span class="hljs-string">"get"</span>, <span class="hljs-string">"watch"</span>, <span class="hljs-string">"list"</span>]
</code></pre><pre><code>kubectl apply <span class="hljs-operator">-</span>f ro<span class="hljs-operator">-</span>role.yaml
</code></pre><h3 id="heading-step-4-attach-this-ro-role-to-our-user">Step-4 Attach this RO role to our user</h3>
<pre><code><span class="hljs-attribute">kind</span>: ClusterRoleBinding
<span class="hljs-attribute">apiVersion</span>: rbac.authorization.k8s.io/v1
<span class="hljs-attribute">metadata</span>:
  <span class="hljs-attribute">name</span>: read-only-role-binding
<span class="hljs-attribute">subjects</span>:
  - <span class="hljs-attribute">kind</span>: User
    <span class="hljs-attribute">name</span>: kratik
    <span class="hljs-attribute">apiGroup</span>: rbac.authorization.k8s.io
<span class="hljs-attribute">roleRef</span>:
  <span class="hljs-attribute">kind</span>: ClusterRole
  <span class="hljs-attribute">name</span>: role-for-read-only-access
  <span class="hljs-attribute">apiGroup</span>: rbac.authorization.k8s.io
</code></pre><pre><code>kubectl apply <span class="hljs-operator">-</span>f ro<span class="hljs-operator">-</span>role<span class="hljs-operator">-</span>binding.yaml
</code></pre><h3 id="heading-step-5-testing-the-access">Step-5 Testing the access</h3>
<p>Fire the same command which we fired previously to check if the error is gone or not.</p>
<pre><code>kubectl <span class="hljs-operator">-</span><span class="hljs-operator">-</span>context<span class="hljs-operator">=</span>kratik<span class="hljs-operator">-</span>context get po <span class="hljs-operator">-</span>A
</code></pre><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650090208250/f1y2bFIak.png" alt="image.png" /></p>
<p>Also, Let's try deleting the pod, let's see what happens 🤔</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650090963034/7A_wWU6nR.png" alt="image.png" /></p>
<p>So this worked out finally as we wanted!!! </p>
<p><img src="https://media4.giphy.com/media/4SU2Cf0g3zPDG/giphy.gif?cid=790b76114def256494b790e76b3f6964ab0449a775ca16be&amp;rid=giphy.gif&amp;ct=g" /></p>
<p>Now you can also tweak and fine-tune your Cluster role to give more granular access. I wanted to create something which is not bounded to some namespace but is usable throughout the cluster, so I chose the <strong>ClusterRole</strong>.</p>
<h2 id="heading-references">References</h2>
<ol>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/Principle_of_least_privilege">Wikipedia - Principle of least privilege</a></li>
<li><a target="_blank" href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/">Kubernetes - RBAC</a></li>
<li><a target="_blank" href="https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig">Kubeconfig file</a></li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Update ConfigMap without restarting Pods in Kubernetes]]></title><description><![CDATA[This blog is basically a tip around Kubernetes Administration.
I am assuming you know about and have some experience in Kubernetes & ConfigMaps.
Anyways I will try to explain concepts as easy as possible 😀 

This is how I used to feel when I started...]]></description><link>https://blogs.kratik.dev/update-configmap-without-restarting-pods-in-kubernetes</link><guid isPermaLink="true">https://blogs.kratik.dev/update-configmap-without-restarting-pods-in-kubernetes</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Docker]]></category><category><![CDATA[containers]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sun, 27 Feb 2022 17:13:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645959718665/I1U5RQHGY.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>This blog is basically a tip around Kubernetes Administration.</em></p>
<p>I am assuming you know about and have some experience in Kubernetes &amp; ConfigMaps.</p>
<p>Anyways I will try to explain concepts as easy as possible 😀 </p>
<hr />
<p>This is how I used to feel when I started : 
<img src="https://media1.giphy.com/media/kaq6GnxDlJaBq/giphy.gif?cid=790b76111bacced6c43e9192131284b8a76e057fcb996616&amp;rid=giphy.gif&amp;ct=g" /></p>
<p>So Don't worry, Let's start improving ourselves, no matter how much but consistently 👍</p>
<hr />
<h3 id="heading-when-we-use-configmaps">When We use ConfigMaps?</h3>
<p>When you want to supply environment-specific config values like env vars.</p>
<p>Using ConfigMaps, You can also mount your config as files in a particular directory which can be later read by our application. </p>
<p>So, In Short, using ConfigMaps, you can supply configs by : </p>
<ol>
<li>Setting up env vars</li>
<li>Mounting the config values as a file (aka data volume).</li>
</ol>
<h3 id="heading-problem-statement">Problem Statement</h3>
<p><strong>You updated a ConfigMap, Now you want to propagate this updated change to all existing running Pods.</strong>  </p>
<h3 id="heading-options">Options</h3>
<h4 id="heading-option-1">Option #1</h4>
<p><strong>You can delete existing pods or restart the deployment rollout.</strong> </p>
<p>But You know what this is not feasible in some cases where you don't want any downtime or you simply just don't want to delete any running Pods.</p>
<p><strong>Note</strong> - if you are using ConfigMaps to set <strong>env vars</strong> then <strong>this is the only option you have.</strong> </p>
<h4 id="heading-option-2">Option #2</h4>
<p>This option is only applicable if you have mounted your ConfigMap as a file in your Pod. Also if you are using ConfigMap as a <a target="_blank" href="https://kubernetes.io/docs/concepts/storage/volumes/#using-subpath">subPath</a> volume, this option won't be applicable.</p>
<p>So Let's discuss this option in detail.</p>
<h3 id="heading-the-option-2">The Option #2</h3>
<p>This is not something which I have invented, rather I read this on the Official K8s docs page <a target="_blank" href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#mounted-configmaps-are-updated-automatically">here</a></p>
<p>Actually, there are two ways you can achieve this, </p>
<h4 id="heading-1-use-annotation">1. Use Annotation</h4>
<p>According to the docs I mentioned above, You can update the pod's annotations which will trigger a refresh and your ConfigMap data volume will be populated with the latest data. Simple! 👀</p>
<h4 id="heading-2-wait-for-kubelet-to-refresh-configmap-data">2. Wait for Kubelet to refresh ConfigMap data</h4>
<p>If configured, Kubelet also tries to refresh the ConfigMaps for pods periodically to keep things in sync. This interval is called <strong>ConfigMap Cache TTL</strong></p>
<p><strong>Extra</strong> - There is one property called <strong>ConfigMapAndSecretChangeDetectionStrategy</strong> which you can configure when you start Kubelet. Possible values we can pass to this are : <strong>Get</strong>, <strong>Cache</strong> &amp; <strong>Watch</strong> (Default)
This property defines how we want Kubelet to detect and propagate ConfigMap and Secret changes.</p>
<p>More info : <a target="_blank" href="https://kubernetes.io/docs/concepts/configuration/configmap/#mounted-configmaps-are-updated-automatically">ConfigMaps | Kubernetes</a>.</p>
<hr />
<p>I hope this quick &amp; short blog made sense :) </p>
<p><img src="https://media1.giphy.com/media/t2eBr71ACeDC0/giphy.gif?cid=790b7611673694a4fbbad457fa36c78a6018d027890e7a5b&amp;rid=giphy.gif&amp;ct=g" /></p>
<hr />
<h3 id="heading-terminology-used">Terminology Used</h3>
<p><strong>Kubernetes</strong> - Kubernetes is an open-source container orchestration system for automating software deployment, scaling, and management. </p>
<p><strong>Containers</strong> - A container is a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another.</p>
<p><strong>Pods</strong> - Pods are the smallest, most basic deployable objects in Kubernetes. Pods contain one or more containers. </p>
<p><strong>ConfigMaps</strong> - A ConfigMap allows you to decouple environment-specific configuration (mostly environment variables) from your container images so that your applications are easily portable.</p>
<hr />
<h3 id="heading-references">References</h3>
<ol>
<li>https://en.wikipedia.org/wiki/Kubernetes </li>
<li>https://www.docker.com/resources/what-container</li>
<li>https://kubernetes.io/docs/concepts/configuration/configmap/</li>
<li>https://kind.sigs.k8s.io/docs/user/quick-start/</li>
<li>https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#mounted-configmaps-are-updated-automatically</li>
<li>https://kubernetes.io/docs/concepts/configuration/configmap/#mounted-configmaps-are-updated-automatically</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[What are CMD and ENTRYPOINT in Docker?]]></title><description><![CDATA[Let's Start
When I was writing Dockerfiles initially, I found CMD and ENTRYPOINT very confusing to me. I assume there will be folks out there who are still not so sure about these two syntaxes and their use cases.
So let's try to understand the conce...]]></description><link>https://blogs.kratik.dev/cmd-and-entrypoint-in-docker</link><guid isPermaLink="true">https://blogs.kratik.dev/cmd-and-entrypoint-in-docker</guid><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[docker images]]></category><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sun, 30 Jan 2022 13:58:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1643517848013/xuJPmx9nO.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-lets-start">Let's Start</h2>
<p>When I was writing <strong>Dockerfiles</strong> initially, I found CMD and ENTRYPOINT very confusing to me. I assume there will be folks out there who are still not so sure about these two syntaxes and their use cases.</p>
<p>So let's try to understand the concept in depth for once and all.    </p>
<iframe src="https://i.giphy.com/media/6zwCn1vbgaYZDVnQt2/giphy.webp" width="480" height="399" class="giphy-embed"></iframe>

<hr />
<p>The best way to understand a tool or technology is to go to their official documentation and start digging.</p>
<p>In our case, you can follow this link of <strong>Docker Official Documentation</strong> - https://docs.docker.com/engine/reference/builder/#entrypoint   </p>
<p>This is what you will see : 
<kbd>
  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643524037149/kULuxpuhB.png" />
</kbd></p>
<p>But did you notice the terms - <code>exec form</code> and <code>shell form</code>? </p>
<hr />
<h2 id="heading-exec-and-shell-form">Exec and Shell form</h2>
<h3 id="heading-what-is-exec-form">What is exec form?</h3>
<p>In this form, you have to pass the executable and all required params as JSON Array. </p>
<p>The exec form makes it possible to avoid <em>shell string munging</em> and to <code>RUN</code> commands using a base image <em>that does not contain the specified shell executable</em>.</p>
<p>Note that as it is a JSON array, make sure you use double quotes, not single quotes. </p>
<p>Example : </p>
<pre><code><span class="hljs-selector-tag">ENTRYPOINT</span> <span class="hljs-selector-attr">[<span class="hljs-string">"/usr/sbin/apache2ctl"</span>, <span class="hljs-string">"-D"</span>, <span class="hljs-string">"FOREGROUND"</span>]</span>
</code></pre><p>Here </p>
<ul>
<li>We are avoiding any shell parsing and directly using the executable which is  <code>apache2ctl</code></li>
<li>We are not invoking any shell to run the binary of our program. </li>
</ul>
<p><strong>This form is recommended over <code>shell form</code></strong> as shell form can cause troubles which make our application inside the container not receive signals properly. </p>
<p><strong>Special Case</strong> - In exec form, docker can substitute env variable if set by <code>ENV</code> instruction in <code>Dockerfile</code>. </p>
<pre><code><span class="hljs-keyword">FROM</span> alpine:latest

ENV VERSION=v1<span class="hljs-number">.2</span><span class="hljs-number">.0</span>

RUN ["echo", "$VERSION"]
</code></pre><p>This will echo <strong>v1.2.0</strong> as docker does the substitution for us. </p>
<h3 id="heading-what-is-shell-form">What is shell form?</h3>
<p>This is a fairly simple form which we can use, as We don't have to follow any special notation - we just have to write exactly as we run commands on our terminals with shell in our day-to-day life. </p>
<p>Example:</p>
<pre><code><span class="hljs-attribute">ENTRYPOINT</span> apache<span class="hljs-number">2</span>ctl -D FOREGROUND
</code></pre><p>Here</p>
<ul>
<li>Shell Processing and Environment Variable parsing can happen. </li>
<li>Our binary is not executed directly, first, a shell is invoked with <code>/bin/sh -c</code> and then our executable is started in that shell. </li>
<li>You can use a \ (backslash) to continue a single RUN instruction onto the next line. </li>
<li>The <code>SHELL</code> instruction allows the default shell (<code>/bin/sh</code>) used for the shell form of commands to be overridden. </li>
</ul>
<h3 id="heading-differencecomparison">Difference/Comparison</h3>
<p><strong>Basic comparison from <a target="_blank" href="https://docs.docker.com/engine/reference/builder/#run">the documentation</a> :</strong> 
Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, <code>RUN [ "echo", "$HOME" ]</code> will not do variable substitution on <code>$HOME</code>. If you want shell processing then either use the shell form or execute a shell directly, for example:  <code>RUN [ "sh", "-c", "echo $HOME"]</code></p>
<h3 id="heading-when-to-use">When to Use?</h3>
<h4 id="heading-exec-form">Exec Form</h4>
<ol>
<li>When You don't want any shell features like env var substitution, I/O Redirection, piping, chaining multiple commands, etc.</li>
<li>When You want to forward process signals to child processes. Ex - Pressing <strong>Ctrl + C</strong>(SIGINT) to stop the process.</li>
<li>Recommended for CMD &amp; ENTRYPOINT in Dockerfile.    </li>
</ol>
<h4 id="heading-shell-form">Shell Form</h4>
<ol>
<li>When you want to use shell features as mentioned above. (Piping, Command chaining using a backslash, I/O Redirection, Shell Variables substitution, etc. )</li>
<li>It is extremely useful with <code>RUN</code> instructions in Dockerfile.  </li>
</ol>
<hr />
<p>As we cleared some basic concepts, Now, Back to the original question: <strong>CMD vs ENTRYPOINT</strong>?</p>
<h2 id="heading-cmd">CMD</h2>
<ul>
<li>It sets the default parameter for a container. </li>
<li>It can be overridden easily while running a container with the <code>docker run</code> command.</li>
<li>If you don't provide any executable in the <code>CMD</code>, it will pass itself as the parameter
to the <code>entrypoint</code>.</li>
</ul>
<h2 id="heading-entrypoint">ENTRYPOINT</h2>
<ul>
<li>When you want to use your container as executable. Example - See below <code>Dockerfile</code></li>
</ul>
<pre><code><span class="hljs-selector-tag">FROM</span> <span class="hljs-selector-tag">alpine</span>

<span class="hljs-selector-tag">ENTRYPOINT</span> <span class="hljs-selector-attr">[<span class="hljs-string">"echo"</span>, <span class="hljs-string">"Hello"</span>]</span>

<span class="hljs-selector-tag">CMD</span> <span class="hljs-selector-attr">[<span class="hljs-string">"World!"</span>]</span>
</code></pre><p><strong>See in Terminal:</strong> (running above Docker Image)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643549014450/RUQSch9ay.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643548995439/u2Y22ATwP.png" alt="image.png" /></p>
<p><em>Here you can see that I am passing some parameters and My container is behaving according to that!</em></p>
<ul>
<li><p>By implementing <code>ENTRYPOINT</code>, we are implicitly specifying that this container is made for some specific use case.</p>
</li>
<li><p>When we run the container with <code>docker run</code>, all the params are appended to <code>ENTRYPOINT</code></p>
</li>
</ul>
<hr />
<h2 id="heading-how-to-use-cmdentrypoint-with">How to Use CMD/ENTRYPOINT with :</h2>
<h3 id="heading-docker-run-command"><code>docker run</code> command</h3>
<h4 id="heading-cmd">CMD</h4>
<pre><code>docker run <span class="hljs-operator">-</span>it <span class="hljs-operator">&lt;</span>NAME_OF_DOCKER_IMAGE<span class="hljs-operator">&gt;</span> param1
</code></pre><p>Here param1 is equivalent to CMD instruction. </p>
<h4 id="heading-entrypoint">ENTRYPOINT</h4>
<pre><code>docker run <span class="hljs-operator">-</span>it <span class="hljs-operator">-</span><span class="hljs-operator">-</span>entrypoint <span class="hljs-operator">/</span>path<span class="hljs-operator">/</span>of<span class="hljs-operator">/</span>entrypoint<span class="hljs-operator">/</span>executable <span class="hljs-operator">&lt;</span>NAME_OF_DOCKER_IMAGE<span class="hljs-operator">&gt;</span> param1
</code></pre><p>Here we are specifying some other entrypoint executable. </p>
<h3 id="heading-docker-compose">docker-compose</h3>
<pre><code><span class="hljs-attribute">version</span>: <span class="hljs-string">'3'</span>

<span class="hljs-attribute">services</span>:
  <span class="hljs-attribute">web</span>:
    <span class="hljs-attribute">image </span>: repo/<span class="hljs-attribute">my-web-server</span>:v2
    <span class="hljs-attribute">entrypoint</span>: [<span class="hljs-string">"python3"</span>, <span class="hljs-string">"manage.py"</span>]
    <span class="hljs-attribute">command</span>: [<span class="hljs-string">"runserver"</span>]
    <span class="hljs-attribute">ports</span>:
      - <span class="hljs-string">"8000:8000"</span>
</code></pre><h3 id="heading-kubernetes">Kubernetes</h3>
<pre><code><span class="hljs-attribute">apiVersion</span>: v1
<span class="hljs-attribute">kind</span>: Pod
<span class="hljs-attribute">metadata</span>:
  <span class="hljs-attribute">name</span>: command-demo
  <span class="hljs-attribute">labels</span>:
    <span class="hljs-attribute">purpose</span>: demonstrate-command
<span class="hljs-attribute">spec</span>:
  <span class="hljs-attribute">containers</span>:
  - <span class="hljs-attribute">name</span>: command-demo-container
    <span class="hljs-attribute">image</span>: debian
    <span class="hljs-attribute">command</span>: [<span class="hljs-string">"printenv"</span>]
    <span class="hljs-attribute">args</span>: [<span class="hljs-string">"HOSTNAME"</span>, <span class="hljs-string">"KUBERNETES_PORT"</span>]
  <span class="hljs-attribute">restartPolicy</span>: OnFailure
</code></pre><p>Here <code>command</code> corresponds to <code>ENTRYPOINT</code> &amp; <code>args</code> corresponds to <code>CMD</code> in Dockerfile. </p>
<hr />
<p>This is how I feel when I am comfortable with these small concepts with additional knowledge ;) </p>
<p> <img src="https://media1.giphy.com/media/GS9pfaxQj5hPKFGGp8/giphy.gif?cid=ecf05e47lbbmqxkaqwxputzzy4mt8ip9ac6mvwb5ynauvkjv&amp;rid=giphy.gif&amp;ct=g" /></p>
<hr />
<h2 id="heading-related-articles">Related Articles</h2>
<ol>
<li>https://docs.docker.com/engine/reference/builder/#run</li>
<li>https://docs.docker.com/engine/reference/builder/#entrypoint </li>
<li>https://hynek.me/articles/docker-signals/</li>
<li>https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/ </li>
</ol>
<hr />
<p>Thanks for reading! Hope it adds something to your knowledge base. </p>
<p>Let me know if you have any feedback. 🙏 </p>
<p>- Kratik</p>
]]></content:encoded></item><item><title><![CDATA[Best Solution for Collecting Logs/Metrics for AWS EC2]]></title><description><![CDATA[🪔🪔🪔🪔🪔🪔🪔
Problems -
Suppose you have a web server, deployed on AWS EC2. Now to check your app logs you have to SSH to that EC2 and then cat the logs.
Or when you wanted to check some metrics for that EC2, you went to the built-in CloudWatch met...]]></description><link>https://blogs.kratik.dev/using-cloudwatch-agent-for-logs-and-metrics</link><guid isPermaLink="true">https://blogs.kratik.dev/using-cloudwatch-agent-for-logs-and-metrics</guid><category><![CDATA[ec2]]></category><category><![CDATA[logging]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sat, 06 Nov 2021 10:07:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1636170420739/z7rG4Wkmi.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>🪔🪔🪔🪔🪔🪔🪔</p>
<h3 id="heading-problems">Problems -</h3>
<p>Suppose you have a web server, deployed on AWS EC2. Now to check your app logs you have to SSH to that EC2 and then <code>cat</code> the logs.</p>
<p>Or when you wanted to check some metrics for that EC2, you went to the built-in CloudWatch metrics section on the EC2 dashboard page aka Monitoring. You checked the CPU utilization... that's great! but wait, now you wanted to check current RAM usage or Swap memory usage or Disk Usage. </p>
<p>Wait, what? What did you observe? There are no metrics available for RAM/Swap/Disk usage? Now what?</p>
<p><img src="https://i.giphy.com/media/BY8ORoRpnJDXeBNwxg/giphy.webp" /></p>
<hr />
<h3 id="heading-make-a-wish">Make a Wish🌠</h3>
<p>I wish I had some simple solution🤞, using which I can check my logs and unlock a few more metrics that are not available to CloudWatch metrics by default, like how much space is left in my EBS volume or how much RAM is being utilized.</p>
<hr />
<h3 id="heading-the-solution">The Solution</h3>
<p>Don't worry! The unified CloudWatch Agent is here for the rescue!</p>
<h4 id="heading-features">Features</h4>
<ul>
<li>Collect internal system-level metrics from Amazon EC2 instances across operating systems.</li>
<li>Collect system-level metrics from on-premises servers.</li>
<li>Retrieve custom metrics from your applications or services using the StatsD and collectd protocols.</li>
<li>Collect logs from Amazon EC2 instances and on-premises servers, running either Linux or Windows Server.</li>
</ul>
<hr />
<h3 id="heading-how-to-set-up-cloudwatch-agent-for-logsmetrics-collection">How to set up Cloudwatch Agent for Logs/Metrics Collection?</h3>
<h4 id="heading-assumption">Assumption</h4>
<p>I am assuming that you have a web server that serves a beautiful web app something like below (<em>and it is generating some access and error logs</em>):
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636181592742/CXuS-n2Xb.png" alt="image.png" /></p>
<p><strong>Note</strong> - <em>If Some Images are not clear due to big dimensions, just open them in a new tab, They will become the full-size image and you will be able to see the content clearly. The Images which I think need to open in a new tab once, I have made them clickable explicitly! So Enjoy</em> :)</p>
<h4 id="heading-installation-of-cw-agent">Installation of CW Agent</h4>
<p>Install the agent using the command line for your OS. FYI, I am using Ubuntu 20.04. Head over to this AWS  <a target="_blank" href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/download-cloudwatch-agent-commandline.html">Documentation</a> and follow the steps.</p>
<ol>
<li><p>Download the DEB Package</p>
<pre><code><span class="hljs-attribute">wget</span> https://s<span class="hljs-number">3</span>.amazonaws.com/amazoncloudwatch-agent/debian/amd<span class="hljs-number">64</span>/latest/amazon-cloudwatch-agent.deb
</code></pre></li>
<li><p>Install the package</p>
<pre><code>sudo dpkg <span class="hljs-operator">-</span>i <span class="hljs-operator">-</span>E ./amazon<span class="hljs-operator">-</span>cloudwatch<span class="hljs-operator">-</span>agent.deb
</code></pre></li>
</ol>
<h4 id="heading-setting-up-iam-permissions">Setting up IAM Permissions</h4>
<p>No Matter If you using AWS EC2 or an On-Prem Server, you need to provide proper permissions using IAM so the machine can access and post metrics/logs to Cloudwatch! </p>
<p>For now, Let's take an example where we are using EC2, for on-prem servers, you can go through this  <a target="_blank" href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/create-iam-roles-for-cloudwatch-agent-commandline.html">documentation</a> which will give you an idea around setup. </p>
<ol>
<li><p>Create an IAM role. Select <strong>AWS Service</strong>, then <strong>EC2</strong> and click on <strong>Next</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636182801098/ObrTqwe3J.png" alt="image.png" /></p>
</li>
<li><p>In Attach Permissions, select a managed policy named <strong>CloudWatchAgentServerPolicy</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636182883367/VON5NCWfu.png" alt="image.png" /></p>
</li>
<li><p><strong>Bonus</strong> - Later on, you will be creating a configuration file for CloudWatch, best practice here is that you save this config to AWS Systems Manager in the <strong>Parameter Store</strong>. You can use it later, also, you can modify it as per your needs. So for that, you need to add permissions to access SSM Param Store. Attach the policy for that as shown below.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636184828345/Cl1iULByX.png" alt="image.png" /></p>
</li>
<li><p>Click Next, Supply some apt name for it and Finish the creation of Role.</p>
</li>
<li><p>Now, Go to EC2 Dashboard, select your EC2, Right-click for options, Select <strong>Security</strong>, then <strong>Modify IAM role</strong> and attach this newly created role.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636183529273/POEh7Uruc.png" alt="image.png" />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636183500501/3qFl_Mg20.png" alt="image.png" /></p>
<h4 id="heading-create-cloudwatch-agent-configuration-file">Create CloudWatch agent configuration file</h4>
<p>We have to instruct the CloudWatch agent that from where to fetch logs, what metrics to scrap, etc. For that, we need to create/generate the config file. We can do it manually or we can generate the file using a wizard. (Recommended) Follow <a target="_blank" href="Link">this</a>  Docs to get started.</p>
<p>Before that, let's verify that our role is attached to our EC2!</p>
<ol>
<li><p>Install AWS CLI  </p>
<pre><code>sudo apt<span class="hljs-operator">-</span>get update <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> sudo apt<span class="hljs-operator">-</span>get <span class="hljs-operator">-</span>y install awscli
</code></pre></li>
<li><p>Check what is the current entity, which is attached to our EC2 </p>
<pre><code>aws sts <span class="hljs-keyword">get</span>-caller-<span class="hljs-keyword">identity</span>
</code></pre></li>
<li><p>If You get some output like below, you are good to go, else recheck all the steps!
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636185234369/RPZWeNCIB.png" alt="image.png" /></p>
</li>
<li><p>Run the wizard by running the following command:</p>
<pre><code>sudo <span class="hljs-operator">/</span>opt<span class="hljs-operator">/</span>aws<span class="hljs-operator">/</span>amazon<span class="hljs-operator">-</span>cloudwatch<span class="hljs-operator">-</span>agent<span class="hljs-operator">/</span>bin<span class="hljs-operator">/</span>amazon<span class="hljs-operator">-</span>cloudwatch<span class="hljs-operator">-</span>agent<span class="hljs-operator">-</span>config<span class="hljs-operator">-</span>wizard
</code></pre><p>I have used <a target="_blank" href="https://asciinema.org">asciinema</a> to record what parameters it asks, you can follow the same to make it work! I have chosen advanced metrics with a high-resolution time frame. + <strong>For the log part</strong>, I have to give the path of my log file which will be read and the content of it will be sent to CloudWatch. You can check these logs in the CloudWatch Logs later. If you see the recording below you'll see I have put the name of CloudWatch Log Group as <code>access.log</code> 
<a target="_blank" href="https://asciinema.org/a/EERnshRCVBWLpnJjehwi7dnfO"><img src="https://asciinema.org/a/EERnshRCVBWLpnJjehwi7dnfO.svg" alt="asciicast" /></a></p>
</li>
<li><p>That was not it :) Once you have done start the agent using the <code>amazon-cloudwatch-agent-ctl</code> utility : </p>
<pre><code>sudo <span class="hljs-operator">/</span>opt<span class="hljs-operator">/</span>aws<span class="hljs-operator">/</span>amazon<span class="hljs-operator">-</span>cloudwatch<span class="hljs-operator">-</span>agent<span class="hljs-operator">/</span>bin<span class="hljs-operator">/</span>amazon<span class="hljs-operator">-</span>cloudwatch<span class="hljs-operator">-</span>agent<span class="hljs-operator">-</span>ctl <span class="hljs-operator">-</span>a fetch<span class="hljs-operator">-</span>config <span class="hljs-operator">-</span>m ec2 <span class="hljs-operator">-</span>c ssm:AmazonCloudWatch<span class="hljs-operator">-</span>linux <span class="hljs-operator">-</span>s
</code></pre><p><a target="_blank" href="https://cdn.hashnode.com/res/hashnode/image/upload/v1636206905023/Ld9lX79QY.png"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636207135446/kbvtNcXH3.png" alt="image.png" /></a>
<strong>Output</strong>
<a target="_blank" href="https://cdn.hashnode.com/res/hashnode/image/upload/v1636186946469/S7r3FDupj.png"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636186946469/S7r3FDupj.png" alt="image.png" /></a></p>
</li>
<li><p>I got some errors when I fired the above command, a file called <code>types.db</code> was missing in the <code>collectd</code> directory! Create both the things :</p>
<pre><code>sudo mkdir <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>share<span class="hljs-operator">/</span>collectd<span class="hljs-operator">/</span>
sudo touch <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>share<span class="hljs-operator">/</span>collectd<span class="hljs-operator">/</span>types.db
</code></pre></li>
<li><p>Let's Re-run the agent : </p>
<pre><code>sudo <span class="hljs-operator">/</span>opt<span class="hljs-operator">/</span>aws<span class="hljs-operator">/</span>amazon<span class="hljs-operator">-</span>cloudwatch<span class="hljs-operator">-</span>agent<span class="hljs-operator">/</span>bin<span class="hljs-operator">/</span>amazon<span class="hljs-operator">-</span>cloudwatch<span class="hljs-operator">-</span>agent<span class="hljs-operator">-</span>ctl <span class="hljs-operator">-</span>a fetch<span class="hljs-operator">-</span>config <span class="hljs-operator">-</span>m ec2 <span class="hljs-operator">-</span>c ssm:AmazonCloudWatch<span class="hljs-operator">-</span>linux <span class="hljs-operator">-</span>s
</code></pre><p><a target="_blank" href="https://cdn.hashnode.com/res/hashnode/image/upload/v1636187213983/VJKaJuQoC.png"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636187377628/kfyXr7IJ8.png" alt="image.png" /></a>
From Above Screenshot, I think you can conclude that the run was successful.</p>
</li>
<li><p>Let's verify in the CloudWatch, Check logs. In AWS Console, Go To <strong>Cloudwatch</strong> &gt; <strong>log groups</strong> 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636191888718/mO3CvYRSD.png" alt="image.png" />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636191930903/L8y1QiqyY.png" alt="2021-11-06_15-14.png" /> 
You can see that our log group is created and logs are also there :) </p>
</li>
<li><p>Let's Verify the Metrics, Go To <strong>Cloudwatch</strong> &gt; <strong>Metrics</strong> &gt; <strong>All Metrics</strong>. You'll see something like this: 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636192022340/dDqwsYgpR.png" alt="image.png" />
This means that metrics scraped by CW Agents are available.</p>
</li>
</ol>
<p>Open <strong>CWAgent</strong> Metrics and select below metrics group :
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636192400481/0s-Wl-Byg.png" alt="image.png" /></p>
<p>From Here, select the row which have <code>mem_used_percent</code>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636192483857/nOvAh1_Om.png" alt="image.png" /></p>
<p>You will be able to see the graph of Memory Usage
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636192593000/COEVcnYmW.png" alt="image.png" /></p>
<hr />
<p>Now as you have both, App logs and Advanced Metrics (which are not enabled by default in CloudWatch), Possibilities are infinite. You can create your CloudWatch alarms based on these metrics. You can keep an eye on logs efficiently without logging in to the server.   </p>
<p><img src="https://i.giphy.com/media/lTZvj21tbQSTC/giphy.webp" /> </p>
<hr />
<h4 id="heading-extra-why-aws-cloudwatch-does-not-have-memory-usage-metrics">Extra - Why AWS CloudWatch does not have Memory usage metrics</h4>
<p>The default metrics which AWS provides come from the Hypervisor level so we don't have to do anything special to see those. But for some metrics like Memory Usage, Disk Usage you have to get those metrics from the OS level. That's why we had to install an agent on the OS. </p>
<hr />
<h4 id="heading-references">References:</h4>
<ol>
<li><p>AWS Official Documentation - <a target="_blank" href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html">Amazon CloudWatch</a></p>
</li>
<li><p>Installing the CloudWatch agent -  <a target="_blank" href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/installing-cloudwatch-agent-commandline.html">AWS Docs</a> </p>
</li>
</ol>
<hr />
<p>Drop Some Emojis if you find this useful. Or leave a comment, That's how I will know what I can improve :) </p>
<p>Thanks for Reading! 🙇‍♂️</p>
<hr />
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik">Wanna buy me a Coffee</a> ☕ ?</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623604053124/HDvapUuoW.jpeg" alt="buy-me-a-coffee.jpeg" /></a></p>
]]></content:encoded></item><item><title><![CDATA[How to prevent users from downloading files using S3 URLs]]></title><description><![CDATA[Suppose you have some assets for your platform which is being served to your customers in form of MP4 videos, PDFs, Etc.
I am assuming that data is important but not that sensitive, so this guide can be applicable for some specific use cases. 
For ea...]]></description><link>https://blogs.kratik.dev/prevent-users-to-download-files-using-s3-urls</link><guid isPermaLink="true">https://blogs.kratik.dev/prevent-users-to-download-files-using-s3-urls</guid><category><![CDATA[AWS]]></category><category><![CDATA[Amazon S3]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sun, 17 Oct 2021 06:48:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1634317354632/Fj4Boauo8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Suppose you have some assets for your platform which is being served to your customers in form of MP4 videos, PDFs, Etc.</p>
<p><em>I am assuming that data is important but not that sensitive, so this guide can be applicable for some specific use cases.</em> </p>
<p>For ease of storing and to save some <code>$$$</code>, you chose to host your content on an AWS S3 bucket. That way you will also be utilizing <a target="_blank" href="https://aws.amazon.com/about-aws/global-infrastructure/">AWS Global Infrastructure</a>  to make sure your assets are highly available.</p>
<hr />
<p>Let's See, How You created Your Bucket - </p>
<p>Enter a suitable name for your bucket :
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634393717977/NHBeFrp99.png" alt="2021-10-16_18-24_1.png" /></p>
<p>Select an Encryption Method (To enable Encryption at Rest) :
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634393757852/196MtlCmF.png" alt="2021-10-16_18-25_2.png" /></p>
<p>and leave everything as it is. Now proceed to create your new shiny bucket.</p>
<p>Create a folder for storing your assets, for example - <code>course-videos</code> + maintain the encryption scheme.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634394002721/afJjbHhiv.png" alt="image.png" /></p>
<p>Upload the content, for now, I am uploading two videos, see below :</p>
<p><a target="_blank" href="https://cdn.hashnode.com/res/hashnode/image/upload/v1634439073528/3T52Yxj_U.png"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634439073528/3T52Yxj_U.png" alt="Image.png" /></a></p>
<hr />
<p>Now, If you try to open your video in the browser using that S3 object URL, you'll see something like this: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634439318646/5ceyixALd.png" alt="image.png" /></p>
<p>and, that is totally fine, by default, your bucket is private and objects can not be accessed directly.</p>
<p>So now what? Ok, let's try to make the objects public and see what happens 🤔</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634440526406/h4rtqDhpB.png" alt="image.png" /></p>
<p><a target="_blank" href="https://cdn.hashnode.com/res/hashnode/image/upload/v1634440127469/xDCyeSqcA.png"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634440127469/xDCyeSqcA.png" alt="image.png" /></a></p>
<blockquote>
<p><em>Note - Click the image to enlarge</em></p>
</blockquote>
<hr />
<p>So If you were unable to understand, let me help you...
Did you observe a checklist (aka <strong>Block public access (bucket settings)</strong>) while creating the bucket?</p>
<p> Which look something like this :
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634447606202/L4pNK-Thb.png" alt="2021-10-16_18-25_1.png" /></p>
<p>So the thing is this checklist makes sure that all public access to the bucket and its objects is denied!</p>
<p>But for our use case, we need our assets to be public so our users can consume them.</p>
<p>So let's make our objects public now... For that go to your <strong>BUCKET</strong> &gt; <strong>Permissions</strong> &gt; <strong>Block public access (bucket settings)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634449052158/zMqH36tuY.png" alt="image.png" /></p>
<p>Here, uncheck all the settings as I have shown in the image above.</p>
<p>Now, try again to make that folder public for our users. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634449293321/49uZt8Lm2.png" alt="2021-10-17_08-45.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634449261246/UdekqoMzWJ.png" alt="image.png" /></p>
<p>Woohoo! this time we were able to make our objects public.</p>
<p><img src="https://i.giphy.com/media/o75ajIFH0QnQC3nCeD/giphy.webp" /></p>
<hr />
<p><em>I'd recommended you to go through  <a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html#access-control-block-public-access-options">this</a> once to understand better what the <strong>Block public access settings</strong> offers.</em></p>
<hr />
<p>Now let's add these URLs to your platform, I have created a Codepen where I will be referring these videos for demo purposes.</p>
<p>https://codepen.io/k4kratik/pen/NWvxowL?editors=1000 </p>
<p>HTML Source : https://www.geeksforgeeks.org/html5-video/ </p>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634449952257/Kxjr0GDWC.png" alt="image.png" /></p>
<p>When you add the S3 Object URL to your platform, users are able to see the content, Great!</p>
<p><img src="https://i.giphy.com/media/a0h7sAqON67nO/giphy.webp" /></p>
<p>💡For those who don't know, you can click on any object in S3 and from there you can copy the object URL.</p>
<hr />
<h2 id="heading-the-problem">The Problem</h2>
<p>Wait what, you thought that was it? 😂
No! Let's see what's the issue here.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634450471684/-zkMsijve.gif" alt="ezgif-7-c4d1c5b01880.gif" /></p>
<p>Keeping objects as <code>public</code> does not mean anyone should be able to download these objects so easily!  We made those objects public so our users on the Internet can access it for viewing, surely not for downloading! 😤</p>
<hr />
<h2 id="heading-the-workaround">The Workaround</h2>
<p>Yup! You read the title right! It is not a solution but a workaround that can be effective for most of the users who can try to download your content! 😜 </p>
<p> So even before we start, I want to summarize what we are gonna do - <em>We will make use of <strong>Bucket Policies</strong>, We will whitelist your platform's domain name so only your platform's webpages can access your assets.</em></p>
<h3 id="heading-step-1-change-block-public-access-bucket-settings">Step-1: Change Block public access (bucket settings)</h3>
<p>Again, Go back to  <strong>Block public access (bucket settings)</strong> and keep the checklist as shown below :</p>
<p>In <strong>BUCKET</strong> &gt; <strong>Permissions</strong> &gt; <strong>Block public access (bucket settings)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634451611465/Y-Lbr404c.png" alt="image.png" /></p>
<p>You'll see that UI is little changes with these indications:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634451742450/VMptRV1H7.png" alt="image.png" /></p>
<p>Don't worry we will fine-tune the access in a bit, Just stick to the guide!</p>
<h4 id="heading-validation">Validation</h4>
<p>Now when I check the webpage (In my case, it is Codepen) The video is not accessible anymore! 😥</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634451185136/CSYvKpguLa.png" alt="image.png" /></p>
<h3 id="heading-step-2-add-the-bucket-policy-to-whitelist-our-domain">Step-2: Add the bucket policy to whitelist our domain</h3>
<p><code>ℹ️</code> As you know from Step-1, our video was unable to play. let's resolve this.</p>
<p>For that go to your <strong>BUCKET</strong> &gt; <strong>Permissions</strong> &gt; <strong>Bucket policy</strong> and paste the below code.</p>
<p>⚠️ Make sure you are replacing <strong>kratik-blog-assets</strong> with your bucket name.</p>
<p>⚠️ For the value of the Key <strong>"aws:Referer"</strong>, make sure you are adding your domain name(s).</p>
<pre><code>{
    <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-attr">"Id"</span>: <span class="hljs-string">"http referer policy example"</span>,
    <span class="hljs-attr">"Statement"</span>: [
        {
            <span class="hljs-attr">"Sid"</span>: <span class="hljs-string">"Allow get requests originating from your domains only."</span>,
            <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Principal"</span>: <span class="hljs-string">"*"</span>,
            <span class="hljs-attr">"Action"</span>: [
                <span class="hljs-string">"s3:GetObject"</span>,
                <span class="hljs-string">"s3:GetObjectVersion"</span>
            ],
            <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:s3:::kratik-test/*"</span>,
            <span class="hljs-attr">"Condition"</span>: {
                <span class="hljs-attr">"StringLike"</span>: {
                    <span class="hljs-attr">"aws:Referer"</span>: [
                        <span class="hljs-string">"https://codepen.io/*"</span>,
                        <span class="hljs-string">"https://cdpn.io/*"</span>
                    ]
                }
            }
        }
    ]
}
</code></pre><p>It will look something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634452550186/zWJU3_-0z.png" alt="image.png" /></p>
<h4 id="heading-validation">Validation</h4>
<p>Check the Codepen again and observe if something is different now.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634452409664/nDYcilOAt.png" alt="image.png" /></p>
<p>Oh, Wow! Our video is able to play on our platform's Webpage.</p>
<p><code>ℹ️</code> As we created the above Bucket policy, it whitelisted our domain and allowed access.</p>
<h2 id="heading-step-3-testing-the-download">Step-3: Testing the Download</h2>
<p>Let's try again downloading our assets.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634452238202/8MCU2pKtL.gif" alt="ezgif-2-9edf37a2f1d0.gif" /></p>
<p> 🎊 🎉 🥳</p>
<p>This is what we wanted right? Users can view your content but won't be able to download it. </p>
<p>isn't it cool? </p>
<p><img src="https://i.giphy.com/media/XAdbHJywVjF5K/giphy.webp" /></p>
<hr />
<p><strong>Disclaimer</strong></p>
<p>As I mentioned this is a workaround but not a 100% foolproof solution, technically advanced users can bypass this policy very easily, So if your data is really very very sensitive, my Suggestion is, Don't do this :) </p>
<p>For more sensitive content there are other methods available that require time and knowledge to set up but yeah, they will def fulfill your use case.</p>
<p>For example :</p>
<ul>
<li><a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html">S3 signed URLs</a></li>
<li><a target="_blank" href="https://stackoverflow.com/a/46702434">Elastic transcoder</a></li>
</ul>
<hr />
<p>Drop Some Emojis if you find this useful. Or leave a comment.</p>
<p>Thanks for Reading! 🙇‍♂️</p>
<hr />
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik">Wanna buy me a Coffee</a> ☕ ?</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623604053124/HDvapUuoW.jpeg" alt="buy-me-a-coffee.jpeg" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Using Multiple Docker-Compose files]]></title><description><![CDATA[I have assumed that you have some basic knowledge of Docker & Docker Compose!
What is Docker 🐬 ?

An Open-Source tool that can be used to bundle your application with all the required dependencies.
Similar to VMs but way more lightweight than it.
Us...]]></description><link>https://blogs.kratik.dev/using-multiple-docker-compose-files</link><guid isPermaLink="true">https://blogs.kratik.dev/using-multiple-docker-compose-files</guid><category><![CDATA[Docker]]></category><category><![CDATA[docker images]]></category><category><![CDATA[containers]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sun, 19 Sep 2021 16:28:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1632071079887/03D46npeY.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>I have assumed that you have some basic knowledge of Docker &amp; Docker Compose!</em></p>
<h4 id="heading-what-is-docker">What is Docker 🐬 ?</h4>
<ul>
<li>An Open-Source tool that can be used to bundle your application with all the required dependencies.</li>
<li>Similar to VMs but way more lightweight than it.</li>
<li>Uses <code>Dockerfile</code></li>
<li>Written in Golang</li>
</ul>
<hr />
<h4 id="heading-what-is-docker-compose">What is Docker Compose?</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1632151102448/PcvnqFI-N.png" alt="image.png" /></p>
<ul>
<li>When you have a requirement of multiple containers and need of managing it nicely, you can achieve that with Docker Compose.</li>
<li>Here you can define multiple containers, networking and how should they connect to each other, etc.</li>
<li>Uses <code>docker-compose</code> file</li>
<li>Written in Python </li>
</ul>
<hr />
<p>Ok So this was some basic info. Now, Let me ask you a question - Have you seen something like this before and ever wondered what it does exactly?</p>
<pre><code>docker<span class="hljs-operator">-</span>compose <span class="hljs-operator">-</span>f docker<span class="hljs-operator">-</span>compose.yml <span class="hljs-operator">-</span>f docker<span class="hljs-operator">-</span>compose.prod.yml up <span class="hljs-operator">-</span>d
</code></pre><p>By observing the few lines, one can deduce that here we are using two compose files and then launching the compose stack in detach mode<code>(-d)</code></p>
<hr />
<p>The basic idea behind this is to <strong>keep your main compose file generic for all environments</strong> (like dev, stage, and prod) as much as possible.</p>
<p>And for all the needs, which are specific to an environment, pass another file with those configs and it will append all the files to generate the desired config.</p>
<p><strong>Use case example</strong> - You are using almost the same config on your environments but for dev, the database port is open for devs to testing OR Suppose if you want to run the dev and stage environment in DEBUG mode but not the prod one!</p>
<hr />
<h4 id="heading-examples">Examples</h4>
<p><strong>#1</strong> Usual <code>docker-compose.yml</code> file</p>
<pre><code><span class="hljs-attribute">web</span>:
  <span class="hljs-attribute">image</span>: example/<span class="hljs-attribute">my_web_app</span>:latest
  <span class="hljs-attribute">build</span>: .
  <span class="hljs-attribute">volumes</span>:
    - <span class="hljs-string">".:/code"</span>
  <span class="hljs-attribute">ports</span>:
    - <span class="hljs-number">8883</span>:<span class="hljs-number">80</span>
  <span class="hljs-attribute">depends_on</span>:
    - db
    - cache

<span class="hljs-attribute">db</span>:
  <span class="hljs-attribute">image</span>: <span class="hljs-attribute">postgres</span>:latest
  <span class="hljs-attribute">command</span>: <span class="hljs-string">"-d"</span>

<span class="hljs-attribute">cache</span>:
  <span class="hljs-attribute">image</span>: <span class="hljs-attribute">redis</span>:latest
  <span class="hljs-attribute">ports</span>:
    - <span class="hljs-number">6379</span>:<span class="hljs-number">6379</span>
</code></pre><p><em>Now, to pass some extra configuration updates, use another compose file called ...</em></p>
<p><strong>#2</strong> <code>docker-compose-dev.yml</code></p>
<pre><code><span class="hljs-attribute">web</span>:
  <span class="hljs-attribute">environment</span>:
    <span class="hljs-attribute">DEBUG</span>: <span class="hljs-string">'true'</span>

<span class="hljs-attribute">db</span>:
  <span class="hljs-attribute">environment</span>:
    <span class="hljs-attribute">production</span>: <span class="hljs-string">'true'</span>
  <span class="hljs-attribute">ports</span>:
    - <span class="hljs-number">5432</span>:<span class="hljs-number">5432</span>
</code></pre><p>Notice in the second override file we only wrote the config we wanted at the respective block of each service.</p>
<p>Now when you want to <code>up</code> your stack, you need to fire the below command : </p>
<pre><code>docker<span class="hljs-operator">-</span>compose <span class="hljs-operator">-</span>f docker<span class="hljs-operator">-</span>compose.yml <span class="hljs-operator">-</span>f docker<span class="hljs-operator">-</span>compose<span class="hljs-operator">-</span>dev.yml up <span class="hljs-operator">-</span>d
</code></pre><hr />
<h4 id="heading-features-of-using-override-compose-files">Features of using override compose files</h4>
<ol>
<li>It does not need to be a valid compose file, just wrote the part you want to update/add.</li>
<li>By default, Compose reads two files, a <code>docker-compose.yml</code> and an optional <code>docker-compose.override.yml</code> </li>
<li>We need to use <code>-f</code> to specify the file name or path.</li>
<li>Helps you to keep your base compose file generic as much as possible, you just have to create separate override files only according to your requirements.</li>
</ol>
<hr />
<h4 id="heading-references">References</h4>
<ol>
<li>https://docs.docker.com/get-started/overview/</li>
<li>https://docs.docker.com/compose/extends/#multiple-compose-files</li>
</ol>
<hr />
<p>Thanks for reading! have a nice day! </p>
<iframe src="https://giphy.com/embed/26hiubgNAC4Enzd1S" width="480" height="270" class="giphy-embed"></iframe>

<hr />
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik">Wanna buy me a Coffee</a> ☕?</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623604053124/HDvapUuoW.jpeg" alt="buy-me-a-coffee.jpeg" /></a></p>
]]></content:encoded></item><item><title><![CDATA[What does it mean by adding a Security Group ID as a source in another Security Group ?]]></title><description><![CDATA[At some places like Documentation and Tutorials, I have seen that In the source of a rule in the Security Group, they add another security group which seems weird 🤔
Attached a screenshot below : 


Okay, so this was pretty difficult for me to unders...]]></description><link>https://blogs.kratik.dev/what-does-it-mean-by-adding-a-security-group-id-as-a-source-in-another-security-group</link><guid isPermaLink="true">https://blogs.kratik.dev/what-does-it-mean-by-adding-a-security-group-id-as-a-source-in-another-security-group</guid><category><![CDATA[AWS]]></category><category><![CDATA[networking]]></category><category><![CDATA[Security]]></category><category><![CDATA[Devops]]></category><category><![CDATA[AWS Certified Solutions Architect Associate]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sat, 18 Sep 2021 18:11:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1631964763274/NBSZUhAGO.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>At some places like Documentation and Tutorials, I have seen that In the source of a rule in the Security Group, they add another security group which seems weird 🤔</p>
<p>Attached a screenshot below : </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631989292725/IhniNjTr1.png" alt="image.png" /></p>
<hr />
<p>Okay, so this was pretty difficult for me to understand initially😅 and I was kind of misunderstanding this, So let's clear some air and improve our basics.</p>
<p>Let's Start!
<img src="https://media1.giphy.com/media/o0vwzuFwCGAFO/200w.webp" /></p>
<hr />
<p>Have you ever seen this kind of Security Group Configuration?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631965224257/4i4QhlVnD.png" alt="image.png" /></p>
<p>Notice the second rule in the SG Inbound rules!</p>
<hr />
<h4 id="heading-explaination">Explaination</h4>
<p>It simply means that <strong><em>" allow the traffic coming from all the members attached to that Security Group(which we added as the source in that particular rule)"</em></strong></p>
<ul>
<li><strong>Members</strong> means the instances(or other resources which uses a network interface)</li>
<li>Remember! - Here it points the Private IPs of those <strong>Members</strong></li>
</ul>
<hr />
<h4 id="heading-use-cases">Use cases</h4>
<ol>
<li><p>In the case of <strong>ELB and EC2s</strong>, you can create 2 Security Groups, one for EC2s and one for Load Balancer. Now, In the EC2's Security Group, suppose your app runs at ports 80 and 443, create the inbound rules, and in the source for those rules add the <strong>Security Group ID of the Load Balancer's Security Group</strong>. That way you are only allowing traffic coming from Load Balancer, All direct traffic will be dropped!
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631987480420/x_Imar92U.png" alt="image.png" /></p>
</li>
<li><p>Suppose you have a few instances. Now, you want to allow all inter-instance communication b/w them. </p>
</li>
</ol>
<p>One dumb and insecure approach can be : </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631987619053/TvnZnfRVi.png" alt="image.png" /></p>
<p><em>Here you allowed all Traffic from any source (0.0.0.0/0)</em></p>
<hr />
<p>One other and better approach can be to use the concept, what we just learned.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631987386530/js9OlF06b.png" alt="image.png" />
<em>Here you can see we have given the ID of the Security Group itself.(Instead of giving 0.0.0.0/0)</em>
This means --&gt; All the member(who have this SG attached), will be able to connect to other members on any port. </p>
<p><em>Note: Instead of all traffic, if you want, you can specify ports as well which are being used.</em> </p>
<hr />
<h4 id="heading-private-ip-addresses">Private IP Addresses</h4>
<p>From the Official Docs, <strong><em>"When you specify a security group as the source for a rule, traffic is allowed from the network interfaces that are associated with the source security group for the specified protocol and port. Incoming traffic is allowed based on the private IP addresses of the network interfaces that are associated with the source security group (and not the public IP or Elastic IP addresses).</em></strong></p>
<ul>
<li>So when we are using this approach, keep in mind that behind the scenes <strong>it is whitelisting the private IPs of those Members!</strong>, <em>So you won't be able to connect to the specified port if you are using Public or Elastic IP.</em></li>
</ul>
<hr />
<p>Hope you have learned something new from this blog :)</p>
<p><img src="https://media.giphy.com/media/QNFhOolVeCzPQ2Mx85/giphy.gif" />
If you have any Queries/Feedback, do let me know 🙏</p>
<p>Thank you so much!</p>
<hr />
<h4 id="heading-references">References</h4>
<ol>
<li>https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html#SecurityGroupRules </li>
</ol>
<hr />
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik">Wanna buy me a Coffee</a> ☕ ?</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623604053124/HDvapUuoW.jpeg" alt="buy-me-a-coffee.jpeg" /></a></p>
]]></content:encoded></item><item><title><![CDATA[What are Network ACLs in AWS Virtual Private Cloud?]]></title><description><![CDATA[Network ACLs or NACLs are one of the additional layers of security AWS provides to safeguard your resources in the AWS Cloud. It acts as a firewall for controlling traffic in and out. 

Image Source:  https://docs.aws.amazon.com/vpc/latest/userguide/...]]></description><link>https://blogs.kratik.dev/what-are-network-acls-in-aws-virtual-private-cloud</link><guid isPermaLink="true">https://blogs.kratik.dev/what-are-network-acls-in-aws-virtual-private-cloud</guid><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Security]]></category><category><![CDATA[networking]]></category><category><![CDATA[AWS Certified Solutions Architect Associate]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sun, 12 Sep 2021 04:27:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1631774727166/vxCFiXNxVXH.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Network ACLs or NACLs are one of the additional layers of security AWS provides to safeguard your resources in the AWS Cloud. It acts as a firewall for controlling traffic in and out. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631848486677/MpKsNa5mX.png" alt="image.png" />
<em>Image Source:  https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Security.html</em></p>
<p>When You create a new VPC, it comes with a default NACL which allows all ingress/egress(or inbound/outbound) traffic but when you create a custom NACL it denies all the inbound and outbound traffic by default. </p>
<p><em>If you don't explicitly associate a subnet with a network ACL, the subnet is automatically associated with the default network ACL.</em></p>
<h3 id="heading-features-of-nacls">Features of NACLs</h3>
<ol>
<li>You can apply NACL at the <strong>Subnet level</strong>. (A subnet can be associated with only one NACL, you can attach multiple subnets to the same NACL though).</li>
<li>You can specify in the rules that if you want to <strong>ALLOW</strong> or <strong>DENY</strong> the traffic that matches the particular rule.</li>
<li><strong>These are stateless</strong>. It means if you allow inbound traffic on port 3000 in NACL, it won't allow the outbound or returning traffic by default (for requests made on port 3000), you have to allow the port(if you know already what port will be used for returning traffic) or port range in the Outbound Rules of NACL (for example, for web HTTP requests, it uses ephemeral ports i.e. 1024–65535).</li>
<li>When you are creating rules for an NACL you have to provide that entry a number (max is 32766), the reason for this is, <strong>AWS evaluates the rules in order, starting with the lowest numbered rule, to determine whether traffic is allowed in or out.</strong></li>
<li>AWS recommends that we should create rules in increments (for example, increments of 10 or 100) so that we can insert new rules later on.</li>
<li>NACL rules are applied on the traffic which is inbound or outbound but not for the traffic within resources of the subnet. </li>
</ol>
<hr />
<h3 id="heading-how-nacls-looks-in-aws-console">How NACLs looks in AWS Console</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631940676728/jpuXbUKxf.png" alt="image.png" /></p>
<hr />
<h3 id="heading-difference-bw-security-groups-and-nacls">Difference b/w Security Groups and NACLs</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Security Groups</td><td>NACLs</td></tr>
</thead>
<tbody>
<tr>
<td>Instance(or Interface Level)</td><td>Subnet Level</td></tr>
<tr>
<td>Stateful</td><td>Stateless</td></tr>
<tr>
<td>Can have only ALLOW rules</td><td>Can ALLOW or DENY traffic</td></tr>
<tr>
<td>It evaluates all rules</td><td>Starts evaluating the rules in numbered ordered, stops once find an ALLOW or DENY rule</td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-practical-example">Practical Example</h3>
<ol>
<li>Suppose you set up a Web Server on an EC2 that is serving req at port 80.</li>
<li>Assume you associated an SG that allows all traffic.</li>
<li>Now, you created a custom NACL, as you have just read above that a new NACL denies all traffic. And attached this NACL to the subnet where our EC2 is.</li>
<li>Now, You tested the EC2 Public IP with port 80 using curl to check the response but it got timed out. (<strong>REASON</strong> - NACL is at the subnet level and denying all traffic)</li>
<li>Okay, So Now you added a port 80 allow rule in the <strong>inbound rules</strong> in NACL to allow traffic on port 80.</li>
<li>Back to Terminal, Tried <code>curl</code> again but wait what, why it is still not working?
<strong>REASON/EXPLANATION -&gt;</strong> So this is how it behaves, aka <strong>stateless</strong> behavior.
Your web server listens at port 80, that is true but when sending the response it uses some random port (also called short-lived aka <em>ephemeral ports</em>)</li>
<li>Now In the NACL <strong>Outbound rules</strong>, allow traffic for ephemeral ports (1024–65535).</li>
<li>Now if you try <code>curl</code> you will be able to see the response in your terminal because now NACL allows the outbound traffic as well.  🎊🎉</li>
</ol>
<hr />
<h3 id="heading-where-nacls-are-perfect-and-sgs-are-not">Where NACLs are perfect and SGs are not?</h3>
<p>There can be a few use cases where your requirement can not be fulfilled by Security Groups and you have to use NACLs. For example - Suppose you want to <strong>block traffic from specific IPs</strong>, as SG doesn't have the feature of explicitly DENY you have to use NACLs only. </p>
<p>and Remember! NACL is the first line of defense, SGs is the second!</p>
<hr />
<h3 id="heading-references">References</h3>
<ol>
<li>https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html </li>
<li>https://digitalcloud.training/certification-training/aws-solutions-architect-associate/networking-and-content-delivery/amazon-vpc/</li>
<li>https://stackoverflow.com/a/53625507</li>
<li>https://qr.ae/pGS8f1</li>
</ol>
<hr />
<p><iframe src="https://giphy.com/embed/xUNd9YJwF6ifDUnqNi" width="480" height="270" class="giphy-embed"></iframe></p><p></p>
<p><strong>Thanks for reading! have a nice day! </strong></p>
<hr />
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik">Wanna buy me a Coffee</a> ☕ ?</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623604053124/HDvapUuoW.jpeg" alt="buy-me-a-coffee.jpeg" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Best Logging Solution for Docker  [Basic Version]]]></title><description><![CDATA[Logs are important, right? While debugging something, It helps us more than anything. 
Generally, we use commands to check logs of each container, for example,
docker logs -f container_1 
and it is fine for a few containers, but suppose, you have a c...]]></description><link>https://blogs.kratik.dev/best-logging-solution-for-docker</link><guid isPermaLink="true">https://blogs.kratik.dev/best-logging-solution-for-docker</guid><category><![CDATA[Docker]]></category><category><![CDATA[logging]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Developer Blogging]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sun, 11 Jul 2021 13:12:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1625404988207/hpCJimcOG.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Logs are important, right? While debugging something, It helps us more than anything. </p>
<p>Generally, we use commands to check logs of each container, for example,</p>
<p><code>docker logs -f container_1</code> </p>
<p>and it is fine for a few containers, but suppose, you have a complex architecture, where you are using <a target="_blank" href="https://docs.docker.com/engine/swarm/">Docker Swarm</a>  or  <a target="_blank" href="https://docs.docker.com/compose/">Docker-Compose</a> or just assume you have a number of containers, then what?</p>
<p>Yes! It will be a real pain to maintain those logs, on top of that, other issues like No sharing of logs with the team, No Access-based control, etc. will also be there.        </p>
<p>I bet the native Docker plugin kinda feeling similar as below right now :</p>
<iframe src="https://giphy.com/embed/CbOJUVPfOUGiMQRieq" width="480" height="400" class="giphy-embed"></iframe> 



<p>Let's try a solution for that :)</p>
<p>We will be using Loki and Grafana to achieve our goal. Loki is a set of components that can be composed into a fully-featured logging stack. More info  <a target="_blank" href="https://github.com/candlerb/loki/blob/a2d1aba305299db2aa5747e227026277a591f6d7/docs/overview/README.md">here</a>  and  <a target="_blank" href="https://github.com/candlerb/loki/blob/a2d1aba305299db2aa5747e227026277a591f6d7/docs/architecture.md">here</a></p>
<hr />
<p>Before we jump into installation and all other intense stuff, let's see what benefits are we going to get by end of this installation: </p>
<ol>
<li>Restricted User Access</li>
<li>Organization Support</li>
<li>Log Rotation</li>
<li>Centralisation of logs</li>
<li>Visualize various logs in a single place using Loki Queries.</li>
<li>Live Tailing of logs</li>
</ol>
<hr />
<iframe src="https://giphy.com/embed/BpGWitbFZflfSUYuZ9" width="480" height="400" class="giphy-embed"></iframe>


<p>Let's start the setup:</p>
<ol>
<li>Configuring the Logging Driver for Docker</li>
<li>Setting up the Loki instance with its config file</li>
<li>Setting up Grafana Dashboard to visualize the logs.</li>
</ol>
<h3 id="heading-1-configuring-the-logging-driver-for-docker">1. Configuring the Logging Driver for Docker</h3>
<p><em>*you need to repeat these steps for each Docker node if you are using Docker Swarm</em></p>
<h4 id="heading-install-the-driver">- Install the driver</h4>
<pre><code>docker plugin install grafana<span class="hljs-operator">/</span>loki<span class="hljs-operator">-</span>docker<span class="hljs-operator">-</span>driver:latest <span class="hljs-operator">-</span><span class="hljs-operator">-</span>alias loki <span class="hljs-operator">-</span><span class="hljs-operator">-</span>grant<span class="hljs-operator">-</span>all<span class="hljs-operator">-</span>permissions
</code></pre><h4 id="heading-configure-the-docker-to-send-logs-to-loki-instance">- Configure the Docker to send logs to Loki Instance</h4>
<pre><code>{
    <span class="hljs-attr">"debug"</span> : <span class="hljs-literal">false</span>,
    <span class="hljs-attr">"log-driver"</span>: <span class="hljs-string">"loki"</span>,
    <span class="hljs-attr">"log-opts"</span>: {
        <span class="hljs-attr">"loki-url"</span>: <span class="hljs-string">"http://127.0.0.1:3100/loki/api/v1/push"</span>
    }
}
</code></pre><h4 id="heading-restart-the-docker-daemon-on-each-node">- Restart the Docker Daemon on each node</h4>
<pre><code><span class="hljs-comment"># Check the number of the nodes in your swarm </span>
docker node ls
<span class="hljs-comment"># Drain the node so the running workload can be shifted to other nodes.</span>
docker node <span class="hljs-keyword">update</span> <span class="hljs-comment">--availability drain node1_name</span>
<span class="hljs-comment"># Restart the docker daemon on that node</span>
systemctl restart docker
<span class="hljs-comment"># Make the node active again</span>
docker node <span class="hljs-keyword">update</span> <span class="hljs-comment">--availability active node1_name</span>
</code></pre><h3 id="heading-2-setting-up-the-loki-instance-with-its-config-file">2. Setting up the Loki instance with its config file</h3>
<p>To get the codebase, Clone my  <a target="_blank" href="https://github.com/k4kratik/loki">fork</a>  </p>
<pre><code>git <span class="hljs-keyword">clone</span> https:<span class="hljs-comment">//github.com/k4kratik/loki</span>
</code></pre><h4 id="heading-observe-the-config-file-loki-local-configyaml">- Observe the config file <code>loki-local-config.yaml</code></h4>
<pre><code><span class="hljs-attr">auth_enabled:</span> <span class="hljs-literal">false</span>

<span class="hljs-attr">server:</span>
  <span class="hljs-attr">http_listen_port:</span> <span class="hljs-number">3100</span>

<span class="hljs-attr">ingester:</span>
  <span class="hljs-attr">lifecycler:</span>
    <span class="hljs-attr">address:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>
    <span class="hljs-attr">ring:</span>
      <span class="hljs-attr">kvstore:</span>
        <span class="hljs-attr">store:</span> <span class="hljs-string">inmemory</span>
      <span class="hljs-attr">replication_factor:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">chunk_idle_period:</span> <span class="hljs-string">15m</span>

<span class="hljs-attr">schema_config:</span>
  <span class="hljs-attr">configs:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">from:</span> <span class="hljs-number">2019-06-01</span>
    <span class="hljs-attr">store:</span> <span class="hljs-string">boltdb</span>
    <span class="hljs-attr">object_store:</span> <span class="hljs-string">filesystem</span>
    <span class="hljs-attr">schema:</span> <span class="hljs-string">v9</span>
    <span class="hljs-attr">index:</span>
      <span class="hljs-attr">prefix:</span> <span class="hljs-string">index_</span>
      <span class="hljs-attr">period:</span> <span class="hljs-string">168h</span>

<span class="hljs-attr">table_manager:</span>
<span class="hljs-string">.</span>
<span class="hljs-string">.</span>
<span class="hljs-string">.</span>
<span class="hljs-comment"># Notice this</span>
  <span class="hljs-attr">retention_deletes_enabled:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">retention_period:</span> <span class="hljs-string">672h</span>
<span class="hljs-string">...</span>
</code></pre><p>Here we are setting up the auto-deletion of old logs after a certain period, which is 28 days (672 Hours) in our case. </p>
<h4 id="heading-now-install-the-stack">- Now Install the stack</h4>
<pre><code>docker stack deploy <span class="hljs-operator">-</span>c docker<span class="hljs-operator">-</span>compose.yml loki<span class="hljs-operator">-</span>stack
</code></pre><h4 id="heading-verify-that-everything-is-working-fine">- Verify that everything is working fine.</h4>
<pre><code><span class="hljs-attribute">docker</span> service ls
</code></pre><h3 id="heading-3-setting-up-grafana-dashboard-to-visualize-the-logs">3. Setting up Grafana Dashboard to visualize the logs.</h3>
<p>above stack file also contains a grafana instance, which will be available to <code>YOUR_SERVER_IP:3000</code> </p>
<p>Go to Grafana Dashboard, Initial credentials will be <code>admin:admin</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625979377717/XCOXfRJ2Z.png" alt="2021-07-11_10-25.png" /></p>
<p>Go to <code>Configuration</code> &gt; <code>Data Sources</code> as displayed in the picture.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625979525970/gf130JFMst.png" alt="2021-07-11_10-27.png" /></p>
<p>Now Click on <code>Add Data Source</code> and enter the <code>URL</code> as <code>http://loki:3100</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625980802424/IVV_lXXJc.png" alt="2021-07-11_10-49.png" /></p>
<p>If you have done everything correctly till this point, Clicking <code>Save &amp; Test</code> will emit a success message.</p>
<p>❓ Just one question out of curiosity... We did not enter a IP or Domain Name, then How did it figure out our Loki instance correctly ? 🤔</p>
<p><strong>The answer is :</strong> Check the docker-compose file, the loki service is named as <code>loki</code>, and other containers can reach to this container by the service name, which is in our case is <strong>loki</strong>.</p>
<blockquote>
<p> Every container has by default a nameserver entry in /etc/resolve.conf, so it is not required to specify the address of the nameserver from within the container. That is why you can find your service from within the network with <strong>service</strong> or task_service_n.</p>
</blockquote>
<p>- <a target="_blank" href="https://stackoverflow.com/a/64867075">A stack<strong>overflow</strong> answer</a> </p>
<hr />
<h4 id="heading-using-grafana">Using Grafana</h4>
<p>Once you have added the Data Source, Go to Explore as instructed below: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625982014300/ZyuqZlaG_.png" alt="2021-07-11_11-09.png" /></p>
<p>Select a container to view its logs</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625982502511/Ik3D80PfHd.png" alt="2021-07-11_11-17.png" /></p>
<p>Just like the image below, you can check logs of host, stdout &amp; stderr streams, containers, Docker services, Docker stacks, etc. You can also combine multiple filter to get a consolidated output of your choice.</p>
<h2 id="heading-2021-07-1111-22pnghttpscdnhashnodecomreshashnodeimageuploadv16259827324355nfpubbiwpng"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625982732435/5nFPUbBiW.png" alt="2021-07-11_11-22.png" /></h2>
<h4 id="heading-security-measures">Security Measures</h4>
<ol>
<li>Block the internet access on Loki port 3100 (+ Grafana Port 3000 if you want). Using Firewall is heavily recommended. As the official  <a target="_blank" href="https://grafana.com/docs/loki/latest/operations/authentication/">docs</a> says, <strong>Loki does not come with any included authentication layer</strong>, So it would be good if you allow only specific IPs.</li>
<li>If you want to make it more secure, set up an NGINX reverse proxy with Basic Auth. [For Production Environments]  </li>
</ol>
<hr />
<h4 id="heading-making-it-more-fault-tolerant-and-ha">Making it more Fault-Tolerant and HA</h4>
<p>If you want to take this on a whole next level, well, there is good news for you, for the storage part you can also use Cloud DBs like <strong>DynamoDB</strong> for index and <strong>S3</strong> for storing chunks. You can read storage options  <a target="_blank" href="https://grafana.com/docs/loki/latest/operations/storage/">here</a>. </p>
<hr />
<iframe src="https://giphy.com/embed/CQwNV6b8ScIFvTSO6u" width="480" height="400" class="giphy-embed"></iframe>

<p>My excited Indian ass dances like this whenever I complete any setup :) We all love that feeling, don't we?</p>
<p>Anyways, Thanks for reading and bearing all my content and  <a href="https://en.wikipedia.org/wiki/The_Office_(American_TV_series)">The Office</a>  GIFs! 
Hope you got some knowledge from this blog, I am just writing what I am thinking, still trying to making it as simple and straightforward as possible. </p>
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik">Wanna buy me a Coffee</a> ☕ ?</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623604053124/HDvapUuoW.jpeg" alt="buy-me-a-coffee.jpeg" /></a></p>
]]></content:encoded></item><item><title><![CDATA[How to Prevent a file to be overwritten in a Git Repo?]]></title><description><![CDATA[Hey All!
We all have worked with at least one Version Control System Softwares in our day-to-day tasks and Git is one of those and the most widely used VCS Tool in the world! 
What are Hooks in Git?
According to the official site, Hooks are programs ...]]></description><link>https://blogs.kratik.dev/prevent-changes-in-git-using-hooks</link><guid isPermaLink="true">https://blogs.kratik.dev/prevent-changes-in-git-using-hooks</guid><category><![CDATA[Git]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[automation]]></category><category><![CDATA[development]]></category><category><![CDATA[version control]]></category><dc:creator><![CDATA[Kratik Jain]]></dc:creator><pubDate>Sun, 13 Jun 2021 13:14:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1623524952924/skkfwz8hS.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<iframe src="https://giphy.com/embed/kH6CqYiquZawmU1HI6" width="480" height="220" class="giphy-embed"></iframe>

<p>Hey All!</p>
<p>We all have worked with at least one <strong>Version Control System</strong> Softwares in our day-to-day tasks and <strong>Git</strong> is one of those and the most widely used VCS Tool in the world! </p>
<h3 id="heading-what-are-hooks-in-git">What are Hooks in Git?</h3>
<p>According to <a target="_blank" href="https://git-scm.com/docs/githooks">the official site</a>, <em>Hooks are programs that trigger actions at certain points in git’s execution</em>.</p>
<h3 id="heading-how-to-add-a-hook">How to Add a Hook?</h3>
<p>The default directory where Git Hooks resides is <code>.git/hooks</code>. (if you don't know you will find this hidden git folder at the root of your repo)</p>
<p>If you want, you can change this behavior and configure another directory for Hooks. Example -</p>
<pre><code>$ git config core.hooksPath my<span class="hljs-operator">-</span>custom<span class="hljs-operator">-</span>hook<span class="hljs-operator">-</span>directory
</code></pre><p>So the idea here is that we can configure something in the <code>pre-commit</code> hook and before we commit, it will check If we have modified the restricted file or not and If we have modified the file it will raise an error, and the commit will be exited/rejected.</p>
<p>Git Hook files are nothing but shell scripts, So Before doing any Scripting, let's see what hooks we have by default in our git repo.</p>
<p>So I did <code>ls -lha .git/hooks/</code> on my repo's root and got the below output:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623583970388/rW0dukZcE.png" alt="Screenshot_20210613_170051.png" /></p>
<p>As you saw in the output of <code>ls</code> command above, by default there are some sample hooks and if you want to use one, simply remove the <code>.sample</code> from the name. And then you can edit the file as you want and the respective hook will be applied automatically. It is clear from the name of the files that when that particular hook will be called. Here we want to use that <code>pre-commit</code> hook.</p>
<p>⚠️ These are Client Side hooks, so if you push your code (For example - to GitHub), <strong>the hooks won't get pushed to the remote</strong> (or can not be shared amongst other contributors), As the <code>.git</code> directory never gets pushed. You can read some explanations  <a target="_blank" href="https://stackoverflow.com/a/3703207">here</a> as well. </p>
<hr />
<p>That was some basic info about hooks, now Coming to the title of our Blog, which is <strong>How to Prevent a file to be overwritten in a Git Repo?</strong> </p>
<p>So now if you have guessed already then congrats, but for others, we can configure something in the <strong>pre-commit hook</strong> which will reject any commit which includes changes of that file.</p>
<p>Add the hook file as I mentioned above (by creating a file called <em>pre-commit</em> in the <code>.git/hooks</code> directory)</p>
<p>So now the question arises How can we check if a file changed or not? 
We generally run the command <code>git diff</code> to check if a file changed or not. There is an argument available for the above command <code>--exit-code</code> which returns <strong>exit code 1</strong> if the file changed, otherwise returns 0 (no error).</p>
<p>A more mature command looks like below:</p>
<pre><code>git diff <span class="hljs-operator">-</span><span class="hljs-operator">-</span>exit<span class="hljs-operator">-</span>code HEAD $FileName <span class="hljs-operator">&gt;</span><span class="hljs-operator">/</span>dev<span class="hljs-operator">/</span>null <span class="hljs-number">2</span><span class="hljs-operator">&gt;</span><span class="hljs-operator">&amp;</span><span class="hljs-number">1</span>
</code></pre><p>which means it is checking the difference between <strong>HEAD</strong> (current branch or last committed state on current branch) &amp; <strong>Current Changes</strong> (Index, Staged, or whatever you prefer to call it.)</p>
<p>Now what we can do is, that we can store that exit code into some variable and according to that print a message for the user before rejecting the commit.</p>
<p>For example, In my repo, I have created a file called <code>secure.file</code> and we want to prevent any changes for this file.</p>
<p>So My <code>pre-commit</code> file looks somewhat like below:</p>
<pre><code>FileName=<span class="hljs-string">"secure.file"</span>

git diff --exit-code HEAD <span class="hljs-variable">$FileName</span> &gt;/dev/null 2&gt;&amp;1

DIFF_EXIT_CODE=$(<span class="hljs-built_in">echo</span> <span class="hljs-string">"$?"</span>)

<span class="hljs-built_in">echo</span> [LOG] [DEBUG] Diff Exit code was: <span class="hljs-variable">$DIFF_EXIT_CODE</span>

<span class="hljs-keyword">if</span> [ <span class="hljs-variable">$DIFF_EXIT_CODE</span> != 0 ]; <span class="hljs-keyword">then</span>
    RED=<span class="hljs-string">'\033[0;31m'</span>
    NC=<span class="hljs-string">'\033[0m'</span> <span class="hljs-comment"># No Color</span>
    <span class="hljs-built_in">printf</span> <span class="hljs-string">"<span class="hljs-variable">${RED}</span>Error Ocurred! You can not change the<span class="hljs-variable">${NC}</span> <span class="hljs-variable">$FileName</span>\n"</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Revert the changes for <span class="hljs-variable">$FileName</span> file and try again"</span>
    <span class="hljs-built_in">exit</span> 1
<span class="hljs-keyword">fi</span>
</code></pre><p>It may look a little scary but the above code snippet is too easy to understand and interpret. We Stored the EXIT CODE, Using the <code>If</code> Block and <code>exit</code> command we printed the appropriate message and exited the script. Also, I have used Colourised echo output and a more customized message, just for flex ;) </p>
<p>So now If I try to commit any change to that file, I get the below Error:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623589865123/uGbsnA1cV.png" alt="Screenshot_20210613_183247.png" /></p>
<hr />
<p>So that was a basic example, there is no end to your creativity. Read more about all the hooks  <a target="_blank" href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">here</a> . This way you can implement as many checks and hooks as you want, The exciting part here is that you will be the one who will be tailoring a custom UX for yourself. </p>
<p>As I mentioned that hooks will never get pushed to the central repo, what you can do is store the hooks in a separate directory and Push that to remote. That way, it will be shared amongst other devs and also, it will be version controlled. </p>
<p>Hope this article helped in some way, Let me know if there is any suggestion.
Thanks! </p>
<p>Till We meet the next time 👋🏻 :</p>
<iframe src="https://giphy.com/embed/dYlV3J2KsnuGdKFoDr" width="480" height="270" class="giphy-embed"></iframe>

<hr />
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik">Wanna buy me a Coffee</a> ☕ ?</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/kratik"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623604053124/HDvapUuoW.jpeg" alt="buy-me-a-coffee.jpeg" /></a></p>
]]></content:encoded></item></channel></rss>