<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Posts on Tr01Lab</title>
		<link>https://trollab.ca/posts/</link>
		<description>Recent content in Posts on Tr01Lab</description>
		<generator>Hugo -- gohugo.io</generator>
		<language>en-us</language>
		<lastBuildDate>Fri, 19 Nov 2021 16:10:57 -0500</lastBuildDate>
		<atom:link href="https://trollab.ca/posts/index.xml" rel="self" type="application/rss+xml" />
		
		<item>
			<title>Snippets: Kubernetes secrets base64 decoder</title>
			<link>https://trollab.ca/posts/snippets_k8s_secret_decoder/</link>
			<pubDate>Fri, 19 Nov 2021 16:10:57 -0500</pubDate>
			
			<guid>https://trollab.ca/posts/snippets_k8s_secret_decoder/</guid>
			<description>It is very common that we need to see the secret content when working with Kubernetes. Imagine that you have installed a Helm chart in your cluster with auto-generated credentials and you need to get them to login to the app UI, or maybe you are troubleshooting and you want to make sure that the secret has the data it is supposed to have. If that sounds familiar, you might find this snippet particularly useful.</description>
			<content type="html"><![CDATA[

<p>It is very common that we need to see the secret content when working with Kubernetes. Imagine that you have installed a Helm chart in your cluster with auto-generated credentials and you need to get them to login to the app UI, or maybe you are troubleshooting and you want to make sure that the secret has the data it is supposed to have. If that sounds familiar, you might find this snippet particularly useful.</p>

<h2 id="problem">Problem</h2>

<p>Extracting values from a Kubernetes secret is quite easy but requires quite a bit of typing. Here&rsquo;s one of the ways you can do it:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">kubectl get secret someapp-credentials -o <span class="nv">jsonpath</span><span class="o">=</span><span class="s1">&#39;{.data.username}&#39;</span> <span class="p">|</span> base64 -d
kubectl get secret someapp-credentials -o <span class="nv">jsonpath</span><span class="o">=</span><span class="s1">&#39;{.data.password}&#39;</span> <span class="p">|</span> base64 -d</code></pre></div>
<p>Not so bad. Though for a person like me, who prefers to have <code>k</code> as an alias to <code>kubectl</code> because it&rsquo;s just too much to type, doing this task regularly can become annoying pretty fast.</p>

<p>This is actually something I used to do a lot. But one day the level of frustration when typing all these commands reached the threshold so I sit to come up with a simple script that could become my &ldquo;Swiss Army Knife&rdquo; when it comes to working with Kubernetes secrets.</p>

<h2 id="solution">Solution</h2>

<p>The core idea here is to support two use cases:</p>

<ol>
<li><p>Print the decoded value of a single key in a secret</p></li>

<li><p>Print the entire <code>data</code> section of a secret with all values being decoded</p></li>
</ol>

<p>Ideally it should support both getting secrets from the namespace of the current context and any other namespace in a very concise manner(typing -n namespace every time is just too much trouble). Below is the script itself. It utilizes a bit of bash, basic linux commands, and <a href="https://stedolan.github.io/jq/">jq</a>.</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH"><span class="cp">#!/bin/bash
</span><span class="cp"></span>
<span class="k">if</span> <span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
  <span class="nb">echo</span> <span class="s2">&#34;Usage: </span><span class="nv">$0</span><span class="s2"> [&lt;namespace&gt;/]&lt;secret&gt; [&lt;key&gt;]&#34;</span>
  <span class="nb">exit</span> <span class="m">1</span>
<span class="k">fi</span>

<span class="nv">namespace_or_secret</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$1</span> <span class="p">|</span> awk -F <span class="s1">&#39;/&#39;</span> <span class="s1">&#39;{print $1}&#39;</span><span class="k">)</span>
<span class="nv">secret</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$1</span> <span class="p">|</span> awk -F <span class="s1">&#39;/&#39;</span> <span class="s1">&#39;{print $2}&#39;</span><span class="k">)</span>

<span class="nv">result</span><span class="o">=</span><span class="s2">&#34;&#34;</span>
<span class="k">if</span> <span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$secret</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
  <span class="nv">secret</span><span class="o">=</span><span class="nv">$namespace_or_secret</span>
  <span class="nv">result</span><span class="o">=</span><span class="k">$(</span>kubectl get secret <span class="nv">$secret</span> -o <span class="nv">jsonpath</span><span class="o">=</span><span class="s1">&#39;{.data}&#39;</span> <span class="p">|</span> jq <span class="s1">&#39;map_values(@base64d)&#39;</span><span class="k">)</span>
<span class="k">else</span>
  <span class="nv">namespace</span><span class="o">=</span><span class="nv">$namespace_or_secret</span>
  <span class="nv">result</span><span class="o">=</span><span class="k">$(</span>kubectl -n <span class="nv">$namespace</span> get secret <span class="nv">$secret</span> -o <span class="nv">jsonpath</span><span class="o">=</span><span class="s1">&#39;{.data}&#39;</span> <span class="p">|</span> jq <span class="s1">&#39;map_values(@base64d)&#39;</span><span class="k">)</span>
<span class="k">fi</span>

<span class="k">if</span> <span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$2</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
  <span class="nb">echo</span> <span class="nv">$result</span> <span class="p">|</span> jq <span class="s1">&#39;.&#39;</span>
<span class="k">else</span>
  <span class="nb">echo</span> <span class="nv">$result</span> <span class="p">|</span> jq -r --arg k <span class="s2">&#34;</span><span class="nv">$2</span><span class="s2">&#34;</span> <span class="s1">&#39;.[$k]&#39;</span>
<span class="k">fi</span></code></pre></div>
<p>Now I can get a decrypted content of any secret I need in any namespace with one very simple command(assuming you put it in <code>/usr/local/bin/ksecret</code> or <code>$HOME/.local/bin/ksecret</code> and grant <code>chmod +x</code>):</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">ksecret kube-system/default-token-bf4vd</code></pre></div>
<p>the output looks like this:</p>
<div class="highlight"><pre class="chroma"><code class="language-JSON" data-lang="JSON"><span class="p">{</span>
  <span class="nt">&#34;ca.crt&#34;</span><span class="p">:</span> <span class="s2">&#34;-----BEGIN CERTIFICATE-----\nMIIC/jCCAeagAwIBAgIBADANBgkqhkiG9...&#34;</span><span class="p">,</span>
  <span class="nt">&#34;namespace&#34;</span><span class="p">:</span> <span class="s2">&#34;kube-system&#34;</span><span class="p">,</span>
  <span class="nt">&#34;token&#34;</span><span class="p">:</span> <span class="s2">&#34;eyJhbGciOiJSUzx1NiIsImtpZCI6ImRvTHJHVHZqWjNhU05pYlpDYl84cUlscTN...&#34;</span>
<span class="p">}</span></code></pre></div>
<p>or if I need just the token:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">ksecret kube-system/default-token-bf4vd token</code></pre></div>
<p>the output will be raw string ready for copy/paste:</p>

<pre><code>eyJhbGciOiJSUzI1NiI...
</code></pre>

<p>It also supports getting keys with characters reserved by jq like <code>.</code>:</p>

<pre><code>ksecret kube-system/default-token-bf4vd ca.crt
</code></pre>

<p>the output will be a properly formatted certificate:</p>

<pre><code>-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
</code></pre>
]]></content>
		</item>
		
		<item>
			<title>Terraform variable definitions in YAML</title>
			<link>https://trollab.ca/posts/terraform_with_yaml/</link>
			<pubDate>Sat, 14 Aug 2021 14:51:12 -0400</pubDate>
			
			<guid>https://trollab.ca/posts/terraform_with_yaml/</guid>
			<description>In my previous posts dedicated to Terraform I have described various ways of creating resources in bulk. Designing objects composed of basic data types such as string, number, bool, list, and map allows you to decouple &amp;ldquo;the logic&amp;rdquo; from the configuration. As a consequence of extensively applying this approach you can end up having a huge configuration files that are hard to read and navigate through. Naturally one starts wondering if there&amp;rsquo;s a way to break it into manageable chunks.</description>
			<content type="html"><![CDATA[

<p>In my previous posts dedicated to Terraform I have described various ways of creating resources in bulk. Designing objects composed of basic data types such as string, number, bool, list, and map allows you to decouple &ldquo;the logic&rdquo; from the configuration. As a consequence of extensively applying this approach you can end up having a huge configuration files that are hard to read and navigate through. Naturally one starts wondering if there&rsquo;s a way to break it into manageable chunks. For some time I&rsquo;ve been doing this by splitting big configs into smaller files and then using a bash &ldquo;glue code&rdquo; to concat them before plan/apply. It did the trick very well but looked hacky. Every time I had to on-board another person I had to go though a lengthy explanation of what&rsquo;s going on there and why. Recently I came across one Terraform Module from the <a href="https://github.com/terraform-google-modules/cloud-foundation-fabric">Google Cloud Fabric</a> repo. It&rsquo;s author was able to implement the same idea in a much cleaner way. In this post I&rsquo;ll share with you what is it about and how you can use it.</p>

<h2 id="overview">Overview</h2>

<p>As you might have guessed from the title, it has something to do with YAML. Although you could use JSON or even HCL configs just as well, YAML is considered to be better suited for the human reader. Besides due to it&rsquo;s wide use in many CI/CD systems and Kubernetes more people are familiar with it. Therefore the ultimate goal of writing IaC that can be easily read/updated by people who don&rsquo;t have extensive experience using Terraform becomes easily achievable.</p>

<p>The approach is based on usage of 3 built-in terraform functions:</p>

<ul>
<li><a href="https://www.terraform.io/docs/language/functions/fileset.html">fileset</a> will return a set of filenames in the given path based on a given pattern.</li>
<li><a href="https://www.terraform.io/docs/language/functions/file.html">file</a> will return a content of a file on a given path as a string.</li>
<li><a href="https://www.terraform.io/docs/language/functions/yamldecode.html">yamldecode</a> will parse a string and return a HCL compliant value(typically a map/object).</li>
</ul>

<p>Combining them will convert a list of YAML files into a list of objects. The cherry on top of this cake is the ellipsis symbol <code>...</code> which will expand a list of objects into separate arguments if a <code>merge</code> function call te produce the ultimate config, ready to be ingested by the <code>for_each</code> meta-argument of any terraform resource/module.</p>

<h2 id="example">Example</h2>

<p>Consider a scenario when you want to group your firewall rules based on the team name they are created for. Such grouping would keep your IaC repo nice and tidy and would simplify any change review process as it would be easier to see at a glance what file you are changing and what teams would be affected by it:</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ tree ./firewall_configs

./firewall_configs
├── team_a
│   ├── app.yaml
│   └── db.yaml
└── team_b
    ├── backend.yaml
    └── frontend.yaml</code></pre></div>
<p>Each of those files represents a map of firewall_name =&gt; firewall configuration, something like this:</p>

<pre><code>fwl_allow_https_ingres:
  allow:
    - protocol: &quot;tcp&quot;
      ports:
        - &quot;443&quot;
  target_tags:
    - &quot;https_server&quot;
  source_ranges:
    - &quot;0.0.0.0/0&quot;
</code></pre>

<p>The transformation chain could look like this:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="c1"># you start with pointing to where all of your firewall configs are stored</span>
  <span class="n">root_path</span>        <span class="o">=</span> <span class="s2">&#34;./firewall_configs&#34;</span>
  <span class="c1"># then you obtain all individual config paths in all subfolders</span>
  <span class="n">config_list</span>      <span class="o">=</span> <span class="n">fileset</span><span class="p">(</span><span class="n">local</span><span class="o">.</span><span class="n">root_path</span><span class="p">,</span> <span class="s2">&#34;**/*.yaml&#34;</span><span class="p">)</span>
  <span class="c1"># next you read those files and get their string content</span>
  <span class="n">yaml_string_list</span> <span class="o">=</span> <span class="o">[</span> <span class="k">for</span> <span class="nb">name</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">config_list</span><span class="p">:</span> <span class="n">file</span><span class="p">(</span><span class="s2">&#34;${local.root_path}/${name}&#34;</span><span class="p">)</span> <span class="o">]</span>
  <span class="c1"># then you perform the yaml decoding sieving empty values(files with no content or commented content)</span>
  <span class="n">firewall_configs</span> <span class="o">=</span> <span class="o">[</span> <span class="k">for</span> <span class="n">yaml_string</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">yaml_string_list</span><span class="p">:</span> <span class="n">yamldecode</span><span class="p">(</span><span class="n">yaml_string</span><span class="p">)</span> <span class="k">if</span> <span class="n">length</span><span class="p">(</span><span class="n">yaml_string</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">]</span>
  <span class="c1"># and finally merging all configs into a single map</span>
  <span class="n">final_config</span>     <span class="o">=</span> <span class="n">merge</span><span class="p">(</span><span class="n">local</span><span class="o">.</span><span class="n">firewall_configs</span><span class="o">...</span><span class="p">)</span>
<span class="p">}</span></code></pre></div>
<p>I will leave the consumption of such config map out of the scope of this article as it was covered in slightest details in my previous &ldquo;Iterations in Terraform&rdquo; series(<a href="https://trollab.ca/posts/iterative_terraform_part1/">1</a>, <a href="https://trollab.ca/posts/iterative_terraform_part2/">2</a>). The only additional important aspect to keep in mind is that your firewall rule names must follow some sort of naming convention that ensure their uniqueness. The way <code>merge</code> works will consume duplicates without warnings with later key/value pair overwriting earlier ones. If you want to check out the concrete example with Firewall Rules please check out the <a href="https://github.com/terraform-google-modules/cloud-foundation-fabric/tree/master/modules/net-vpc-firewall-yaml">Google Cloud VPC Firewall - Yaml Module</a> in Google&rsquo;s Cloud Foundation Fabric.</p>
]]></content>
		</item>
		
		<item>
			<title>Iterations in Terraform Part 2: list and map expressions</title>
			<link>https://trollab.ca/posts/iterative_terraform_part2/</link>
			<pubDate>Mon, 22 Mar 2021 17:45:05 -0400</pubDate>
			
			<guid>https://trollab.ca/posts/iterative_terraform_part2/</guid>
			<description>List and Map expressions are powerful terraform constructs that can be used to create and transform data structures for better use in your resources and modules. In this post I will show you why, when, and how to use them in your infrastructure code. I will refrain from printing Terraform outputs. Each of the short snippets are simple enough and have sufficient explanation, but you can easily run most of them on your laptop as they are self-sufficient(with the exception of a few snippets that do have resources relying on variables and provider configuration).</description>
			<content type="html"><![CDATA[

<p>List and Map expressions are powerful terraform constructs that can be used to create and transform data structures for better use in your resources and modules. In this post I will show you why, when, and how to use them in your infrastructure code. I will refrain from printing Terraform outputs. Each of the short snippets are simple enough and have sufficient explanation, but you can easily run most of them on your laptop as they are self-sufficient(with the exception of a few snippets that do have resources relying on variables and provider configuration).</p>

<h2 id="list-expressions">List expressions</h2>

<p>List expression is an expression that produces a new list based on the value of an existing list(or map). This concept should be familiar to those of you who knows Python because it is akin to list comprehensions. There is nothing special about expression itself, though their capacity for transforming collections allows to write very concise and flexible infrastructure code. We&rsquo;ll get to this later, and now let&rsquo;s look at couple of examples.</p>

<p>Very simple and straightforward, creates list of numbers 1 to 10:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">output</span> <span class="s2">&#34;the_list&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="o">[</span> <span class="k">for</span> <span class="n">num</span> <span class="k">in</span> <span class="n">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span> <span class="n">num</span> <span class="o">]</span>
<span class="p">}</span></code></pre></div>
<p>Same as before but with a condition that keeps just the odd numbers:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">output</span> <span class="s2">&#34;the_odd_list&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="o">[</span> <span class="k">for</span> <span class="n">num</span> <span class="k">in</span> <span class="n">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span> <span class="n">num</span> <span class="k">if</span> <span class="n">num</span> <span class="sx">% 2 </span> <span class="o">==</span> <span class="mi">1</span><span class="o">]</span>
<span class="p">}</span></code></pre></div>
<p>You can use any kind of expressions to form a list item:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="n">the_list</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="o">]</span>
<span class="p">}</span>

<span class="n">output</span> <span class="s2">&#34;squares_list&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="o">[</span> <span class="k">for</span> <span class="n">num</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">the_list</span><span class="p">:</span> <span class="n">num</span> <span class="o">*</span> <span class="n">num</span> <span class="o">]</span>
<span class="p">}</span>

<span class="n">output</span> <span class="s2">&#34;another_squares_list&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="o">[</span> <span class="k">for</span> <span class="n">num</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">the_list</span><span class="p">:</span> <span class="n">pow</span><span class="p">(</span><span class="n">num</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="o">]</span>
<span class="p">}</span></code></pre></div>
<p>You can create nested lists and then flatten them. Example below gets all possible combinations of elements from two lists. Please note that both <code>number</code> and <code>letter</code> are available in the innermost list expression:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="n">numbers</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span>
  <span class="n">letters</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="s2">&#34;b&#34;</span><span class="p">,</span> <span class="s2">&#34;c&#34;</span><span class="o">]</span>

  <span class="n">all_combinations</span> <span class="o">=</span> <span class="n">flatten</span><span class="p">(</span>
    <span class="o">[</span> <span class="k">for</span> <span class="n">number</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">numbers</span><span class="p">:</span>
      <span class="o">[</span> <span class="k">for</span> <span class="n">letter</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">letters</span><span class="p">:</span>
        <span class="p">{</span>
          <span class="s2">&#34;number&#34;</span> <span class="o">=</span> <span class="n">number</span>
          <span class="s2">&#34;letter&#34;</span> <span class="o">=</span> <span class="n">letter</span>
        <span class="p">}</span>
      <span class="o">]</span>
    <span class="o">]</span>
  <span class="p">)</span>
<span class="p">}</span>

<span class="n">output</span> <span class="s2">&#34;result&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">all_combinations</span>
<span class="p">}</span></code></pre></div>
<p>You are not limited to using variables as a source. Using a list of resources created with <code>count</code> is very common. Resources are referenced in locals block before it was even declared but in Terraform order of code blocks doesn&rsquo;t matter:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="n">service_account_emails</span> <span class="o">=</span> <span class="o">[</span>
    <span class="k">for</span> <span class="n">sa</span> <span class="k">in</span> <span class="n">google_service_account</span><span class="o">.</span><span class="n">service_accounts</span><span class="p">:</span> <span class="n">sa</span><span class="o">.</span><span class="n">email</span>
  <span class="o">]</span>
<span class="p">)</span>

<span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;service_accounts&#34;</span> <span class="p">{</span>
  <span class="n">count</span>        <span class="o">=</span> <span class="mi">5</span>
  <span class="n">account_id</span>   <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">service_account_id</span>
  <span class="n">display_name</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">service_account_display_name</span>
<span class="p">}</span>

<span class="n">output</span> <span class="s2">&#34;service_account_emails&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">service_account_emails</span>
<span class="p">}</span></code></pre></div>
<p>Last but not least you can declare two variables rather than one in the list expression. In that case first one will get an index of the source list element and the second will get the value:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="n">the_list</span>          <span class="o">=</span> <span class="o">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="o">]</span>
  <span class="n">list_with_indexes</span> <span class="o">=</span> <span class="o">[</span> <span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">value</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">the_list</span><span class="p">:</span> <span class="s2">&#34;${index}-${value}&#34;</span> <span class="o">]</span>
<span class="p">}</span>

<span class="n">output</span> <span class="s2">&#34;list_with_indexes&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">list_with_indexes</span>
<span class="p">}</span></code></pre></div>
<p>Using this syntax you can use maps as a source collection for the list expression. Your variables will obtain element key and value(instead of index and value as in previous example):</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="n">the_map</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span>
    <span class="n">b</span> <span class="o">=</span> <span class="mi">2</span>
  <span class="p">}</span>
  <span class="n">list_from_map</span> <span class="o">=</span> <span class="o">[</span> <span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">the_map</span><span class="p">:</span> <span class="s2">&#34;${key} = ${value}&#34;</span> <span class="o">]</span>
<span class="p">}</span>

<span class="n">output</span> <span class="s2">&#34;the_new_list&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">list_from_map</span>
<span class="p">}</span></code></pre></div>
<p>Now when we had a good look at the syntax and all sorts of list expression variations, it&rsquo;s time to see some practical example.</p>

<p>Lets assume we have a team of individuals that are working on the same project but require individual sandboxes in the cloud to perform their daily activities. In GCP terms that would be projects. Each developer may request multiple Service Accounts in each of their projects. At least one of those Service Accounts will need a JSON key(for Terraform) created to allow developers to deploy resources within their sandboxes.</p>

<p>As you can see, the request is generic so we should be able to leverage same set of resources to deploy all sandboxes at once. Although it&rsquo;s not a software development, DRY principle applies here just as much. At the same time the number of projects and service accounts in each of them is unknown so the code needs to be flexible, no hard-coded stuff. The cherry on the cake will be the optional flag in a Service Account config to signal that it needs a JSON authentication key generated.</p>

<p>Here&rsquo;s the snippet to complete the task:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="c1"># we start with a `projects` map, where key is project id and value is it&#39;s config</span>
  <span class="n">projects</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">project_1</span> <span class="o">=</span> <span class="p">{</span>
      <span class="c1"># `service_accounts` is the list of service accounts to create</span>
      <span class="c1"># in a given project</span>
      <span class="n">service_accounts</span> <span class="o">=</span> <span class="o">[</span>
        <span class="p">{</span>
          <span class="n">account_id</span> <span class="o">=</span> <span class="s2">&#34;iac-deployer&#34;</span>
          <span class="c1"># `create_key` defines if we should create a key for the service account</span>
          <span class="n">create_key</span> <span class="o">=</span> <span class="kp">true</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="n">account_id</span> <span class="o">=</span> <span class="s2">&#34;cloud-function&#34;</span>
        <span class="p">}</span>
      <span class="o">]</span>
    <span class="p">}</span>
    <span class="n">project_2</span> <span class="o">=</span> <span class="p">{</span>
      <span class="c1"># `service_accounts` is the list of service accounts to create in a project</span>
      <span class="n">service_accounts</span> <span class="o">=</span> <span class="o">[</span>
        <span class="p">{</span>
          <span class="n">account_id</span> <span class="o">=</span> <span class="s2">&#34;iac-deployer&#34;</span>
          <span class="n">create_key</span> <span class="o">=</span> <span class="kp">true</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="n">account_id</span> <span class="o">=</span> <span class="s2">&#34;testing-vm&#34;</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="n">account_id</span> <span class="o">=</span> <span class="s2">&#34;testing-cloudsql&#34;</span>
        <span class="p">}</span>
      <span class="o">]</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="c1"># we&#39;ll start with transforming the source map into more &#34;consumable&#34; form</span>
  <span class="c1"># one list for all service accounts that needs keys</span>
  <span class="c1"># the default `false` value provided in the `lookup` makes</span>
  <span class="c1"># the `create_key` flag optional</span>
  <span class="n">sa_with_keys</span> <span class="o">=</span> <span class="n">flatten</span><span class="p">(</span>
    <span class="o">[</span> <span class="k">for</span> <span class="n">project_id</span><span class="p">,</span> <span class="n">config</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">projects</span><span class="p">:</span>
      <span class="o">[</span> <span class="k">for</span> <span class="n">service_account</span> <span class="k">in</span> <span class="n">lookup</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="s2">&#34;service_accounts&#34;</span><span class="p">,</span> <span class="o">[]</span><span class="p">):</span>
        <span class="p">{</span>
          <span class="s2">&#34;project_id&#34;</span> <span class="o">=</span> <span class="n">project_id</span>
          <span class="s2">&#34;account_id&#34;</span> <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">service_account</span><span class="p">,</span> <span class="s2">&#34;account_id&#34;</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">if</span> <span class="n">lookup</span><span class="p">(</span><span class="n">service_account</span><span class="p">,</span> <span class="s2">&#34;create_key&#34;</span><span class="p">,</span> <span class="kp">false</span><span class="p">)</span> <span class="o">==</span> <span class="kp">true</span>
      <span class="o">]</span>
    <span class="o">]</span>
  <span class="p">)</span>

  <span class="c1"># and one list for all service accounts without keys</span>
  <span class="n">sa_without_keys</span> <span class="o">=</span> <span class="n">flatten</span><span class="p">(</span>
    <span class="o">[</span> <span class="k">for</span> <span class="n">project_id</span><span class="p">,</span> <span class="n">config</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">projects</span><span class="p">:</span>
      <span class="o">[</span> <span class="k">for</span> <span class="n">service_account</span> <span class="k">in</span> <span class="n">lookup</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="s2">&#34;service_accounts&#34;</span><span class="p">,</span> <span class="o">[]</span><span class="p">):</span>
        <span class="p">{</span>
          <span class="s2">&#34;project_id&#34;</span> <span class="o">=</span> <span class="n">project_id</span>
          <span class="s2">&#34;account_id&#34;</span> <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">service_account</span><span class="p">,</span> <span class="s2">&#34;account_id&#34;</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">if</span> <span class="n">lookup</span><span class="p">(</span><span class="n">service_account</span><span class="p">,</span> <span class="s2">&#34;create_key&#34;</span><span class="p">,</span> <span class="kp">false</span><span class="p">)</span> <span class="o">==</span> <span class="kp">false</span>
      <span class="o">]</span>
    <span class="o">]</span>
  <span class="p">)</span>
<span class="p">}</span>

<span class="c1"># now we can proceed to service account creation</span>
<span class="c1"># not the best example of resource re-usability but we&#39;ll get another shot</span>
<span class="c1"># with map expressions</span>
<span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;with_keys&#34;</span> <span class="p">{</span>
  <span class="n">count</span>        <span class="o">=</span> <span class="n">length</span><span class="p">(</span><span class="n">local</span><span class="o">.</span><span class="n">sa_with_keys</span><span class="p">)</span>
  <span class="n">project</span>      <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">sa_with_keys</span><span class="o">[</span><span class="n">count</span><span class="o">.</span><span class="n">index</span><span class="o">].</span><span class="n">project_id</span>
  <span class="n">account_id</span>   <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">sa_with_keys</span><span class="o">[</span><span class="n">count</span><span class="o">.</span><span class="n">index</span><span class="o">].</span><span class="n">account_id</span>
<span class="p">}</span>

<span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;without_keys&#34;</span> <span class="p">{</span>
  <span class="n">count</span>        <span class="o">=</span> <span class="n">length</span><span class="p">(</span><span class="n">local</span><span class="o">.</span><span class="n">sa_without_keys</span><span class="p">)</span>
  <span class="n">project</span>      <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">sa_without_keys</span><span class="o">[</span><span class="n">count</span><span class="o">.</span><span class="n">index</span><span class="o">].</span><span class="n">project_id</span>
  <span class="n">account_id</span>   <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">sa_without_keys</span><span class="o">[</span><span class="n">count</span><span class="o">.</span><span class="n">index</span><span class="o">].</span><span class="n">account_id</span>
<span class="p">}</span>

<span class="c1"># and the keys</span>
<span class="c1"># because both keys and service accounts are based on the same list we can</span>
<span class="c1"># pull this trick and be sure that right service accounts will get keys</span>
<span class="n">resource</span> <span class="s2">&#34;google_service_account_key&#34;</span> <span class="s2">&#34;with_keys&#34;</span> <span class="p">{</span>
  <span class="n">count</span>              <span class="o">=</span> <span class="n">length</span><span class="p">(</span><span class="n">local</span><span class="o">.</span><span class="n">sa_with_keys</span><span class="p">)</span>
  <span class="n">service_account_id</span> <span class="o">=</span> <span class="n">google_service_account</span><span class="o">.</span><span class="n">with_keys</span><span class="o">[</span><span class="n">count</span><span class="o">.</span><span class="n">index</span><span class="o">].</span><span class="n">name</span>
<span class="p">}</span></code></pre></div>
<p>This was rather small snippet but it is sufficient to illustrate how the inputs can be composed to reflect the user requirements, and then transformed to fit the resource block constraints.</p>

<h2 id="map-expressions">Map expressions</h2>

<p>Map expressions are almost identical to list expressions with the only difference that they create a maps and have slightly different syntax. As I mentioned in <a href="/posts/iterative_terraform_part1/">Part 1</a>, using maps is preferable way because resources created with from maps(for_each) are less fragile than lists(count). Maps expressions are similar to Python&rsquo;s dictionary comprehensions. Here are couple of examples:</p>

<p>Create a map from the list:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">output</span> <span class="s2">&#34;the_list&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="p">{</span> <span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">num</span> <span class="k">in</span> <span class="n">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span> <span class="n">index</span> <span class="o">=&gt;</span> <span class="n">num</span> <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>Previous example was not particularly useful due to the fact it use indexes as keys. Though sometimes we do want to convert list to map to leverage <code>for_each</code> versatility. In such cases finding a way to create a unique key without relying on list item index is paramount:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="n">the_list</span> <span class="o">=</span> <span class="o">[</span>
    <span class="p">{</span>
      <span class="n">project_id</span> <span class="o">=</span> <span class="s2">&#34;project_1&#34;</span>
      <span class="n">account_id</span> <span class="o">=</span> <span class="s2">&#34;account_1&#34;</span>
    <span class="p">},</span>
    <span class="p">{</span>
      <span class="n">project_id</span> <span class="o">=</span> <span class="s2">&#34;project_2&#34;</span>
      <span class="n">account_id</span> <span class="o">=</span> <span class="s2">&#34;account_2&#34;</span>
    <span class="p">},</span>
  <span class="o">]</span>
<span class="p">}</span>
<span class="n">output</span> <span class="s2">&#34;the_map&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="p">{</span>
    <span class="k">for</span> <span class="n">elem</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">the_list</span><span class="p">:</span> <span class="s2">&#34;${elem.project_id}-${elem.account_id}&#34;</span> <span class="o">=&gt;</span> <span class="n">elem</span>
  <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>Your map expressions can have conditional expressions to filter unwanted items. You can check either key or value(including some of it&rsquo;s sub-elements):</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="n">the_map</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">el1</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nb">name</span> <span class="o">=</span> <span class="s2">&#34;John&#34;</span>
      <span class="n">team</span> <span class="o">=</span> <span class="s2">&#34;Dev&#34;</span>
    <span class="p">}</span>
    <span class="n">el2</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nb">name</span> <span class="o">=</span> <span class="s2">&#34;Jane&#34;</span>
      <span class="n">team</span> <span class="o">=</span> <span class="s2">&#34;Ops&#34;</span>
    <span class="p">}</span>
    <span class="n">el3</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nb">name</span> <span class="o">=</span> <span class="s2">&#34;Jim&#34;</span>
      <span class="n">team</span> <span class="o">=</span> <span class="s2">&#34;Ops&#34;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="n">output</span> <span class="s2">&#34;filtered_map&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="p">{</span> <span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">the_map</span><span class="p">:</span> <span class="n">key</span> <span class="o">=&gt;</span> <span class="n">value</span> <span class="k">if</span> <span class="n">value</span><span class="o">.</span><span class="n">team</span> <span class="o">==</span> <span class="s2">&#34;Ops&#34;</span> <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>All previous examples transformed lists to maps. Of course maps can be created from other maps. Following example is a bit superficial and brittle, though very clear. It inverts keys and values:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="n">the_map</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">a</span> <span class="o">=</span> <span class="s2">&#34;z&#34;</span>
    <span class="n">b</span> <span class="o">=</span> <span class="s2">&#34;y&#34;</span>
    <span class="n">c</span> <span class="o">=</span> <span class="s2">&#34;x&#34;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="n">output</span> <span class="s2">&#34;the_new_map&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="p">{</span> <span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">the_map</span><span class="p">:</span> <span class="n">value</span> <span class="o">=&gt;</span> <span class="n">key</span> <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>Now for a more practical example let&rsquo;s look again at the Service Account creation use case and try to improve it. We want our code to be DRY but unfortunately first attempt had some duplicated resources which doesn&rsquo;t look nice. In addition to that Service Accounts and their keys are one of those resource which suffers a lot from the <code>count</code> flaw(deletion of the item in the middle of the list). We definitely need a map here.</p>

<p>The snippet below use a combination of list and map expressions to get a list of objects with all information about each individual service account to create and then merges it into a single map. Each map item must have a unique key. In this case it is <code>&quot;${project_id}/${service_account.account_id}&quot;</code> and its sole purpose is to provide this uniqueness. Although it is not consumed in the <code>service_account</code> resource it will be present in the Terraform state Resource ID thus will come handy when referencing <code>service_account_id</code> in the <code>service_account_key</code> resource.
Please also note that the <code>merge</code> function of terraform accepts multiple maps so we have to use ellipsis operator(<code>...</code>) to unpack a list as a set of individual values:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="n">projects</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">project_1</span> <span class="o">=</span> <span class="p">{</span>
      <span class="n">service_accounts</span> <span class="o">=</span> <span class="o">[</span>
        <span class="p">{</span>
          <span class="n">account_id</span> <span class="o">=</span> <span class="s2">&#34;terraform&#34;</span>
          <span class="n">create_key</span> <span class="o">=</span> <span class="kp">true</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="n">account_id</span> <span class="o">=</span> <span class="s2">&#34;cloud-function&#34;</span>
        <span class="p">}</span>
      <span class="o">]</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="n">service_accounts</span> <span class="o">=</span> <span class="n">merge</span><span class="p">(</span>
    <span class="o">[</span> <span class="k">for</span> <span class="n">project_id</span><span class="p">,</span> <span class="n">config</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">projects</span><span class="p">:</span>
      <span class="p">{</span> <span class="k">for</span> <span class="n">service_account</span> <span class="k">in</span> <span class="n">lookup</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="s2">&#34;service_accounts&#34;</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span> <span class="p">:</span> <span class="s2">&#34;${project_id}/${service_account.account_id}&#34;</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="s2">&#34;project_id&#34;</span> <span class="o">=</span> <span class="n">project_id</span>
          <span class="s2">&#34;account_id&#34;</span> <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">service_account</span><span class="p">,</span> <span class="s2">&#34;account_id&#34;</span><span class="p">)</span>
          <span class="s2">&#34;create_key&#34;</span> <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">service_account</span><span class="p">,</span> <span class="s2">&#34;create_key&#34;</span><span class="p">,</span> <span class="kp">false</span><span class="p">)</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="o">]...</span>
  <span class="p">)</span>
<span class="p">}</span>

<span class="c1"># so now we can create all service accounts at once</span>
<span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;default&#34;</span> <span class="p">{</span>
  <span class="n">for_each</span>   <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">service_accounts</span>
  <span class="n">project</span>    <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">project_id</span>
  <span class="n">account_id</span> <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">account_id</span>
<span class="p">}</span>


<span class="c1"># an alternative of creating multiple local variables would be a simple filter</span>
<span class="c1"># expression within the resource block</span>
<span class="n">resource</span> <span class="s2">&#34;google_service_account_key&#34;</span> <span class="s2">&#34;default&#34;</span> <span class="p">{</span>
  <span class="n">for_each</span>           <span class="o">=</span> <span class="p">{</span> <span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">service_accounts</span><span class="p">:</span> <span class="n">key</span> <span class="o">=&gt;</span> <span class="n">value</span> <span class="k">if</span> <span class="n">value</span><span class="o">.</span><span class="n">create_key</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">}</span>
  <span class="n">service_account_id</span> <span class="o">=</span> <span class="n">google_service_account</span><span class="o">.</span><span class="n">default</span><span class="o">[</span><span class="n">each</span><span class="o">.</span><span class="n">key</span><span class="o">].</span><span class="n">name</span>
<span class="p">}</span></code></pre></div>
<h2 id="conclusion">Conclusion</h2>

<p>List and map expressions are indispensable tools in the toolbox of an infrastructure engineer writing IaC with Terraform. Imagining how to compose the data in a coherent way and then transforming it to fit the resource or module interface is essential for writing concise, flexible and repeatable infrastructure code. I hope this material will help you to feel comfortable working with list and map expressions and creating resources in bulk. Good luck!</p>
]]></content>
		</item>
		
		<item>
			<title>Iterations in Terraform Part 1: meta-arguments</title>
			<link>https://trollab.ca/posts/iterative_terraform_part1/</link>
			<pubDate>Sat, 13 Mar 2021 20:00:00 -0500</pubDate>
			
			<guid>https://trollab.ca/posts/iterative_terraform_part1/</guid>
			<description>In this post I&amp;rsquo;m going to cover two extremely useful meta-arguments in Terraform - count and for_each, used to create resources iteratively. I&amp;rsquo;ll go through their benefits, drawbacks, and demonstrate a comprehensive example of how to use them.
Theory Let&amp;rsquo;s start with a reviewing some theoretic review. Meta-arguments are the kind of arguments that can be used in all resources and modules regardless of what they do. There are five meta-arguments at the moment with count and for_each being the most often used.</description>
			<content type="html"><![CDATA[

<p>In this post I&rsquo;m going to cover two extremely useful meta-arguments in Terraform - <code>count</code> and <code>for_each</code>, used to create resources iteratively. I&rsquo;ll go through their benefits, drawbacks, and demonstrate a comprehensive example of how to use them.</p>

<h2 id="theory">Theory</h2>

<p>Let&rsquo;s start with a reviewing some theoretic review. Meta-arguments are the kind of arguments that can be used in all resources and modules regardless of what they do. There are five meta-arguments at the moment with <code>count</code> and <code>for_each</code> being the most often used.</p>

<h3 id="count"><code>count</code></h3>

<p>The oldest way to create resources iteratively is to use meta-argument <code>count</code>. It takes a number and creates that exact number of copies of the resources. On each iteration it exposes an object <code>count</code> with a single argument <code>index</code> inside of the resource block. By accessing <code>count.index</code> you can get an index number of current iteration. You would use this index to somehow ensure that resource arguments which have to be unique - are unique. Some examples of that would be using <code>count.index</code> interpolation in your strings or as a way to pick an item from some list.</p>

<p>There are multiple ways you can use <code>count</code> in your code:</p>

<ol>
<li><p>The most primitive one is to create identical resources with slightly different names:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;service_accounts&#34;</span> <span class="p">{</span>
  <span class="n">count</span>        <span class="o">=</span> <span class="n">length</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
  <span class="n">account_id</span>   <span class="o">=</span> <span class="s2">&#34;${var.account_id}-${count.index}&#34;</span>
  <span class="n">display_name</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">display_name</span>
<span class="p">}</span></code></pre></div></li>

<li><p>More useful scenario is to reference an item in the list of config objects. This way each of the resources you create can have fully customizable config. Please also note that using <code>length</code> function acts as a toggle in a way. By providing an empty list you disable resource creation.</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">variable</span> <span class="s2">&#34;service_accounts&#34;</span> <span class="p">{</span>
  <span class="n">type</span>    <span class="o">=</span> <span class="n">list</span><span class="p">(</span><span class="n">map</span><span class="p">(</span><span class="n">string</span><span class="p">))</span>
  <span class="n">default</span> <span class="o">=</span> <span class="o">[</span>
    <span class="p">{</span><span class="n">account_id</span> <span class="o">=</span> <span class="s2">&#34;one-sa&#34;</span><span class="p">,</span> <span class="n">display_name</span> <span class="o">=</span> <span class="s2">&#34;Custom SA&#34;</span><span class="p">},</span>
    <span class="p">{</span><span class="n">account_id</span> <span class="o">=</span> <span class="s2">&#34;two-sa&#34;</span><span class="p">,</span> <span class="n">display_name</span> <span class="o">=</span> <span class="s2">&#34;Yet another SA&#34;</span><span class="p">},</span>
  <span class="o">]</span>
<span class="p">}</span>

<span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;service_accounts&#34;</span> <span class="p">{</span>
  <span class="n">count</span>        <span class="o">=</span> <span class="n">length</span><span class="p">(</span><span class="n">var</span><span class="o">.</span><span class="n">service_accounts</span><span class="p">)</span>
  <span class="n">account_id</span>   <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">service_accounts</span><span class="o">[</span><span class="n">count</span><span class="o">.</span><span class="n">index</span><span class="o">].</span><span class="n">account_id</span>
  <span class="n">display_name</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">service_accounts</span><span class="o">[</span><span class="n">count</span><span class="o">.</span><span class="n">index</span><span class="o">].</span><span class="n">display_name</span>
<span class="p">}</span></code></pre></div></li>

<li><p>Sometimes you are in a situations when you need to create resource only if certain conditions are met(eg certain parameter is set to <code>true</code>). So it can act as a toggle if combined with conditional expression:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;service_account&#34;</span> <span class="p">{</span>
  <span class="n">count</span>        <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">create_service_account</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="mi">1</span> <span class="p">:</span> <span class="mi">0</span>
  <span class="n">account_id</span>   <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">service_account_id</span>
  <span class="n">display_name</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">service_account_display_name</span>
<span class="p">}</span></code></pre></div></li>

<li><p>You can also combine toggle feature with creation from the list of configs:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;service_accounts&#34;</span> <span class="p">{</span>
  <span class="n">count</span>        <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">create_service_accounts</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="n">length</span><span class="p">(</span><span class="n">var</span><span class="o">.</span><span class="n">service_accounts</span><span class="p">)</span> <span class="p">:</span> <span class="mi">0</span>
  <span class="n">account_id</span>   <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">service_accounts</span><span class="o">[</span><span class="n">count</span><span class="o">.</span><span class="n">index</span><span class="o">].</span><span class="n">account_id</span>
  <span class="n">display_name</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">service_accounts</span><span class="o">[</span><span class="n">count</span><span class="o">.</span><span class="n">index</span><span class="o">].</span><span class="n">display_name</span>
<span class="p">}</span></code></pre></div></li>
</ol>

<p>As you can see it is very easy to use and quite powerful too. Unfortunately there is one particularly nasty side effect. Resources created using count are referenced in the state by their list index. If you delete a single item in the middle of the underlying list, all the items to the right of it will have their index recalculated, causing recreation of all those resources. Nonetheless it still can be used in scenarios when underlying list is not supposed to change frequently or items will be deleted only from the end of the list(scaling up/down).</p>

<p>If you want to reference an argument of a resource created with <code>count</code> in other resources/outputs, you will have to use a list index or splat expression.</p>

<p>One item:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;service_accounts&#34;</span> <span class="p">{</span>
  <span class="n">count</span>        <span class="o">=</span> <span class="n">length</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
  <span class="n">account_id</span>   <span class="o">=</span> <span class="s2">&#34;${var.account_id}-${count.index}&#34;</span>
  <span class="n">display_name</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">display_name</span>
<span class="p">}</span>

<span class="n">output</span> <span class="s2">&#34;service_account_email&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="n">google_service_account</span><span class="o">.</span><span class="n">service_accounts</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">email</span>
<span class="p">}</span></code></pre></div>
<p>All items:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="c1"># ...</span>
<span class="n">output</span> <span class="s2">&#34;service_account_emails&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="n">google_service_account</span><span class="o">.</span><span class="n">service_accounts</span><span class="o">[*].</span><span class="n">email</span>
<span class="p">}</span></code></pre></div>
<h3 id="for-each"><code>for_each</code></h3>

<p>This meta-argument is relatively new. Originally <code>for_each</code> was introduced in Terraform 0.12.0 as a way of creating &ldquo;dynamic&rdquo; resource blocks (blocks you can use within the same resource multiple times) but since Terraform 0.12.6 it&rsquo;s usage was extended as a <code>count</code> alternative for resource creation. On each iteration it exposes <code>each</code> object with two arguments - <code>key</code> and <code>value</code>, which will have map element key and value correspondingly. Although map is the main resource type to use with <code>for_each</code> you can also use it with sets (or lists converted to sets with <code>toset</code> function).</p>

<p>When used with maps, <code>for_each</code> is not a subject to the side effect that <code>count</code> has. Due to the fact that every item in the map has a unique key and resources created with <code>for_each</code> are referenced in the state by the key name of a corresponding map element, deleting an item from the map will not affect other map elements resulting in more predictable &ldquo;plan&rdquo;.</p>

<p>The main use case scenario is simple creation of multiple resources, but there is also a way you can implement a toggle feature.</p>

<ol>
<li><p>Creating multiple resources with fully customizable configs. Please also note that using empty map works as toggle by itself just like the empty list did in <code>count = length(...</code>.</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">variable</span> <span class="s2">&#34;service_accounts&#34;</span> <span class="p">{</span>
  <span class="n">type</span>    <span class="o">=</span> <span class="n">map</span><span class="p">(</span><span class="n">map</span><span class="p">(</span><span class="n">string</span><span class="p">))</span>
  <span class="n">default</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s2">&#34;one-sa&#34;</span> <span class="o">=</span> <span class="p">{</span>
      <span class="n">display_name</span> <span class="o">=</span> <span class="s2">&#34;Custom SA&#34;</span>
      <span class="n">description</span>  <span class="o">=</span> <span class="s2">&#34;A custom SA&#34;</span>
      <span class="p">},</span>
    <span class="s2">&#34;two-sa&#34;</span> <span class="o">=</span> <span class="p">{</span>
      <span class="n">display_name</span> <span class="o">=</span> <span class="s2">&#34;Yet another SA&#34;</span>
      <span class="n">description</span>  <span class="o">=</span> <span class="s2">&#34;A custom SA&#34;</span>
    <span class="p">},</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;service_account&#34;</span> <span class="p">{</span>
  <span class="n">for_each</span>     <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">service_accounts</span>
  <span class="n">account_id</span>   <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">key</span>
  <span class="n">display_name</span> <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">display_name</span>
  <span class="n">description</span>  <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">description</span>
<span class="p">}</span></code></pre></div></li>

<li><p>Create single resources with a toggle. Looks ugly and does exactly the same thing as <code>count</code> toggle. I would not recommend using it but for the sake of completeness:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;service_accounts&#34;</span> <span class="p">{</span>
  <span class="n">for_each</span>     <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">create_service_accounts</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="n">map</span><span class="p">(</span><span class="n">var</span><span class="o">.</span><span class="n">account_id</span><span class="p">,</span> <span class="n">var</span><span class="o">.</span><span class="n">display_name</span><span class="p">)</span> <span class="p">:</span> <span class="p">{}</span>
  <span class="n">account_id</span>   <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">key</span>
  <span class="n">display_name</span> <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">value</span>
<span class="p">}</span></code></pre></div></li>

<li><p>Create multiple resources with a toggle:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">resource</span> <span class="s2">&#34;google_service_account&#34;</span> <span class="s2">&#34;service_accounts&#34;</span> <span class="p">{</span>
  <span class="n">for_each</span>     <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">create_service_accounts</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="n">var</span><span class="o">.</span><span class="n">service_accounts</span> <span class="p">:</span> <span class="p">{}</span>
  <span class="n">account_id</span>   <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">key</span>
  <span class="n">display_name</span> <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">display_name</span>
  <span class="n">description</span>  <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">description</span>
<span class="p">}</span></code></pre></div></li>
</ol>

<p>When it comes to referencing resource values in the outputs,
If you want to reference an argument of a resource created with <code>for_each</code>, you would need to address each resource by the key it was created with:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">output</span> <span class="s2">&#34;service_account_email&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="n">google_service_account</span><span class="o">.</span><span class="n">service_accounts</span><span class="o">[</span><span class="s2">&#34;one-sa&#34;</span><span class="o">].</span><span class="n">email</span>
<span class="p">}</span></code></pre></div>
<p>Splat expressions will not work with maps but you can use list and map expressions to achieve the same result:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">output</span> <span class="s2">&#34;service_account_email&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="o">[</span>
    <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="k">in</span> <span class="n">google_service_account</span><span class="o">.</span><span class="n">service_accounts</span><span class="p">:</span> <span class="n">v</span><span class="o">.</span><span class="n">email</span>
  <span class="o">]</span>
<span class="p">}</span></code></pre></div>
<p>which would produce following output:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">service_account_email</span> <span class="o">=</span> <span class="o">[</span>
  <span class="s2">&#34;one-sa@.....&#34;</span><span class="p">,</span>
  <span class="s2">&#34;two-sa@.....&#34;</span>
<span class="o">]</span></code></pre></div>
<p>or</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">output</span> <span class="s2">&#34;service_account_email&#34;</span> <span class="p">{</span>
  <span class="n">value</span> <span class="o">=</span> <span class="p">{</span>
    <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="k">in</span> <span class="n">google_service_account</span><span class="o">.</span><span class="n">service_accounts</span><span class="p">:</span>
      <span class="n">k</span> <span class="o">=&gt;</span> <span class="n">v</span><span class="o">.</span><span class="n">email</span>
  <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>which would produce following output:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">service_account_email</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">one</span><span class="o">-</span><span class="n">sa</span> <span class="o">=</span> <span class="s2">&#34;one-sa@.....&#34;</span><span class="p">,</span>
  <span class="n">two</span><span class="o">-</span><span class="n">sa</span> <span class="o">=</span> <span class="s2">&#34;two-sa@.....&#34;</span>
<span class="p">}</span></code></pre></div>
<p>There is one thing you should be aware of when using <code>for_each</code>. It will not be able to calculate the plan if the keys of the underlying map are composed from the outputs of any other resource. Consider the following snippet:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">locals</span> <span class="p">{</span>
  <span class="n">numbers</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;one&#34;</span><span class="p">,</span> <span class="s2">&#34;two&#34;</span><span class="p">,</span> <span class="s2">&#34;three&#34;</span><span class="o">]</span>
  <span class="n">the_map</span> <span class="o">=</span> <span class="p">{</span>
    <span class="k">for</span> <span class="n">number</span> <span class="k">in</span> <span class="n">local</span><span class="o">.</span><span class="n">numbers</span><span class="p">:</span>
      <span class="s2">&#34;${number}-${random_string.random.result}&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;some text&#34;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="n">resource</span> <span class="s2">&#34;random_string&#34;</span> <span class="s2">&#34;random&#34;</span> <span class="p">{</span>
  <span class="n">length</span>           <span class="o">=</span> <span class="mi">2</span>
  <span class="n">special</span>          <span class="o">=</span> <span class="kp">false</span>
<span class="p">}</span>

<span class="n">resource</span> <span class="s2">&#34;local_file&#34;</span> <span class="s2">&#34;foo&#34;</span> <span class="p">{</span>
  <span class="n">for_each</span> <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">the_map</span>
  <span class="n">content</span>  <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">value</span>
  <span class="n">filename</span> <span class="o">=</span> <span class="n">each</span><span class="o">.</span><span class="n">key</span>
<span class="p">}</span></code></pre></div>
<p>Even though it is clear to the human reader that the number of elements in the map does not depend on the result of <code>random_string</code> resource, terraform will not be able to create the plan. So try to avoid such scenarios.</p>

<pre><code>Error: Invalid for_each argument

  on main.tf line 13, in resource &quot;local_file&quot; &quot;foo&quot;:
  13:     for_each = local.the_map

The &quot;for_each&quot; value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the for_each depends on.
</code></pre>

<h2 id="practice">Practice</h2>

<p>Now as a practical example I will assemble some primitive certificate creation tool. It is not production-grade but might come handy for experiments in the home lab. The code will allow to either generate the CA or import the external one. I will be using TLS and Local providers with most of the aforementioned ways of using <code>count</code> and <code>for_each</code>.</p>

<p>Let&rsquo;s start with the variable declaration. I&rsquo;m going to use two composite variables, one for CA configuration and one for all leaf certificates.</p>

<p><code>cat variables.tf</code></p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">variable</span> <span class="s2">&#34;root_ca&#34;</span> <span class="p">{</span>
  <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;Root CA Specification&#34;</span>
  <span class="n">type</span> <span class="o">=</span> <span class="n">object</span><span class="p">({</span>
    <span class="n">algorithm</span>     <span class="o">=</span> <span class="n">string</span>
    <span class="n">rsa_bits</span>      <span class="o">=</span> <span class="n">number</span>
    <span class="n">ecdsa_curve</span>   <span class="o">=</span> <span class="n">string</span>
    <span class="n">external_cert</span> <span class="o">=</span> <span class="n">string</span>
    <span class="n">external_key</span>  <span class="o">=</span> <span class="n">string</span>
    <span class="n">subject</span>       <span class="o">=</span> <span class="n">map</span><span class="p">(</span><span class="n">string</span><span class="p">)</span>
  <span class="p">})</span>
  <span class="n">default</span> <span class="o">=</span> <span class="n">null</span>
<span class="p">}</span>

<span class="n">variable</span> <span class="s2">&#34;certificates&#34;</span> <span class="p">{</span>
  <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;Leaf Certs Specification&#34;</span>
  <span class="n">default</span>     <span class="o">=</span> <span class="p">{}</span>
<span class="p">}</span></code></pre></div>
<p>You have probably noticed one significant difference between the <code>root_ca</code> variable and the <code>certificates</code>. First one has an <code>object</code> type declared while second has none. While <code>certificates</code> is a map of <code>object</code>s, keeping its type unset allows me to have &ldquo;optional parameters&rdquo; in the config. Omitting such parameters will not be interpreted by terraform as an error and will make the <code>tfvars</code> file significantly shorter in some cases. Terraform is smart enough to deduce variable type automatically during the plan/apply. If I were using strictly defined type I would have to set all key/value pairs declared in the object type. Even empty ones would have to be explicitly set to <code>null</code>.</p>

<p>Next I&rsquo;ll put some values to have a better perspective on the possible way of consuming them:</p>

<p><code>cat terraform.tfvars</code></p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">root_ca</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">algorithm</span>     <span class="o">=</span> <span class="s2">&#34;RSA&#34;</span>
  <span class="n">rsa_bits</span>      <span class="o">=</span> <span class="mi">4096</span>
  <span class="n">ecdsa_curve</span>   <span class="o">=</span> <span class="n">null</span>
  <span class="n">external_cert</span> <span class="o">=</span> <span class="n">null</span>
  <span class="n">external_key</span>  <span class="o">=</span> <span class="n">null</span>
  <span class="n">subject</span> <span class="o">=</span> <span class="p">{</span>
    <span class="no">CN</span> <span class="o">=</span> <span class="s2">&#34;Root CA&#34;</span>
    <span class="n">O</span>  <span class="o">=</span> <span class="s2">&#34;Example Org&#34;</span>
  <span class="p">}</span>
<span class="p">}</span>
<span class="n">certificates</span> <span class="o">=</span> <span class="p">{</span>
  <span class="s2">&#34;example.com&#34;</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">algorithm</span> <span class="o">=</span> <span class="s2">&#34;RSA&#34;</span>
    <span class="n">rsa_bits</span>  <span class="o">=</span> <span class="mi">2048</span>
    <span class="n">subject</span> <span class="o">=</span> <span class="p">{</span>
      <span class="no">CN</span> <span class="o">=</span> <span class="s2">&#34;Example Leaf Cert&#34;</span>
      <span class="n">O</span>  <span class="o">=</span> <span class="s2">&#34;Example Org&#34;</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="s2">&#34;website.com&#34;</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">algorithm</span> <span class="o">=</span> <span class="s2">&#34;ECDSA&#34;</span>
    <span class="n">subject</span> <span class="o">=</span> <span class="p">{</span>
      <span class="no">CN</span> <span class="o">=</span> <span class="s2">&#34;Some Website&#34;</span>
      <span class="n">O</span>  <span class="o">=</span> <span class="s2">&#34;Example Org&#34;</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="s2">&#34;anotherone.com&#34;</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">subject</span> <span class="o">=</span> <span class="p">{</span>
      <span class="no">CN</span> <span class="o">=</span> <span class="s2">&#34;Leaf Cert&#34;</span>
      <span class="n">O</span>  <span class="o">=</span> <span class="s2">&#34;Example Org&#34;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>As you can see, the keys of <code>certificates</code> variable are domain names which make them unique to a certain degree. The &ldquo;website.com&rdquo; value has only <code>algorithm</code> set and &ldquo;anotherone.com&rdquo; omits even that. In both cases missing values will be calculated automatically according to the sane defaults I will put in my <code>main.tf</code>. At the same time omitting <code>root_ca.ecdsa_curve</code> would be an error.</p>

<p>Finally the heart of this tool, the resources.</p>

<p><code>cat main.tf</code></p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="c1"># the locals block is prefect for reusable expressions, it has a toggle which</span>
<span class="c1"># defines if CA cert and key must be generated or imported from file</span>
<span class="n">locals</span> <span class="p">{</span>
  <span class="n">use_external_cert</span> <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="p">,</span> <span class="s2">&#34;external_key&#34;</span><span class="p">,</span> <span class="n">null</span><span class="p">)</span> <span class="o">!=</span> <span class="n">null</span> <span class="o">&amp;&amp;</span> <span class="n">lookup</span><span class="p">(</span><span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="p">,</span> <span class="s2">&#34;external_cert&#34;</span><span class="p">,</span> <span class="n">null</span><span class="p">)</span> <span class="o">!=</span> <span class="n">null</span>
<span class="p">}</span>
<span class="c1"># next two data sources import the external cert and key file only it toggle</span>
<span class="c1"># is true</span>
<span class="n">data</span> <span class="s2">&#34;local_file&#34;</span> <span class="s2">&#34;ca-key&#34;</span> <span class="p">{</span>
  <span class="n">count</span>    <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">use_external_cert</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="mi">1</span> <span class="p">:</span> <span class="mi">0</span>
  <span class="n">filename</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="o">.</span><span class="n">external_key</span>
<span class="p">}</span>

<span class="n">data</span> <span class="s2">&#34;local_file&#34;</span> <span class="s2">&#34;ca-cert&#34;</span> <span class="p">{</span>
  <span class="n">count</span>    <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">use_external_cert</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="mi">1</span> <span class="p">:</span> <span class="mi">0</span>
  <span class="n">filename</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="o">.</span><span class="n">external_cert</span>
<span class="p">}</span>

<span class="c1"># Root CA Config #</span>
<span class="c1"># same logic goes into the self-signed CA config, but the toggle is reversed</span>
<span class="n">resource</span> <span class="s2">&#34;tls_private_key&#34;</span> <span class="s2">&#34;root&#34;</span> <span class="p">{</span>
  <span class="n">count</span>       <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">use_external_cert</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="mi">1</span>
  <span class="n">algorithm</span>   <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="o">.</span><span class="n">algorithm</span>
  <span class="n">rsa_bits</span>    <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="o">.</span><span class="n">algorithm</span> <span class="o">==</span> <span class="s2">&#34;RSA&#34;</span> <span class="p">?</span> <span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="o">.</span><span class="n">rsa_bits</span> <span class="p">:</span> <span class="n">null</span>
  <span class="n">ecdsa_curve</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="o">.</span><span class="n">algorithm</span> <span class="o">==</span> <span class="s2">&#34;ECDSA&#34;</span> <span class="p">?</span> <span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="o">.</span><span class="n">ecdsa_curve</span> <span class="p">:</span> <span class="n">null</span>
<span class="p">}</span>

<span class="c1"># notice that in subject block we cant reference CN and O as we did with with</span>
<span class="c1"># algorithm so usilg `lookup` function is desirable</span>
<span class="n">resource</span> <span class="s2">&#34;tls_self_signed_cert&#34;</span> <span class="s2">&#34;root&#34;</span> <span class="p">{</span>
  <span class="n">count</span>           <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">use_external_cert</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="mi">1</span>
  <span class="n">key_algorithm</span>   <span class="o">=</span> <span class="n">tls_private_key</span><span class="o">.</span><span class="n">root</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">algorithm</span>
  <span class="n">private_key_pem</span> <span class="o">=</span> <span class="n">tls_private_key</span><span class="o">.</span><span class="n">root</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">private_key_pem</span>

  <span class="n">subject</span> <span class="p">{</span>
    <span class="n">common_name</span>  <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="o">.</span><span class="n">subject</span><span class="p">,</span> <span class="s2">&#34;CN&#34;</span><span class="p">)</span>
    <span class="n">organization</span> <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="o">.</span><span class="n">subject</span><span class="p">,</span> <span class="s2">&#34;O&#34;</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="n">validity_period_hours</span> <span class="o">=</span> <span class="mi">87600</span> <span class="c1"># 10 years</span>

  <span class="n">allowed_uses</span> <span class="o">=</span> <span class="o">[</span>
    <span class="s2">&#34;crl_signing&#34;</span><span class="p">,</span>
    <span class="s2">&#34;cert_signing&#34;</span>
  <span class="o">]</span>
  <span class="n">is_ca_certificate</span> <span class="o">=</span> <span class="kp">true</span>
<span class="p">}</span>

<span class="c1"># Leaf Certs Config #</span>
<span class="c1"># leaf config is not as strict as root ca, because there was no explicit type</span>
<span class="c1"># declaration. Again the `lookup` function saves the day providing defaults for</span>
<span class="c1"># all omitted values</span>
<span class="n">resource</span> <span class="s2">&#34;tls_private_key&#34;</span> <span class="s2">&#34;leaf&#34;</span> <span class="p">{</span>
  <span class="n">for_each</span>    <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">certificates</span>
  <span class="n">algorithm</span>   <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="p">,</span> <span class="s2">&#34;algorithm&#34;</span><span class="p">,</span> <span class="s2">&#34;RSA&#34;</span><span class="p">)</span>
  <span class="n">rsa_bits</span>    <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="p">,</span> <span class="s2">&#34;algorithm&#34;</span><span class="p">,</span> <span class="s2">&#34;RSA&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;RSA&#34;</span> <span class="p">?</span> <span class="n">lookup</span><span class="p">(</span><span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="p">,</span> <span class="s2">&#34;rsa_bits&#34;</span><span class="p">,</span> <span class="mi">2048</span><span class="p">)</span> <span class="p">:</span> <span class="n">null</span>
  <span class="n">ecdsa_curve</span> <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="p">,</span> <span class="s2">&#34;algorithm&#34;</span><span class="p">,</span> <span class="s2">&#34;RSA&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;ECDSA&#34;</span> <span class="p">?</span> <span class="n">lookup</span><span class="p">(</span><span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="p">,</span> <span class="s2">&#34;ecdsa_curve&#34;</span><span class="p">,</span> <span class="s2">&#34;P224&#34;</span><span class="p">)</span> <span class="p">:</span> <span class="n">null</span>
<span class="p">}</span>

<span class="n">resource</span> <span class="s2">&#34;tls_cert_request&#34;</span> <span class="s2">&#34;leaf&#34;</span> <span class="p">{</span>
  <span class="n">for_each</span>        <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">certificates</span>
  <span class="n">key_algorithm</span>   <span class="o">=</span> <span class="n">tls_private_key</span><span class="o">.</span><span class="n">leaf</span><span class="o">[</span><span class="n">each</span><span class="o">.</span><span class="n">key</span><span class="o">].</span><span class="n">algorithm</span>
  <span class="n">private_key_pem</span> <span class="o">=</span> <span class="n">tls_private_key</span><span class="o">.</span><span class="n">leaf</span><span class="o">[</span><span class="n">each</span><span class="o">.</span><span class="n">key</span><span class="o">].</span><span class="n">private_key_pem</span>

  <span class="n">subject</span> <span class="p">{</span>
    <span class="n">common_name</span>  <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">subject</span><span class="p">,</span> <span class="s2">&#34;CN&#34;</span><span class="p">)</span>
    <span class="n">organization</span> <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="n">each</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">subject</span><span class="p">,</span> <span class="s2">&#34;O&#34;</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="n">dns_names</span> <span class="o">=</span> <span class="o">[</span><span class="n">each</span><span class="o">.</span><span class="n">key</span><span class="o">]</span>
<span class="p">}</span>

<span class="c1"># another resource with local toggle use, this time to either get</span>
<span class="c1"># the generated CA or the external one</span>
<span class="n">resource</span> <span class="s2">&#34;tls_locally_signed_cert&#34;</span> <span class="s2">&#34;leaf&#34;</span> <span class="p">{</span>
  <span class="n">for_each</span>           <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">certificates</span>
  <span class="n">cert_request_pem</span>   <span class="o">=</span> <span class="n">tls_cert_request</span><span class="o">.</span><span class="n">leaf</span><span class="o">[</span><span class="n">each</span><span class="o">.</span><span class="n">key</span><span class="o">].</span><span class="n">cert_request_pem</span>
  <span class="n">ca_key_algorithm</span>   <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">root_ca</span><span class="o">.</span><span class="n">algorithm</span>
  <span class="n">ca_private_key_pem</span> <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">use_external_cert</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="n">data</span><span class="o">.</span><span class="n">local_file</span><span class="o">.</span><span class="n">ca</span><span class="o">-</span><span class="n">key</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">content</span> <span class="p">:</span> <span class="n">tls_private_key</span><span class="o">.</span><span class="n">root</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">private_key_pem</span>
  <span class="n">ca_cert_pem</span>        <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">use_external_cert</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="n">data</span><span class="o">.</span><span class="n">local_file</span><span class="o">.</span><span class="n">ca</span><span class="o">-</span><span class="n">cert</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">content</span> <span class="p">:</span> <span class="n">tls_self_signed_cert</span><span class="o">.</span><span class="n">root</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">cert_pem</span>

  <span class="n">validity_period_hours</span> <span class="o">=</span> <span class="mi">8760</span> <span class="c1"># 1 year</span>

  <span class="n">allowed_uses</span> <span class="o">=</span> <span class="o">[</span>
    <span class="s2">&#34;key_encipherment&#34;</span><span class="p">,</span>
    <span class="s2">&#34;digital_signature&#34;</span><span class="p">,</span>
    <span class="s2">&#34;server_auth&#34;</span><span class="p">,</span>
  <span class="o">]</span>
<span class="p">}</span>

<span class="c1"># File Outputs #</span>
<span class="c1"># and finally file outputs</span>
<span class="c1"># nothing new here, same tricks</span>
<span class="n">resource</span> <span class="s2">&#34;local_file&#34;</span> <span class="s2">&#34;ca-cert&#34;</span> <span class="p">{</span>
  <span class="n">count</span>    <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">use_external_cert</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="mi">1</span>
  <span class="n">content</span>  <span class="o">=</span> <span class="n">tls_self_signed_cert</span><span class="o">.</span><span class="n">root</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">cert_pem</span>
  <span class="n">filename</span> <span class="o">=</span> <span class="s2">&#34;ca.pem&#34;</span>
<span class="p">}</span>

<span class="n">resource</span> <span class="s2">&#34;local_file&#34;</span> <span class="s2">&#34;ca-key&#34;</span> <span class="p">{</span>
  <span class="n">count</span>    <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">use_external_cert</span> <span class="o">==</span> <span class="kp">true</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="mi">1</span>
  <span class="n">content</span>  <span class="o">=</span> <span class="n">tls_private_key</span><span class="o">.</span><span class="n">root</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">private_key_pem</span>
  <span class="n">filename</span> <span class="o">=</span> <span class="s2">&#34;ca-key.pem&#34;</span>
<span class="p">}</span>

<span class="n">resource</span> <span class="s2">&#34;local_file&#34;</span> <span class="s2">&#34;leaf-certs&#34;</span> <span class="p">{</span>
  <span class="n">for_each</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">certificates</span>
  <span class="n">content</span>  <span class="o">=</span> <span class="n">tls_locally_signed_cert</span><span class="o">.</span><span class="n">leaf</span><span class="o">[</span><span class="n">each</span><span class="o">.</span><span class="n">key</span><span class="o">].</span><span class="n">cert_pem</span>
  <span class="n">filename</span> <span class="o">=</span> <span class="s2">&#34;${each.key}-cert.pem&#34;</span>
<span class="p">}</span>

<span class="n">resource</span> <span class="s2">&#34;local_file&#34;</span> <span class="s2">&#34;leaf-keys&#34;</span> <span class="p">{</span>
  <span class="n">for_each</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">certificates</span>
  <span class="n">content</span>  <span class="o">=</span> <span class="n">tls_private_key</span><span class="o">.</span><span class="n">leaf</span><span class="o">[</span><span class="n">each</span><span class="o">.</span><span class="n">key</span><span class="o">].</span><span class="n">private_key_pem</span>
  <span class="n">filename</span> <span class="o">=</span> <span class="s2">&#34;${each.key}-key.pem&#34;</span>
<span class="p">}</span></code></pre></div>
<p>Now run it and you will get the CA certificate, all leaf certificates and corresponding private keys:</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">terraform init
terraform plan -out main.tfplan
terraform apply main.tfplan
ls -l <span class="p">|</span> grep pem</code></pre></div>
<p>Output:</p>

<pre><code>-rwxr-xr-x 1 alex alex  1554 Mar  9 23:52 anotherone.com-cert.pem
-rwxr-xr-x 1 alex alex  1679 Mar  9 23:52 anotherone.com-key.pem
-rwxr-xr-x 1 alex alex  3243 Mar  9 23:52 ca-key.pem
-rwxr-xr-x 1 alex alex  1834 Mar  9 23:52 ca.pem
-rwxr-xr-x 1 alex alex  1558 Mar  9 23:52 example.com-cert.pem
-rwxr-xr-x 1 alex alex  1675 Mar  9 23:52 example.com-key.pem
-rwxr-xr-x 1 alex alex  1265 Mar  9 23:52 website.com-cert.pem
-rwxr-xr-x 1 alex alex   207 Mar  9 23:52 website.com-key.pem
</code></pre>

<p>To test the script with imported CA you can delete the state and all leaf certs and then replace the <code>root_ca</code> if your <code>terraform.tfvars</code> with the following content:</p>
<div class="highlight"><pre class="chroma"><code class="language-RB" data-lang="RB"><span class="n">root_ca</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">algorithm</span>     <span class="o">=</span> <span class="s2">&#34;RSA&#34;</span>
  <span class="n">rsa_bits</span>      <span class="o">=</span> <span class="mi">4096</span>
  <span class="n">ecdsa_curve</span>   <span class="o">=</span> <span class="n">null</span>
  <span class="n">external_cert</span> <span class="o">=</span> <span class="s2">&#34;ca.pem&#34;</span>
  <span class="n">external_key</span>  <span class="o">=</span> <span class="s2">&#34;ca-key.pem&#34;</span>
  <span class="n">subject</span> <span class="o">=</span> <span class="p">{</span>
    <span class="no">CN</span> <span class="o">=</span> <span class="s2">&#34;Root CA&#34;</span>
    <span class="n">O</span>  <span class="o">=</span> <span class="s2">&#34;Example Org&#34;</span>
  <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>and then repeat the Terraform routine</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">rm -f terraform.tfstate*
ls <span class="p">|</span> grep .com <span class="p">|</span> xargs rm -f
terraform plan -out main.tfplan
terraform apply main.tfplan
ls -l <span class="p">|</span> grep pem</code></pre></div>
<p>Output:</p>

<pre><code>-rwxr-xr-x 1 alex alex  1554 Mar 10 21:27 anotherone.com-cert.pem
-rwxr-xr-x 1 alex alex  1679 Mar 10 21:27 anotherone.com-key.pem
-rwxr-xr-x 1 alex alex  3243 Mar 10 21:25 ca-key.pem
-rwxr-xr-x 1 alex alex  1834 Mar 10 21:25 ca.pem
-rwxr-xr-x 1 alex alex  1562 Mar 10 21:27 example.com-cert.pem
-rwxr-xr-x 1 alex alex  1675 Mar 10 21:27 example.com-key.pem
-rwxr-xr-x 1 alex alex  1265 Mar 10 21:27 website.com-cert.pem
-rwxr-xr-x 1 alex alex   207 Mar 10 21:27 website.com-key.pem
</code></pre>

<h2 id="conclusion">Conclusion</h2>

<p>I hope this article shed some light on the potential and the ways of using the iteration mechanisms in Terraform. In part two I am going to cover the list and map expressions for building robust data structures that can be consumed by the <code>count</code> and <code>for_each</code> meta-arguments. Combining those you will be able to write the code to support &ldquo;self-serve&rdquo; infrastructure deployment processes and create complex customizable resource hierarchies.</p>

<p>Stay tuned.</p>
]]></content>
		</item>
		
		<item>
			<title>Hashicorp Vault Dynamic Secret Engines</title>
			<link>https://trollab.ca/posts/vault-dynamic-secrets/</link>
			<pubDate>Sun, 07 Mar 2021 12:50:12 -0500</pubDate>
			
			<guid>https://trollab.ca/posts/vault-dynamic-secrets/</guid>
			<description>In this post I will cover the Hashicorp Vault dynamic secret engines. Their operating mechanism, use case scenarios, and some of the pitfalls you need to be aware of before you start using them at scale. It might look oversimplified at times but my goal is to give a conceptual understanding and a good head start for folks who never used Hashicorp Vault in general or dynamic secret engines in particular.</description>
			<content type="html"><![CDATA[

<p>In this post I will cover the Hashicorp Vault dynamic secret engines. Their operating mechanism, use case scenarios, and some of the pitfalls you need to be aware of before you start using them at scale. It might look oversimplified at times but my goal is to give a conceptual understanding and a good head start for folks who never used Hashicorp Vault in general or dynamic secret engines in particular.</p>

<h2 id="dynamic-secret-engines-what-do-they-do">Dynamic Secret Engines: What do they do?</h2>

<p>In a nutshell dynamic secrets engines provide a way to off-load credential management from the 3rd party apps/services(e.g. databases, message brokers, cloud providers, etc) giving you in exchange a generic mechanism of creating, rotating and revoking credentials with configurable TTL, RBAC, etc. All done via contacting a single API over HTTPS.</p>

<h2 id="dynamic-secret-engines-how-they-do-it">Dynamic Secret Engines: How they do it?</h2>

<p>You would configure the secret engine with an &ldquo;admin&rdquo;-like credentials(I will call them &ldquo;root&rdquo; credentials) of a target app/service giving it permissions to create, update, or delete users, passwords, tokens, etc. Then the end user would access Vault to obtain short-lived credentials with a given access scope. Vault would track the status of such credentials via the &ldquo;lease&rdquo; mechanism and revoke them(by sending appropriate instructions to the target app/service) when the lease expires.</p>

<p>The &ldquo;root&rdquo; credentials are decently privileged and long-lived at the same time. Although once they are written to the Vault, they can&rsquo;t be extracted in any way making a leak an unlikely event and giving you some peace of mind.</p>

<h2 id="dynamic-secret-engines-why-and-how-would-you-use-them">Dynamic Secret Engines: Why and how would you use them?</h2>

<p>Dynamic secret engines give you a powerful mechanism to take control over the secrets sprawl and put an end to the long-lived credentials. At the same time, Vault&rsquo;s audit trail will have an ultimate record of all access attempts. After Secret Engine is configured you would create one or more Role(some secret engines call it RoleSets) which would be associated with a specific access scope on the target. In other words speaking a Role/RoleSet is a Vault&rsquo;s internal representation of an identity in a target app/service(such as user, service account, etc). The capabilities of a RoleSet greatly depends on the Secret Engine implementation and capabilities of the target app/service. By reading(or writing sometimes) from the Role/RoleSet path you are obtaining a credentials of such identity.</p>

<p>The end-user access is controlled by the Vault&rsquo;s policies. You can grant access to the user/group on a specific path where secrets engine Roles/RoleSets are mounted. The granularity of the access granted is limited by the target service capabilities and your imagination.</p>

<h2 id="practice">Practice</h2>

<p>Now let&rsquo;s look closely at the Google Cloud secret engine in action. We&rsquo;ll start as always with setting up Vault server. To keep it simple, I&rsquo;ll use the &lsquo;filesystem&rsquo; storage backend and no SSL/TLS.
Config file first:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">cat <span class="s">&lt;&lt;EOF &gt; config.hcl
</span><span class="s">listener &#34;tcp&#34; {
</span><span class="s">  address     = &#34;127.0.0.1:8200&#34;
</span><span class="s">  tls_disable = &#34;true&#34;
</span><span class="s">}
</span><span class="s">
</span><span class="s">storage &#34;file&#34; {
</span><span class="s">  path = &#34;$(pwd)/data&#34;
</span><span class="s">}
</span><span class="s">
</span><span class="s">disable_mlock = &#34;true&#34;
</span><span class="s">api_addr      = &#34;http://127.0.0.1:8200&#34;
</span><span class="s">EOF</span></code></pre></div>
<p>And the start the server:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">vault server -config ./config.hcl</code></pre></div>
<p>In another terminal window initialize and unseal it (just one key share would do for this test) and then set up environment variables for further configuration:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH"><span class="nb">export</span> <span class="nv">VAULT_ADDR</span><span class="o">=</span><span class="s2">&#34;http://127.0.0.1:8200&#34;</span>
vault operator init -key-shares<span class="o">=</span><span class="m">1</span> -key-threshold<span class="o">=</span><span class="m">1</span> -format<span class="o">=</span>json <span class="p">|</span> tee init_info.json
vault operator unseal <span class="k">$(</span>jq -r <span class="s2">&#34;.unseal_keys_hex[0]&#34;</span> init_info.json<span class="k">)</span>
<span class="nb">export</span> <span class="nv">VAULT_TOKEN</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>jq -r <span class="s2">&#34;.root_token&#34;</span> init_info.json<span class="k">)</span><span class="s2">&#34;</span></code></pre></div>
<p>Now enable the secret engine:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">vault secrets <span class="nb">enable</span> gcp</code></pre></div>
<p>At this point we&rsquo;ll need a &ldquo;root&rdquo; GCP Service Account key. The Service Account must have following permissions:</p>

<pre><code>iam.serviceAccounts.create
iam.serviceAccounts.delete
iam.serviceAccounts.get
iam.serviceAccounts.list
iam.serviceAccounts.update
iam.serviceAccountKeys.create
iam.serviceAccountKeys.delete
iam.serviceAccountKeys.get
iam.serviceAccountKeys.list
</code></pre>

<p>Plus additional set of permission of a following pattern:</p>

<pre><code>&lt;service&gt;.&lt;resource&gt;.getIamPolicy
&lt;service&gt;.&lt;resource&gt;.setIamPolicy
</code></pre>

<p>First block will give the &ldquo;root&rdquo; service account permissions to create child Service Accounts and their keys. Second block will allow to grant them roles according to the RoleSet specification.</p>

<p><code>&lt;service&gt;.&lt;resource&gt;</code> will define what kind of GCP services and resources Vault will be able to grant access to. I will be granting pre-defined roles, though in real use cases you would probably go with a Custom Role.</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">gcloud iam service-accounts create vault-dyn-engine
<span class="nv">SA_EMAIL</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>gcloud iam service-accounts list --project vault-dynamic-secrets<span class="p">|</span> grep vault-dyn-engine <span class="p">|</span> awk <span class="s1">&#39;{print $1}&#39;</span><span class="k">)</span><span class="s2">&#34;</span>

gcloud iam service-accounts keys create credentials.json --iam-account<span class="o">=</span><span class="nv">$SA_EMAIL</span> --project vault-dynamic-secrets
<span class="nv">PROJECT_ID</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>jq -r <span class="s1">&#39;.project_id&#39;</span> credentials.json<span class="k">)</span><span class="s2">&#34;</span>

gcloud projects add-iam-policy-binding <span class="nv">$PROJECT_ID</span> <span class="se">\
</span><span class="se"></span>    --member<span class="o">=</span>serviceAccount:<span class="nv">$SA_EMAIL</span> <span class="se">\
</span><span class="se"></span>    --role<span class="o">=</span>roles/resourcemanager.projectIamAdmin

gcloud projects add-iam-policy-binding <span class="nv">$PROJECT_ID</span> <span class="se">\
</span><span class="se"></span>    --member<span class="o">=</span>serviceAccount:<span class="nv">$SA_EMAIL</span> <span class="se">\
</span><span class="se"></span>    --role<span class="o">=</span>roles/iam.serviceAccountAdmin

gcloud projects add-iam-policy-binding <span class="nv">$PROJECT_ID</span> <span class="se">\
</span><span class="se"></span>    --member<span class="o">=</span>serviceAccount:<span class="nv">$SA_EMAIL</span> <span class="se">\
</span><span class="se"></span>    --role<span class="o">=</span>roles/iam.serviceAccountKeyAdmin</code></pre></div>
<p>Back to the Vault configuration. Time to create a RoleSet with a Project Viewer role:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">vault write gcp/config <span class="nv">credentials</span><span class="o">=</span>@credentials.json

vault write gcp/roleset/project_viewer <span class="se">\
</span><span class="se"></span>    <span class="nv">project</span><span class="o">=</span><span class="s2">&#34;vault-dynamic-secrets&#34;</span> <span class="se">\
</span><span class="se"></span>    <span class="nv">secret_type</span><span class="o">=</span><span class="s2">&#34;service_account_key&#34;</span>  <span class="se">\
</span><span class="se"></span>    <span class="nv">bindings</span><span class="o">=</span>-<span class="s">&lt;&lt;EOF
</span><span class="s">      resource &#34;//cloudresourcemanager.googleapis.com/projects/vault-dynamic-secrets&#34; {
</span><span class="s">        roles = [&#34;roles/viewer&#34;]
</span><span class="s">      }
</span><span class="s">EOF</span></code></pre></div>
<p>After this step you should be able to see a new service account in GCP:</p>

<figure><a href="/vault-dynamic-secrets/create-roleset.gif">
    <img src="/vault-dynamic-secrets/create-roleset.gif"
         alt="create-roleset"/> </a>
</figure>


<p>IAM roles granted to this Service Account will match one you put it the RoleSet:</p>

<p><img src="/vault-dynamic-secrets/iam-roles.png" alt="vault-roleset-iam" /></p>

<p>Now the secret engine is ready for use. At this point you would create policies and grant your Vault users <code>write</code> permission ot the path <code>gcp/key/project_viewer</code>. I&rsquo;ll omit this part and use the same root token I used for configuration:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">vault write gcp/key/project_viewer <span class="nv">ttl</span><span class="o">=</span>5s -format<span class="o">=</span>json &gt; result.json

jq -r <span class="s2">&#34;.data.private_key_data&#34;</span> result.json <span class="p">|</span> base64 -d &gt; generated_key.json

<span class="nb">echo</span> <span class="s2">&#34;Lease duration: </span><span class="k">$(</span>jq -r <span class="s1">&#39;.lease_duration&#39;</span> result.json<span class="k">)</span><span class="s2">&#34;</span></code></pre></div>
<p>I use a silly TTL value of 5 sec, but it shows that keys are created and then shortly after destroyed:</p>

<figure><a href="/vault-dynamic-secrets/generate-keys.gif">
    <img src="/vault-dynamic-secrets/generate-keys.gif"
         alt="generate-key-with-vault"/> </a>
</figure>


<p>And to finish the demo let&rsquo;s delete the RoleSet:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">vault delete gcp/roleset/project_viewer</code></pre></div>
<figure><a href="/vault-dynamic-secrets/delete-roleset.gif">
    <img src="/vault-dynamic-secrets/delete-roleset.gif"
         alt="roleset-deletion"/> </a>
</figure>


<p>The moment RoleSet is deleted, corresponding Service Account in GCP is deleted too.</p>

<h2 id="pitfalls-and-workarounds">Pitfalls and workarounds</h2>

<p>Thinking about the lease mechanism and decoupled nature of a target app/service and a secrets manager, two corner cases inevitably come to mind:</p>

<ol>
<li><p>What will happen if Vault crashes, reboots, or simply gets sealed?
Typically you would address this issue by configuring HA-Vault. The OSS Vault allows only Active/Standby mode, when only one instance of Vault is serving user requests at all time. Enterprise Vault gives you &ldquo;Performance Standby Nodes&rdquo; feature which allows standby nodes to serve Read request, increasing the request throughput your Vault can handle. Though even if all of Vault nodes will go down at the same time somehow you will be ok. Due to the fact that secret lifetime gets controlled by the Vault and it needs to send the request to the target app/service to revoke the secret, revocation stops working until Vault is down/sealed. The good news is that the moment you unseal it, Vault will reassess all it&rsquo;s leases and will revoke all secrets associated with expired leases.</p></li>

<li><p>What will happen if the target app/service is down, or the connectivity is disrupted in any way?
At the first glance this scenario looks similar to the first one, Vault is aware of all expired leases and can repeat the revocation once connection is restored. In practice there is one significant limitation. Vault makes a limited number of attempts to revoke the secret and then just stops trying. In the test I have performed with Google Cloud secrets engine(and quick research confirmed that it is valid for other dynamic secret engines too) Vault made 6 retries starting with 40sec back-off timer, and then gradually increasing it(+10sec, +20sec, +40sec, etc). What that means is if your target app/service connectivity were not restored in ~13 mins, the secret will be hanging there indefinitely.</p></li>
</ol>

<p>This is a sad news indeed, but there are things you can do to address it:</p>

<ol>
<li>You can manually revoke such secrets(or build the automation around the Vault API and your logging service that captures Vault logs and can detect the event of failed revocation)</li>
<li>If your Vault gets sealed/unsealed all expired leases will be reassessed and revoked accordingly. If you are using manual unsealing it&rsquo;s not making things much better. If you do use auto-unsealing, you can potentially build a workflow with a daily seal/unseal routine. Not a bulletproof solution but it is something at least.</li>
</ol>

<h2 id="conclusion">Conclusion</h2>

<p>Vault dynamic secret&rsquo;s engines is an extremely powerful and handy tool that can make life of you SecOps teams much easier. Though as it typically is the case, it&rsquo;s not perfect. There are pitfalls and edge cases. The &ldquo;maximum revocation attempt&rdquo; issue in the Vault&rsquo;s <a href="https://github.com/hashicorp/vault/issues/8602">Git repository</a> is still open. Let&rsquo;s hope it&rsquo;ll be addressed and resolved in the near future. Meanwhile if you are planning to use Dynamic Secret Engines, consider if a app/service downtime is a frequent event for you and plan your remediation procedures accordingly.</p>
]]></content>
		</item>
		
		<item>
			<title>Injecting secrets into CI/CD from Hashicorp Vault</title>
			<link>https://trollab.ca/posts/ci_secrets_from_vault/</link>
			<pubDate>Tue, 02 Mar 2021 19:25:41 -0500</pubDate>
			
			<guid>https://trollab.ca/posts/ci_secrets_from_vault/</guid>
			<description>Ever since I started my DevOps engineer journey my experience of interacting with secrets from CI system could be summarized in 3 words - &amp;ldquo;pain and suffering&amp;rdquo;. I&amp;rsquo;ve been wondering &amp;ldquo;Wouldn&amp;rsquo;t it be dreamy to have a tool that can take all the secrets from a central location and inject them into my build at runtime?&amp;rdquo;. I&amp;rsquo;ve been planning to explore this idea for a long time and now, finally, the time has come.</description>
			<content type="html"><![CDATA[

<p>Ever since I started my DevOps engineer journey my experience of interacting with secrets from CI system could be summarized in 3 words - &ldquo;pain and suffering&rdquo;. I&rsquo;ve been wondering &ldquo;Wouldn&rsquo;t it be dreamy to have a tool that can take all the secrets from a central location and inject them into my build at runtime?&rdquo;. I&rsquo;ve been planning to explore this idea for a long time and now, finally, the time has come. We&rsquo;ll make this world a better place and lift the weight of CI/CD secrets management off of DevOps engineers shoulders by putting in place some automation.</p>

<h2 id="theory">Theory</h2>

<p>Lets assume we have an org with multiple developer teams. All of them use central CI system to build and deploy their software to the cloud. Each team might have more than one project to develop/maintain and obviously security is the priority for everyone so all secrets must be in the central well protected location. Most of CI&rsquo;s have some sort of secrets storage, but each pipeline has to be configured individually (the only one that did it right is Concourse CI, those folks created beautiful way of integrating 3rd party secrets managers). Now imagine what effort would it take to maintain a dozen of secrets across 3+ environments&hellip; Pain and suffering, isn&rsquo;t it?</p>

<p>What we want is to make this process transparent. Knowing where secrets are stored and how to get them should be the only CI concern. Managing those secrets(adding, updating, deleting, rotating, etc) should be &ldquo;externalized&rdquo; to the lack of better word.</p>

<p>Hashicorp Vault is an example of a software that can make it happen. It is API-driven and has a powerful RBAC system. Key/Value secrets engine is a simple and straightforward but at the same time robust and flexible way of storing arbitrary string data. I will demonstrate how to use it to fetch secrets for your CI/CD pipelines.</p>

<p>Lets think for a moment about typical deployment procedure. We try to keep all environments alike, that&rsquo;s the only way we can guarante(more or less) that if it works flawlessly in staging it will work just as well in production. That means that infrastructure code should be the same for all the environments. Obviously there will be variables like names, credentials, certificates, etc. We need a way to fetch them in a generic way but with ability to detect the target environment type and get the correct secret for environment.</p>

<p>Vault use paths as a mount/access point for secret engines. We could leverage that to make single script to fetch environment secrets using following path structure:</p>

<pre><code>ci-kv/TEAM/PROJECT/ENVIRONMENT/SECRET
</code></pre>

<h2 id="practice">Practice</h2>

<p>We&rsquo;ll start with Vault configuration. Setting up production grade Vault server is out of the scope of this post and I will be using dev-server for this proof of concept.</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">vault server -dev</code></pre></div>
<p>The rest will be done in a separate shell. First enable the secrets engine:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH"><span class="nb">export</span> <span class="nv">VAULT_ADDR</span><span class="o">=</span><span class="s1">&#39;http://127.0.0.1:8200&#39;</span>
vault secrets <span class="nb">enable</span> -path<span class="o">=</span>ci-kv -version<span class="o">=</span><span class="m">1</span> kv</code></pre></div>
<p>Then write some secrets for staging environment:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">vault kv put ci-kv/dev_team_a/project_x/staging/username <span class="se">\
</span><span class="se"></span>    <span class="nv">value</span><span class="o">=</span>staging_user

vault kv put ci-kv/dev_team_a/project_x/staging/password <span class="se">\
</span><span class="se"></span>    <span class="nv">value</span><span class="o">=</span>staging_password</code></pre></div>
<p>And some for production:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">vault kv put ci-kv/dev_team_a/project_x/production/username <span class="se">\
</span><span class="se"></span>    <span class="nv">value</span><span class="o">=</span>production_user

vault kv put ci-kv/dev_team_a/project_x/production/password <span class="se">\
</span><span class="se"></span>    <span class="nv">value</span><span class="o">=</span>production_password</code></pre></div>
<p>Next goes the policy to access all secrets. You will need <code>read</code> and <code>list</code> permissions:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">cat <span class="s">&lt;&lt;EOF &gt; policy.hcl
</span><span class="s">path &#34;ci-kv/dev_team_a/*&#34; {
</span><span class="s">  capabilities = [&#34;list&#34;, &#34;read&#34;]
</span><span class="s">}
</span><span class="s">EOF</span>
vault policy write dev_team_a_ci policy.hcl</code></pre></div>
<p>As you can see I&rsquo;m granting the CI permissions to read all secrets of <code>dev_team_a</code> regardless of the project but you could go as granular as you like.</p>

<p>The next step would be to create an AppRole credentials and bind them with the policy you just created:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">vault auth <span class="nb">enable</span> approle
vault write auth/approle/role/dev_team_a_ci <span class="se">\
</span><span class="se"></span>    <span class="nv">token_max_ttl</span><span class="o">=</span>5m <span class="se">\
</span><span class="se"></span>    <span class="nv">policies</span><span class="o">=</span>dev_team_a_ci
vault <span class="nb">read</span> auth/approle/role/dev_team_a_ci/role-id
vault write -f auth/approle/role/dev_team_a_ci/secret-id</code></pre></div>
<p>That will produce Role ID and Secret ID necessary to obtain a token. You will get an output similar to following:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">Key        Value
---        -----
role_id    f73079cb-619d-6764-c8dd-c5f7c42d58c5

Key                   Value
---                   -----
secret_id             362c3f32-6e94-c942-1669-bff27bda8763
secret_id_accessor    b2a6e6b6-1d54-8a5c-fff4-23257f4d7a62</code></pre></div>
<p>Now when all configurations are done lets write a small shell script to retrieve all secrets. To do the trick you will need <code>curl</code> and <code>jq</code>.</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">cat <span class="s">&lt;&lt;&#39;EOF&#39; &gt; run_with_secrets.sh
</span><span class="s">#!/bin/sh
</span><span class="s">
</span><span class="s">set -o nounset
</span><span class="s">set -o errexit
</span><span class="s">
</span><span class="s"># get the token
</span><span class="s">TOKEN=&#34;$(curl \
</span><span class="s">    --silent \
</span><span class="s">    --request POST \
</span><span class="s">    --data &#34;{\&#34;role_id\&#34;:\&#34;$ROLE_ID\&#34;,\&#34;secret_id\&#34;:\&#34;$SECRET_ID\&#34;}&#34; \
</span><span class="s">    $VAULT_URL/v1/auth/approle/login | jq -r .auth.client_token)&#34;
</span><span class="s">
</span><span class="s"># fetch secrets list
</span><span class="s">SECRETS=&#34;$(curl \
</span><span class="s">    --silent \
</span><span class="s">    --header &#34;X-Vault-Token: $TOKEN&#34; \
</span><span class="s">    --request LIST \
</span><span class="s">    $VAULT_URL/v1/$KV_PATH_PREFIX/$ENVIRONMENT | jq -r &#34;.data.keys[]&#34;)&#34;
</span><span class="s">
</span><span class="s"># fetch secrets and put them in corresponding environment variables
</span><span class="s">for SECRET in $SECRETS; do
</span><span class="s">  VALUE=$(curl \
</span><span class="s">    --silent \
</span><span class="s">    --header &#34;X-Vault-Token: $TOKEN&#34; \
</span><span class="s">    &#34;$VAULT_URL/v1/$KV_PATH_PREFIX/$ENVIRONMENT/$SECRET&#34; | jq -r &#34;.data.value&#34;)
</span><span class="s">  export $SECRET=&#34;$VALUE&#34;
</span><span class="s">done
</span><span class="s">
</span><span class="s"># finally run the deployment command that needs all those secrets
</span><span class="s">exec &#34;$@&#34;
</span><span class="s">EOF</span>

chmod +x ./run_with_secrets.sh</code></pre></div>
<p>Now lets test the result. Generally speaking that should be your deployment tool that would reference the environment variables. But to keep it simple a short script that just greps secrets out of environment variables should be enough:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH">cat <span class="s">&lt;&lt;EOF &gt; tester.sh
</span><span class="s">env | grep username
</span><span class="s">env | grep password
</span><span class="s">EOF</span>

chmod +x ./tester.sh</code></pre></div>
<p>Now run it:</p>
<div class="highlight"><pre class="chroma"><code class="language-SH" data-lang="SH"><span class="c1"># following would be configured in your CI</span>
<span class="nb">export</span> <span class="nv">VAULT_URL</span><span class="o">=</span><span class="s2">&#34;http://127.0.0.1:8200&#34;</span>
<span class="nb">export</span> <span class="nv">KV_PATH_PREFIX</span><span class="o">=</span><span class="s2">&#34;ci-kv/dev_team_a/project_x&#34;</span>
<span class="nb">export</span> <span class="nv">ROLE_ID</span><span class="o">=</span><span class="s2">&#34;f73079cb-619d-6764-c8dd-c5f7c42d58c5&#34;</span>
<span class="nb">export</span> <span class="nv">SECRET_ID</span><span class="o">=</span><span class="s2">&#34;362c3f32-6e94-c942-1669-bff27bda8763&#34;</span>

<span class="c1"># environment can be obtained either from envvar set by CI or from a branch name</span>
<span class="nb">export</span> <span class="nv">ENVIRONMENT</span><span class="o">=</span><span class="s2">&#34;staging&#34;</span>
./run_with_secrets.sh ./tester.sh

<span class="nb">export</span> <span class="nv">ENVIRONMENT</span><span class="o">=</span><span class="s2">&#34;production&#34;</span>
./run_with_secrets.sh ./tester.sh</code></pre></div>
<p>Outputs should perfectly match values you configured in Vault. Obviously same script would work with any number of secrets equally well. Not to mention that you will be able to dynamically upload or rotate secrets without a need to update your CI pipeline config. The only inputs we&rsquo;ll need are Vault URL, KV path prefix, AppRole credentials, and environment name. You can also easily modify the script to dump secrets into a file rather than environment variables(for example if you need a <code>tfvars</code> file for your Terraform code).</p>

<p>To put those values in the Vault you could use either the built-in UI or a custom frontend. It supports integration with authentication backends like LDAP or OIDC so letting users in should be very easy. By properly configuring policies, you can give your users write/update access which will allow them to create/update secrets but not view them.</p>

<h2 id="room-for-improvement">Room for improvement</h2>

<p>If you wonder what&rsquo;s gonna happen if Vault was configured incorrectly, or there&rsquo;s a typo in the path, or even AppRole has expired, the answer is nothing good. The script has no error handling or even decent output to help with troubleshooting when something goes wong. Also there is not that much flexibility in the way Vault operations are performed, login method is hardcoded, etc. In other words, there is plenty of space for improvement.</p>

<p>You can have a look at what was my approach to converting it into a more robust version at <a href="https://github.com/Trollabs/vault-secrets-fetcher">Trollabs/vault-secrets-fetcher</a>. And I would be happy to hear what would you do to further improve it.</p>
]]></content>
		</item>
		
		<item>
			<title>GitOps</title>
			<link>https://trollab.ca/posts/gitops/</link>
			<pubDate>Sat, 27 Feb 2021 23:38:48 -0500</pubDate>
			
			<guid>https://trollab.ca/posts/gitops/</guid>
			<description>The term GitOps became quite popular these days. There are plenty of good reads on the internet about it but I bet if you google &amp;ldquo;gitops&amp;rdquo;, 9 out of 10 will be about application deployments in Kubernetes. This lead to a common confusion that GitOps is for Kubernetes only.
In fact GitOps works great in any scenario where declarative configuration is used. I have successfully used it to provision Cloud Infrastructure with Terraform and I am going to share some insights, tips and tricks I&amp;rsquo;ve learned over the last year.</description>
			<content type="html"><![CDATA[

<p>The term GitOps became quite popular these days. There are plenty of good reads on the internet about it but I bet if you google &ldquo;gitops&rdquo;, 9 out of 10 will be about application deployments in Kubernetes. This lead to a common confusion that GitOps is for Kubernetes only.</p>

<p>In fact GitOps works great in any scenario where declarative configuration is used. I have successfully used it to provision Cloud Infrastructure with Terraform and I am going to share some insights, tips and tricks I&rsquo;ve learned over the last year.</p>

<h2 id="so-what-is-gitops">So what is GitOps?</h2>

<p>In a nutshell, GitOps is an approach for handling Ops tasks using Git as a centerpiece. We all know that git is amazing collaboration tool. It helps us to track changes in code, and reverse them in the matter of minutes when it is necessary. All modern Continuous Integration(CI) systems work in combination with Git. They observe the state of a git repository and trigger jobs when the state changes.</p>

<p><img src="/gitops/gitops-nutshell.png" alt="gitops-nutshell" /></p>

<p>Git repo becomes in a way the single source of truth, because every time the repo state changes new CI job is triggered to reflect that change on the target environment. That means your provisioning procedure might run multiple times in a row on the same or almost the same infrastructure code, so it must be idempotent. This is the only constraint that GitOps puts on the tools you can use. Terraform is a good example of such tool due to the fact that it keeps track of the infrastructure state and (re)deploys only the delta.</p>

<h2 id="bells-and-whistles">Bells and whistles</h2>

<p>Git itself is a small CLI tool and is rarely used on its own, typically one will use a Git based code hosting service. The most popular are GitHub, GitLab, and BitBucket. They all offer the distributed version control and source code management functionality of Git but also bring some additional features (bug tracking, pull requests, integrated ci, etc).</p>

<p>All of them provide capability(either integrated or with help of WebHooks) to launch scripts on events such as push, pull request creation or update, creation of the comment in the PR discussion, etc. Moreover they give you access to their API allowing you to build complex scenarios.</p>

<p>Another common and incredibly useful features are merge checks and branch protection. They empower you to enforce how and when your code &ldquo;moves&rdquo; between branches of the repo. Here are some examples of what you can do with it:</p>

<ol>
<li>Deny direct push to the branch unless it was done by merging a Pull Request</li>
<li>Prevent merging a Pull Request unless it was approved by one ore more people of a certain group (eg code owners)</li>
<li>Prevent merge if last CI build on the branch has failed</li>
<li>Prevent merge if discussions on the PR were not resolved</li>
</ol>

<p>Use them to create &ldquo;gates&rdquo; between different environments.</p>

<h2 id="the-gitops-workflow">The GitOps Workflow</h2>

<p>I&rsquo;ve diverted a bit into the hosted Git solutions and their feature set to give a little bit perspective of what they have to offer. Now lets see how you can use them to build more or less useful GitOps workflow.</p>

<p><img src="/gitops/gitops-workflow.png" alt="gitops-workflow" /></p>

<p>As you can see the diagram is very generic and can be applied with any hosted Git offering, CI system or Cloud Provider. Pull Request event is used only once to validate the code in the testing environment before pushing it to staging. After successful validation neither the code nor deployment process will change so you can use the same job/script. To make this possible you will have to store environment-specific variables, secrets and credentials in some sort of Secret Management system and then fetch them at runtime. Variables that are the same for all environments and are not sensitive can be stored in git together with the infrastructure code.</p>

<p>I prefer Hashicorp Vault&rsquo;s KV engine for storing secrets and credentials. The KV path can be composed as something like:</p>

<pre><code>kv/TEAM_NAME/BRANCH_NAME/*
</code></pre>

<p>That way you can write a generic script that will fetch all secrets from the prefix and write them to the <code>.tfvars</code> file or set as environment variables. You can use it with any number of different teams and environments using <code>BRANCH_NAME</code> as a pointer to the environment.</p>

<p>To get an idea of how you can achieve that you can check out my post <a href="https://trollab.ca/posts/ci_secrets_from_vault/">Injecting secrets into CI/CD from Hashicorp Vault</a>.</p>

<h2 id="what-else-you-can-do">What else you can do?</h2>

<p>Of course you can also have CI jobs for other PRs if you have a suitable action to launch. For example you could generate and send a notification to the Slack channel with a URL of the PR and a results of test/deployment to previous environment. Generally speaking appropriate notifications is a very important aspect of a GitOps workflow. Having a message sent to your Slack channel or a Hipchat room with a good summary helps a lot. Just try to keep the noise down.</p>

<h2 id="conclusion">Conclusion</h2>

<p>I believe that GitOps is extremely useful approach and definitely will be a nice addition to the DevOps engineer&rsquo;s toolbox. At the same time it&rsquo;s not a silver bullet and in some use case scenarios might not make sense. So before going wild transforming everything into GitOps workflows try it in a &ldquo;proof of concept&rdquo; and see if it adds value. I hope this article will bring you some inspiration and will help you to build GitOps workflows in your organization.</p>
]]></content>
		</item>
		
	</channel>
</rss>
