<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mattias Fjellström</title>
    <description>The latest articles on DEV Community by Mattias Fjellström (@mattiasfjellstrom).</description>
    <link>https://dev.to/mattiasfjellstrom</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F580067%2F5ab52217-2582-42db-b2e1-acf842058a56.jpeg</url>
      <title>DEV Community: Mattias Fjellström</title>
      <link>https://dev.to/mattiasfjellstrom</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mattiasfjellstrom"/>
    <language>en</language>
    <item>
      <title>The Reluctant Software Developer: Contributing to HashiCorp Vault</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Wed, 13 Dec 2023 21:10:31 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/the-reluctant-software-developer-contributing-to-hashicorp-vault-cf8</link>
      <guid>https://dev.to/mattiasfjellstrom/the-reluctant-software-developer-contributing-to-hashicorp-vault-cf8</guid>
      <description>&lt;p&gt;First, a confession. I am not a &lt;em&gt;software developer&lt;/em&gt;, or a &lt;em&gt;programmer&lt;/em&gt;. I enjoy writing code, and I am confident in using Python, Go, and TypeScript. I am uncomfortable using Haskell, but I am eager to learn more.&lt;/p&gt;

&lt;p&gt;What I am is a &lt;em&gt;cloud architect&lt;/em&gt;. I work more in the interface between code and cloud services.&lt;/p&gt;

&lt;p&gt;With that confession out of the way, let me move on. A few months ago I spent some intense time learning &lt;strong&gt;HashiCorp Vault&lt;/strong&gt;, enough to feel confident in using Vault day-to-day and to later pass the &lt;a href="https://www.credly.com/badges/515fbaa5-c38d-4f06-807b-8a63737e7d3c/public_url"&gt;associate certification&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Summarizing the Vault certification in four large parts we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vault architecture&lt;/li&gt;
&lt;li&gt;Vault CLI&lt;/li&gt;
&lt;li&gt;Vault UI&lt;/li&gt;
&lt;li&gt;Vault API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is very simplified! It would be intimidating if this was the official list of things to learn to pass the exam.&lt;/p&gt;

&lt;p&gt;The UI and API part of the exam is small, but the CLI part is huge. You need to know the commands. Luckily they are easy to learn.&lt;/p&gt;

&lt;p&gt;During my learning journey I stumbled upon a missing feature in the CLI. In the &lt;a href="https://developer.hashicorp.com/vault/docs/concepts/tokens#token-accessors"&gt;Vault documentation for token accessors&lt;/a&gt; I read that you can do the following things with the accessor:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When tokens are created, a token accessor is also created and returned. This accessor is a value that acts as a reference to a token and can only be used to perform limited actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look up a token's properties (not including the actual token ID)&lt;/li&gt;
&lt;li&gt;Look up a token's capabilities on a path&lt;/li&gt;
&lt;li&gt;Renew the token&lt;/li&gt;
&lt;li&gt;Revoke the token&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;I tried to perform these actions using the CLI and discovered that it was not possible to list a token's capabilities on a path using its accessor (point 2 in the list above).&lt;/p&gt;

&lt;p&gt;If it would have worked the command should have looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault token capabilities &lt;span class="nt"&gt;-accessor&lt;/span&gt; &amp;lt;accessor value&amp;gt; /the/path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it did not work! I wrote this discrepancy down in my list of notes for later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now it is "later"
&lt;/h2&gt;

&lt;p&gt;I have not made any contributions to large collaborative projects like HashiCorp Vault before, so I was a bit hesitant to even get started.&lt;/p&gt;

&lt;p&gt;As with any open-source&lt;sup id="fnref1"&gt;1&lt;/sup&gt; or source-available projects, it is a good idea to start reading the &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; file in the repository to see if there are any special guidelines to follow. For Vault (and I assume it is similar for other HashiCorp projects) there were a few things to keep in mind:&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect your pull-request to an open issue
&lt;/h3&gt;

&lt;p&gt;The first guideline to follow was to make sure you work on something that has an associated issue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When submitting a PR you should reference an existing issue. If no issue already exists, please create one. This can be skipped for trivial PRs like fixing typos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I searched the list of open issues for anything related to token capabilities. A few issues came up but nothing to do with what I wanted to implement concerning accessors. So I created my own issue (or &lt;em&gt;feature request&lt;/em&gt; in the form of an issue).&lt;/p&gt;

&lt;p&gt;To be honest, I waited with this part until I was done implementing the feature. I wanted to make sure I would be able to deliver a solution!&lt;/p&gt;

&lt;p&gt;My issue: &lt;a href="https://github.com/hashicorp/vault/issues/24478"&gt;#24478&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Describe your work
&lt;/h3&gt;

&lt;p&gt;I confess I cheated a bit with this guideline. This guideline asked you to describe your work in the pull-request:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your pull request should have a description of what it accomplishes, how it does so, and why you chose the approach you did.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I took inspiration from other PRs where the description just included a link to the issue it solves. The issue itself contains enough details to describe what is going on. I guess DRY (don't repeat yourself) also applies to issues and pull-requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Include tests
&lt;/h3&gt;

&lt;p&gt;As any good developer know you should test your code:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;PRs should include unit tests that validate correctness and the existing tests must pass.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Of course you should add tests for the functionality you are adding. I took inspiration from the tests that were already written for the &lt;code&gt;token capabilities&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;I added tests to verify that the correct number of arguments were provided to the command if the &lt;code&gt;-accessor&lt;/code&gt; flag was added. I also added a test to verify that the correct capabilities were returned if a valid accessor was used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a changelog entry
&lt;/h3&gt;

&lt;p&gt;The last guideline to follow was to add a specific file describing the change:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please include a file within your PR named &lt;code&gt;changelog/#.txt&lt;/code&gt;, where &lt;code&gt;#&lt;/code&gt; is your pull request ID. There are many examples under changelog, but the general format is&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;release-note:CATEGORY
COMPONENT: summary of change
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;After submitting the pull-request I copied the number it was given and created a new file named &lt;code&gt;changelog/24479.txt&lt;/code&gt; with a summary of my change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The change
&lt;/h2&gt;

&lt;p&gt;So what did I change? Let me tell you!&lt;/p&gt;

&lt;p&gt;I did not have to spend hours and hours understanding the structure of the code, it only took me a few minutes to find where I should implement my change. I wanted to update the &lt;code&gt;token capabilities&lt;/code&gt; &lt;strong&gt;command&lt;/strong&gt;, so I opened the &lt;strong&gt;command&lt;/strong&gt; directory and found the &lt;code&gt;token_capabilities.go&lt;/code&gt; file. It's almost like professional software developers have created this codebase.&lt;/p&gt;

&lt;p&gt;First of all I had to add the &lt;code&gt;accessor&lt;/code&gt; flag to the command. I did this in the &lt;code&gt;TokenCapabilitiesCommand&lt;/code&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TokenCapabilitiesCommand&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BaseCommand&lt;/span&gt;

    &lt;span class="c"&gt;// add this&lt;/span&gt;
    &lt;span class="n"&gt;flagAccessor&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I had to update the help text for the command to reflect the added flag. I got some feedback from a Vault developer here, read more about that in the next section.&lt;/p&gt;

&lt;p&gt;Next up was to update the &lt;code&gt;Flags()&lt;/code&gt; method on the &lt;code&gt;TokenCapabilitiesCommand&lt;/code&gt; type. Before my change it looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TokenCapabilitiesCommand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FlagSets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flagSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FlagSetHTTP&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;FlagSetOutputFormat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I updated it to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TokenCapabilitiesCommand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FlagSets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flagSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FlagSetHTTP&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;FlagSetOutputFormat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFlagSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Command Options"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BoolVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;BoolVar&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"accessor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flagAccessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;EnvVar&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Completion&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PredictNothing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Usage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;"Treat the argument as an accessor instead of a token."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I specified details about the accessor flag and text that will appear when you use the help command.&lt;/p&gt;

&lt;p&gt;Next up I needed to update the &lt;code&gt;Run&lt;/code&gt; method on the &lt;code&gt;TokenCapabilitiesCommand&lt;/code&gt; type. It was using a simple &lt;code&gt;switch&lt;/code&gt; statement to check for the number of arguments provided, and taking different actions depending on how many. I needed to instead use a more complex switch statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flagAccessor&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not enough arguments with -accessor (expected 2, got %d)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flagAccessor&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Too many arguments with -accessor (expected 2, got %d)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not enough arguments (expected 1-2, got 0)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Too many arguments (expected 1-2, got %d)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some of this code was present in the previous implementation, but to summarize my change I had to check if the &lt;code&gt;-accessor&lt;/code&gt; flag was provided to the command, and if so I needed to check that the correct number of arguments was provided.&lt;/p&gt;

&lt;p&gt;Further down in the &lt;code&gt;Run&lt;/code&gt; method there was the following check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CapabilitiesSelf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Capabilities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This just checked if a token was provided or not. If it was not provided, it used the &lt;code&gt;CapabilitiesSelf&lt;/code&gt; method to see the capabilities of the current token. If a token was provided it instead used the &lt;code&gt;Capabilities&lt;/code&gt; method to check the capabilities for the provided token. With the &lt;code&gt;-accessor&lt;/code&gt; flag in the mix I had to add a third case. I needed to update this from an &lt;code&gt;if&lt;/code&gt;-&lt;code&gt;else&lt;/code&gt; check to another &lt;code&gt;switch&lt;/code&gt; statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CapabilitiesSelf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flagAccessor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CapabilitiesAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Capabilities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This change included the case where a token was not provided but the &lt;code&gt;-accessor&lt;/code&gt; flag was provided. Note that if the &lt;code&gt;-accessor&lt;/code&gt; flag was included, there is a call to &lt;code&gt;client.Sys().CapabilitiesAccessor(...)&lt;/code&gt;. I had to implement this &lt;code&gt;CapabilitiesAccessor&lt;/code&gt; method!&lt;/p&gt;

&lt;p&gt;The home for that code was in &lt;code&gt;api/sys_capabilities.go&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First of all I added the &lt;code&gt;CapabilitiesAccessor&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Sys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CapabilitiesAccessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CapabilitiesAccessorWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;accessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just a wrapper for the &lt;code&gt;CapabilitiesAccessorWithContext&lt;/code&gt; method, with the context argument added.&lt;/p&gt;

&lt;p&gt;I also implement the &lt;code&gt;CapabilitiesAccessorWithContext&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Sys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CapabilitiesAccessorWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancelFunc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withConfiguredTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancelFunc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"accessor"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;accessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;reqPath&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"/v1/sys/capabilities-accessor"&lt;/span&gt;

    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodPost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetJSONBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawRequestWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ParseSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data from server response is empty"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapstructure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapstructure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I won't go into details of the code, it is mostly a wrapper for calling the API which was already implemented (lucky me!) If it had not been implemented there would have been a lot more work, and probably not something I could have completed on my own.&lt;/p&gt;

&lt;p&gt;Now I was done. I did add tests for this change as well, since it was one of the guidelines to follow. I won't cover the tests here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback from Vault developers
&lt;/h2&gt;

&lt;p&gt;I got a wonderful comment from one Vault developer (you can read the conversation in the &lt;a href="https://github.com/hashicorp/vault/pull/24479"&gt;PR&lt;/a&gt;):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Looks good.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then I got some feedback on what the help text for the command should be. I think the suggested changes was good, and accepted them without hesitation.&lt;/p&gt;

&lt;p&gt;I am glad the code I wrote was acceptable!&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;I want to summarize my work with a famous quote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That's one small step for man, one giant leap for mankind.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Replace &lt;em&gt;mankind&lt;/em&gt; with &lt;em&gt;Mattias&lt;/em&gt; in this quote from Niel Armstrong and you have the feeling I am experiencing right now.&lt;/p&gt;

&lt;p&gt;It was a small change. But a huge boost in confidence for me.&lt;/p&gt;

&lt;p&gt;I have no plans to be a recurring contributor to Vault, or any other HashiCorp codebase. But this was a great experience and if I stumble upon something else I think is missing I will give it a go!&lt;/p&gt;

&lt;p&gt;But, am I a software developer now?&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Mentioning &lt;em&gt;open source&lt;/em&gt; and &lt;em&gt;HashiCorp&lt;/em&gt; in the same blog post will probably trigger some of you, but I know HashiCorp products are no longer open-source. It does not matter to me, but please let me know why it matters to you! ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>hashicorp</category>
      <category>vault</category>
      <category>security</category>
      <category>programming</category>
    </item>
    <item>
      <title>Test permutations with Terraform and GitHub Actions</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Thu, 23 Nov 2023 17:20:32 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/test-permutations-with-terraform-and-github-actions-1ja6</link>
      <guid>https://dev.to/mattiasfjellstrom/test-permutations-with-terraform-and-github-actions-1ja6</guid>
      <description>&lt;p&gt;I have been exploring the new &lt;strong&gt;test framework for Terraform 1.6&lt;/strong&gt; extensively since HashiConf in October this year. I have already written two very long posts on the topic of testing and validation with Terraform that you can read here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/mattiasfjellstrom/the-issue-of-recursive-module-calls-in-declarative-infrastructure-as-code-3fk"&gt;A Comprehensive Guide to Testing in Terraform: Keep your tests, validations, checks, and policies in order&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/mattiasfjellstrom/testing-framework-in-terraform-16-a-deep-dive-5e2k"&gt;Testing Framework in Terraform 1.6: A deep-dive&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post I want to illustrate a pattern for scaling up your testing using GitHub Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario
&lt;/h2&gt;

&lt;p&gt;You are part of a platform team developing a Terraform module that sets up an Azure storage account according to a specification appropriate for your organization.&lt;/p&gt;

&lt;p&gt;Your module has dependency on a module created by a different team in your organization. That module sets up an Azure resource group where the storage account your module produces is placed.&lt;/p&gt;

&lt;p&gt;The source code for your module is stored in a GitHub repository and you want to use GitHub Actions to perform testing before you publish new versions of your module.&lt;/p&gt;

&lt;p&gt;The scenario is illustrated in the figure below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.mattias.engineer%2Fposts%2Fterraform-regression-testing%2Fscenario.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.mattias.engineer%2Fposts%2Fterraform-regression-testing%2Fscenario.png" alt="scenario"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You expect that new versions of your module should be compatible with the three most recent minor versions of the other team's resource group module that you depend on. You also expect that your module works as intended for each of the Azure regions that your organization is operating in. These regions are &lt;code&gt;swedencentral&lt;/code&gt;, &lt;code&gt;northeurope&lt;/code&gt;, and &lt;code&gt;westeurope&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your team has already figured out that there will be many tests to write to cover the permutation of the above criteria. To be precise, each individual test you write will need to be repeated 3x3=9 times (three locations, three versions).&lt;/p&gt;

&lt;p&gt;The Terraform module your team is developing consists of a single &lt;code&gt;main.tf&lt;/code&gt; file (to keep this scenario simple):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.tf&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/azurerm"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3.80.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;random&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/random"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3.5.1"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"resource_group_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_id"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;keepers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;byte_length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"st${random_id.this.dec}"&lt;/span&gt;
  &lt;span class="nx"&gt;access_tier&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Hot"&lt;/span&gt;
  &lt;span class="nx"&gt;account_kind&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StorageV2"&lt;/span&gt;
  &lt;span class="nx"&gt;account_replication_type&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LRS"&lt;/span&gt;
  &lt;span class="nx"&gt;account_tier&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;enable_https_traffic_only&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important parts to notice in this module is that it takes an input variable named &lt;code&gt;resource_group_name&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"resource_group_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This variable is used in a data source for a resource group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this data source is later referenced in arguments of the storage account resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... &lt;/span&gt;

  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For illustrative purposes your team is interested in running the following test as defined in &lt;code&gt;tests/main.tftest.hcl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/main.tftest.hcl&lt;/span&gt;
&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"setup"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;location&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"swedencentral"&lt;/span&gt;
    &lt;span class="nx"&gt;name_suffix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tftest-swedencentral-1.1.0"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app.terraform.io/your-tf-org/resource-group-module/azurerm"&lt;/span&gt;
    &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.1.0"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"proper_tags_should_be_propagated"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;alltrue&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Proper tags are not propagated to the storage account"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two &lt;code&gt;run&lt;/code&gt; blocks in this test file. The first &lt;code&gt;run&lt;/code&gt; block named &lt;code&gt;setup&lt;/code&gt; uses the module you depend on. In this case it specifically uses version &lt;code&gt;1.1.0&lt;/code&gt; of the module. This &lt;code&gt;run&lt;/code&gt; block defines a &lt;code&gt;location&lt;/code&gt; variable that is set to &lt;code&gt;swedencentral&lt;/code&gt;. This covers one of the nine cases we want to test.&lt;/p&gt;

&lt;p&gt;The second &lt;code&gt;run&lt;/code&gt; block is our actual test. It makes sure that appropriate tags are propagated to the storage account from the resource group. Specifically we require that two tags are set, &lt;code&gt;source&lt;/code&gt; and &lt;code&gt;module&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we turn to the solution to the issue of how to test all permutations of versions and locations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;There are a number of options for how to solve the permutation of tests you need to run in this scenario. In this post I present a simple solution using a &lt;code&gt;strategy&lt;/code&gt; in GitHub Actions.&lt;/p&gt;

&lt;p&gt;Before we look at the GitHub Actions workflow, let's look at a modified version of our test file, this one stored in &lt;code&gt;templates/main.tftest.hcl.tpl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// templates/main.tftest.hcl.tpl&lt;/span&gt;
&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"setup"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;location&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"{{LOCATION}}"&lt;/span&gt;
    &lt;span class="nx"&gt;name_suffix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tftest-{{LOCATION}}-{{VERSION}}"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app.terraform.io/your-tf-org/resource-group-module/azurerm"&lt;/span&gt;
    &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"{{VERSION}}"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"proper_tags_should_be_propagated"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;alltrue&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Proper tags are not propagated to the storage account"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only difference to the test file shown before is that the explicit version is replaced by a placeholder value &lt;code&gt;{{VERSION}}&lt;/code&gt;, and each explicit location is replaced by &lt;code&gt;{{LOCATION}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The GitHub Actions workflow is located in the file &lt;code&gt;.github/workflows/tftest.yaml&lt;/code&gt; in our repo. We start building our workflow like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workflow_dispatch&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start with we only want to trigger the workflow manually, that is why we have &lt;code&gt;on: workflow_dispatch&lt;/code&gt; as the trigger. We add a few permissions that will be required for the Azure login action where we use a federated identity in Azure (see &lt;a href="https://github.com/marketplace/actions/azure-login#login-with-openid-connect-oidc-recommended" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt; for details on how to set this up).&lt;/p&gt;

&lt;p&gt;Next we start defining our job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.1.0"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.2.0"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.3.0"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;swedencentral"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;westeurope"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;northeurope"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have a single job named &lt;code&gt;test&lt;/code&gt;. We will run the job on Ubuntu, using the latest version available. Next we define the &lt;code&gt;strategy&lt;/code&gt; with the following configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fail-fast: false&lt;/code&gt;, this is required to not have all tests cancelled if a single test fails. The default value for &lt;code&gt;fail-fast&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;matrix&lt;/code&gt; is used to configure a few settings that we want to vary between tests. In this case we vary &lt;code&gt;version&lt;/code&gt; and &lt;code&gt;location&lt;/code&gt;. There will be one run for each combination of version and location, for a total of nine runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last part of the workflow consists of the steps we want to run for each test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/login@v1&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;client-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_CLIENT_ID }}&lt;/span&gt;
      &lt;span class="na"&gt;tenant-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_TENANT_ID }}&lt;/span&gt;
      &lt;span class="na"&gt;subscription-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_SUBSCRIPTION_ID }}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;sed 's/{{VERSION}}/${{ matrix.version }}/g; s/{{LOCATION}}/${{ matrix.location }}/g' \&lt;/span&gt;
        &lt;span class="s"&gt;templates/main.tftest.hcl.tpl &amp;gt; tests/main.tftest.hcl&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v2&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;terraform_wrapper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform init&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;TF_TOKEN_app_terraform_io&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.TF_TOKEN }}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first step uses the &lt;code&gt;azure/login@v1&lt;/code&gt; action to sign in to Azure. This is required because we will be creating resources in Azure using Terraform (remember: the Terraform test framework runs &lt;em&gt;actual&lt;/em&gt; plan and apply operations, creating &lt;em&gt;actual&lt;/em&gt; resources!)&lt;/p&gt;

&lt;p&gt;The second step of the workflow uses the &lt;code&gt;actions/checkout@v4&lt;/code&gt; action to check out the source code. If you come from an Azure DevOps background you might be surprised that you need to explicitly add this step. I prefer that GitHub Actions requires that you add this step if you intend to do something with the source code in the repository, I find the behind-the-scenes checkout in Azure DevOps to be confusing. &lt;/p&gt;

&lt;p&gt;The third step requires some explanation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;sed 's/{{VERSION}}/${{ matrix.version }}/g; s/{{LOCATION}}/${{ matrix.location }}/g' \&lt;/span&gt;
      &lt;span class="s"&gt;templates/main.tftest.hcl.tpl &amp;gt; tests/main.tftest.hcl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use &lt;code&gt;sed&lt;/code&gt; to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find each occurrence of &lt;code&gt;{{VERSION}}&lt;/code&gt; in the file &lt;code&gt;templates/main.tftest.hcl.tpl&lt;/code&gt; and replace it by &lt;code&gt;${{ matrix.version }}&lt;/code&gt; which in turn is replaced by GitHub Actions with a value from &lt;code&gt;version&lt;/code&gt; in the &lt;code&gt;matrix&lt;/code&gt; we defined above. &lt;/li&gt;
&lt;li&gt;Find each occurrence of &lt;code&gt;{{LOCATION}}&lt;/code&gt; in the file &lt;code&gt;templates/main.tftest.hcl.tpl&lt;/code&gt; and replace it by &lt;code&gt;${{ matrix.location }}&lt;/code&gt; which in turn is replaced by GitHub Actions with a value from &lt;code&gt;location&lt;/code&gt; in the &lt;code&gt;matrix&lt;/code&gt; we defined above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result of these search-and-replace operations are stored in &lt;code&gt;tests/main.tftest.hcl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here we opted for a simple &lt;code&gt;sed&lt;/code&gt; search-and-replace, which is fine when the number of variables we need to replace are few. If we needed to replace many more variables than two we would probably look into using a templating tool for this instead. Use common sense here, if &lt;code&gt;sed&lt;/code&gt; works for your purposes then there is no need to involve any other tool.&lt;/p&gt;

&lt;p&gt;The last few steps sets up the Terraform CLI tool, initializes Terraform, and finally runs the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v2&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;terraform_wrapper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform init&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;TF_TOKEN_app_terraform_io&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.TF_TOKEN }}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;terraform init&lt;/code&gt; step requires an environment variable &lt;code&gt;TF_TOKEN_app_terraform_io&lt;/code&gt; with the value of a Terraform Cloud token. This is because the module we are using is located in a private Terraform registry (see &lt;a href="https://developer.hashicorp.com/terraform/cli/config/config-file#environment-variable-credentials" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt; for details on this).&lt;/p&gt;

&lt;p&gt;The full workflow for reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workflow_dispatch&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.1.0"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.2.0"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.3.0"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;swedencentral"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;westeurope"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;northeurope"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Azure login&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/login@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;client-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_CLIENT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;tenant-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_TENANT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;subscription-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_SUBSCRIPTION_ID }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;sed 's/{{VERSION}}/${{ matrix.version }}/g; s/{{LOCATION}}/${{ matrix.location }}/g' \&lt;/span&gt;
            &lt;span class="s"&gt;templates/main.tftest.hcl.tpl &amp;gt; tests/main.tftest.hcl&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;terraform_wrapper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform init&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;TF_TOKEN_app_terraform_io&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.TF_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we trigger this single workflow we can see that nine individual jobs are started:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.mattias.engineer%2Fposts%2Fterraform-regression-testing%2Fgithub-actions-start.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.mattias.engineer%2Fposts%2Fterraform-regression-testing%2Fgithub-actions-start.png" alt="github actions have started"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Jumping into Azure after a short while we can confirm that nine different resource groups have been created:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.mattias.engineer%2Fposts%2Fterraform-regression-testing%2Fazure-resource-groups.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.mattias.engineer%2Fposts%2Fterraform-regression-testing%2Fazure-resource-groups.png" alt="resource groups in Azure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, after a few minutes all jobs are finished&lt;sup id="fnref1"&gt;1&lt;/sup&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.mattias.engineer%2Fposts%2Fterraform-regression-testing%2Fgithub-actions-results.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.mattias.engineer%2Fposts%2Fterraform-regression-testing%2Fgithub-actions-results.png" alt="github"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The results are clearly indicating that a regression have been introduced in version 1.3.0 of the resource group module that we depend on. We can conclude that our module is not ready to be release as a new version, and it is time to talk to the team responsible for the resource group module to see what changes they have made.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;When you use a &lt;code&gt;matrix&lt;/code&gt; with many different variables (I use two) the number of billable minutes in GitHub Actions can quickly escalate. In the simple example I used 43 billable minutes. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>hashicorp</category>
      <category>terraform</category>
      <category>testing</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Testing Framework in Terraform 1.6: A deep-dive</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Mon, 13 Nov 2023 21:11:43 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/testing-framework-in-terraform-16-a-deep-dive-5e2k</link>
      <guid>https://dev.to/mattiasfjellstrom/testing-framework-in-terraform-16-a-deep-dive-5e2k</guid>
      <description>&lt;p&gt;In my previous blog post &lt;a href="https://dev.to/mattiasfjellstrom/a-comprehensive-guide-to-testing-in-terraform-keep-your-tests-validations-checks-and-policies-in-order-543h"&gt;A Comprehensive Guide to Testing in Terraform: Keep your tests, validations, checks, and policies in order&lt;/a&gt; I went through all the options for testing and validation that are available to you when you write your Terraform configurations and modules. We saw check blocks, pre-conditions and post-conditions related to a resource's lifecycle, custom conditions for input variables and output values, and more. The latest and greatest topic I covered was the &lt;strong&gt;testing framework&lt;/strong&gt; that arrived in Terraform 1.6.&lt;/p&gt;

&lt;p&gt;In this post I want to focus more on the testing framework to uncover the possibilities that it brings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background on testing with Terraform
&lt;/h2&gt;

&lt;p&gt;If this is the first time you hear about the new testing framework for Terraform I would like to give you a short introduction to what it is.&lt;/p&gt;

&lt;p&gt;Terraform 1.6 brought a new testing framework to general availability after it had been available as an experimental feature for a period of time. The &lt;a href="https://github.com/hashicorp/terraform/releases/tag/v1.6.0"&gt;release notes&lt;/a&gt; for version 1.6 listed a single new feature&lt;sup id="fnref1"&gt;1&lt;/sup&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;terraform test&lt;/code&gt;: The &lt;code&gt;terraform test&lt;/code&gt; command is now generally available. This comes with a significant change to how tests are written and executed, based on feedback from the experimental phase.&lt;/p&gt;

&lt;p&gt;Terraform tests are written in &lt;code&gt;.tftest.hcl&lt;/code&gt; files, containing a series of &lt;code&gt;run&lt;/code&gt; blocks. Each &lt;code&gt;run&lt;/code&gt; block executes a Terraform plan and optional apply against the Terraform configuration under test and can check conditions against the resulting plan and state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What we learn from the release notes is that we can now write tests for our Terraform configurations by including one or more &lt;code&gt;.tftest.hcl&lt;/code&gt; files that each contain a series of &lt;code&gt;run&lt;/code&gt; blocks. We also learn that a &lt;code&gt;run&lt;/code&gt; block runs &lt;code&gt;terraform plan&lt;/code&gt; or  &lt;code&gt;terraform apply&lt;/code&gt; commands. This means that these tests could create real infrastructure from your configuration. For me this is a good thing. I am a strong believer in when it comes to testing infrastructure-as-code there is no way to be sure it will work unless you actually deploy it for real. Why is that? There are just too many things that could go wrong. There might be hidden dependencies that you have no idea of before you actually try to create new infrastructure.&lt;/p&gt;

&lt;p&gt;What is not clear from the release notes is who this testing framework is intended for. Is it meant for all Terraform users? Should you immediately jump on the TDD-with-Terraform train and start writing tests for all Terraform configuration? This is not the case. At least this is not the primary intended case. The testing framework is designed for &lt;strong&gt;module producers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Are you in charge of creating infrastructure modules internally for your organization or publicly for the Terraform community? Then you are a module producer and this testing framework is for you.&lt;/p&gt;

&lt;p&gt;Are you consuming modules in order to create the infrastructure for your application? Then this testing framework is not primarily intended for you. However, there is nothing stopping you from using it if you think it makes sense for your situation.&lt;/p&gt;

&lt;p&gt;Module producers write code that other users will consume. Users of your modules depend on the contract that your module exposes. What is part of the module contract? Generally this includes the following items:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The input variables your module expects.&lt;/li&gt;
&lt;li&gt;The output values your module produces.&lt;/li&gt;
&lt;li&gt;Any externally visible resources your module creates. This could include configuration files, application gateways in Azure, network loadbalancers in AWS, a Cloud Bigtable table in GCP, or it could be pretty much anything else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point might make you wonder if there are any resources created in a module that is not part of the contract? There definitely could be. Some resources could be internal implementation details that are required in order to construct the rest of the infrastructure. If a resource could be swapped out with a different resource without module consumers noticing then it is an internal implementation detail and not part of the contact.&lt;/p&gt;

&lt;p&gt;If you are a module producer and you make an update of your module where you unintentionally make a significant change to the contract your module exposes, then this mistake could end up causing trouble for your module consumers.&lt;/p&gt;

&lt;p&gt;This is exactly the reasoning behind any other kind of test in software development, testing Terraform modules is no different!&lt;/p&gt;

&lt;p&gt;One last point to make about the new testing framework is that you write your tests in &lt;strong&gt;HashiCorp Configuration Language&lt;/strong&gt; (HCL). This means there is no need to learn a new language in order to write tests for your Terraform code. There is no need to install an additional test tool that you need to keep track of and update and scan for vulnerabilities and so on. There is no need to mix your Terraform configuration with a bunch of test-related framework files. Run your tests and deploy your infrastructure using one and the same Terraform binary.&lt;/p&gt;

&lt;p&gt;With all that background out of the way, let's move on to seeing all the nitty-gritty details of what this testing framework can do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nomenclature
&lt;/h2&gt;

&lt;p&gt;Sometimes I need to remind myself of the nomenclature of the Terraform HCL code. To make sure we are all on the same page I introduce the nomenclature I use here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;block&lt;/strong&gt; are containers of other content. A block have a &lt;strong&gt;block type&lt;/strong&gt; and zero or more &lt;strong&gt;labels&lt;/strong&gt;. The generic block looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BLOCK&lt;/span&gt; &lt;span class="nx"&gt;TYPE&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;BLOCK LABEL 1&amp;gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;BLOCK LABEL 2&amp;gt;"&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;BLOCK LABEL N&amp;gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# block content&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;In Terraform the number of labels is zero, one, or two. A block can contain other blocks. Some common block types are &lt;code&gt;terraform&lt;/code&gt;, &lt;code&gt;provider&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, and &lt;code&gt;run&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An &lt;strong&gt;expression&lt;/strong&gt; represents a literal value such as a string or a number, or they could be referencing other values such as the name of a resource. An expression could also be more complex consisting of function calls, string interpolations, and references. Some examples of expressions:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="s2"&gt;"this is a string value"&lt;/span&gt;
&lt;span class="mi"&gt;12345&lt;/span&gt;
&lt;span class="nx"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_account&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="s2"&gt;"rg-${var.name}-${var.location}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An &lt;strong&gt;argument&lt;/strong&gt; is the assignment of a value (from an expression) to a name. Arguments appear inside of blocks. Some examples of arguments:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-my-resource-group"&lt;/span&gt;
&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;number_of_regions&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if the details of the HCL were familiar from before the nomenclature might be unfamiliar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test framework patterns
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;run&lt;/code&gt; block is what executes a given test when we run &lt;code&gt;terraform test&lt;/code&gt;. This block is central to the testing framework, so this is a block you need to become familiar with.&lt;/p&gt;

&lt;p&gt;In the following subsections I will go through three &lt;em&gt;testing patterns&lt;/em&gt;&lt;sup id="fnref2"&gt;2&lt;/sup&gt; that you might see in Terraform.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before we look at the patterns let's briefly look at a typical directory structure for Terraform tests.&lt;/p&gt;

&lt;p&gt;First of all, remember that your test files should use the &lt;code&gt;.tftest.hcl&lt;/code&gt; file ending. If not, the &lt;code&gt;terraform test&lt;/code&gt; command will not execute the &lt;code&gt;run&lt;/code&gt; blocks for your tests.&lt;/p&gt;

&lt;p&gt;When you execute &lt;code&gt;terraform test&lt;/code&gt; you should be located in the module root directory. This is the directory where your primary &lt;code&gt;main.tf&lt;/code&gt; file exists. The Terraform binary will look for your test files in the root directory or in a directory named &lt;code&gt;tests&lt;/code&gt;. I recommend you place your tests in the &lt;code&gt;tests&lt;/code&gt; directory, and do not place test files in the module root directory.&lt;/p&gt;

&lt;p&gt;A typical directory structure for a simple module with tests is this:&lt;/p&gt;


&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tree &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;.
&lt;/span&gt;&lt;span class="go"&gt;├── main.tf
├── outputs.tf
├── providers.tf
├── tests
│   └── main.tftest.hcl
└── variables.tf
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;If you place your test files somewhere else you need to add &lt;code&gt;-test-directory=path/to/tests&lt;/code&gt; to the &lt;code&gt;terraform test&lt;/code&gt; command. But once again I recommend that you keep your test files in the &lt;code&gt;tests&lt;/code&gt; directory to avoid confusing future contributors to your module.&lt;/p&gt;

&lt;p&gt;How many test files should you have? The simple answer is &lt;em&gt;it depends&lt;/em&gt;! If you are building a large module with many moving parts you will probably need to have several test files divided up into coherent and related parts that test a certain part of your module. If your module have a small interface (variables and outputs) it might suffice with a single test file. Use common sense here, if it feels like a file has too many tests then it probable does. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Pattern 1: Assertions
&lt;/h3&gt;

&lt;p&gt;The first pattern is simple, it consists of a &lt;code&gt;run&lt;/code&gt; block with a nested &lt;code&gt;assert&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"file_contents_should_be_valid_json"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;try&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsondecode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local_file&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="err"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The file is not valid JSON"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down this &lt;code&gt;run&lt;/code&gt; block:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;run&lt;/code&gt; block has one label. This label is the name of the test. You should give your test a self-explanatory name that describes what it does. If the test fails you should immediately know why it failed. In this example the name is &lt;code&gt;file_contents_should_be_valid_json&lt;/code&gt;. If this test fails I know that the contents of a file was not valid JSON.&lt;/li&gt;
&lt;li&gt;This &lt;code&gt;run&lt;/code&gt; block executes a &lt;code&gt;terraform plan&lt;/code&gt; command. You specify what command you would like the test to execute in the &lt;code&gt;command = &amp;lt;command&amp;gt;&lt;/code&gt; argument. If you leave this out it will default to execute an &lt;code&gt;apply&lt;/code&gt; command. Personally I think it is a good idea to be clear and always add the &lt;code&gt;command = &amp;lt;command&amp;gt;&lt;/code&gt; argument to be explicit in what the test does.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;run&lt;/code&gt; block can contain &lt;strong&gt;zero or more&lt;/strong&gt; nested &lt;code&gt;assert&lt;/code&gt; blocks. Each &lt;code&gt;assert&lt;/code&gt; block has a &lt;code&gt;condition = &amp;lt;expression&amp;gt;&lt;/code&gt; argument where &lt;code&gt;&amp;lt;expression&amp;gt;&lt;/code&gt; should evaluate to &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; to indicate if the test passes (&lt;code&gt;true&lt;/code&gt;) or fails (&lt;code&gt;false&lt;/code&gt;). If &lt;code&gt;&amp;lt;expression&amp;gt;&lt;/code&gt; evaluates to &lt;code&gt;false&lt;/code&gt; then the expression in &lt;code&gt;error_message = &amp;lt;expression&amp;gt;&lt;/code&gt; will be displayed to the terminal (or in Terraform Cloud). In this case the error message is &lt;code&gt;The file is not valid JSON&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although this example showed a single &lt;code&gt;run&lt;/code&gt; block containing a single &lt;code&gt;assert&lt;/code&gt; block, remember that you could include multiple &lt;code&gt;run&lt;/code&gt; blocks, each containing multiple &lt;code&gt;assert&lt;/code&gt; blocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 2: Expecting failures
&lt;/h3&gt;

&lt;p&gt;The second pattern concerns tests where we expect the test to fail, and we want the test to report success if it does. This is a common testing strategy. The following &lt;code&gt;run&lt;/code&gt; block has a nested &lt;code&gt;variable&lt;/code&gt; block and an &lt;code&gt;expect_failures = [ ... ]&lt;/code&gt; argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"bad_input_url_should_stop_deployment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;config_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.com"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;expect_failures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config_url&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are some new things to look at in this &lt;code&gt;run&lt;/code&gt; block so let's break it down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;variables&lt;/code&gt; block allows you to provide input values for any variables that your module expects. In this case the &lt;code&gt;variables&lt;/code&gt; block is provided as a nested block to the &lt;code&gt;run&lt;/code&gt; block, but it could also be provided as a standalone block outside of any &lt;code&gt;run&lt;/code&gt; blocks. In that case the values would apply to all &lt;code&gt;run&lt;/code&gt; blocks in the entire file. If you include a standalone &lt;code&gt;variables&lt;/code&gt; block you can still include nested &lt;code&gt;variables&lt;/code&gt; block inside of a &lt;code&gt;run&lt;/code&gt; block to override the global values.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;expect_failures = [ ... ]&lt;/code&gt; specifies that we expect this test to fail, and we list the reasons for failure in the array expression of the argument. In this particular example I say that I expect this test to fail due to the variable named &lt;code&gt;config_url&lt;/code&gt;. This basically mean that I validate the value provided for the &lt;code&gt;config_url&lt;/code&gt; variable in my Terraform module, and the value provided in this test (&lt;code&gt;http://example.com&lt;/code&gt;) should result in a failing validation. If the plan can proceed as normal without any failures, then this test would fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is worth spending some time discussing &lt;code&gt;expect_failures&lt;/code&gt;. The values in this array must be &lt;em&gt;checkable objects&lt;/em&gt; with associated custom conditions. In my &lt;a href="//./../terraform-testing-and-validation/"&gt;previous article&lt;/a&gt; I wrote a lot about custom conditions. Objects that can include custom conditions are variables, outputs, resources, data sources, and &lt;code&gt;check&lt;/code&gt; blocks.&lt;/p&gt;

&lt;p&gt;An important point about these custom conditions is that all of them except for the &lt;code&gt;check&lt;/code&gt; block will actually cause Terraform to halt the execution of a plan or apply operation. What does this mean for your tests? It means that if you want to combine &lt;code&gt;expect_failures&lt;/code&gt; with &lt;code&gt;assert&lt;/code&gt; blocks you have to be careful in how you construct your module and your corresponding tests. If you include a variable in the &lt;code&gt;expect_failures&lt;/code&gt; array of values and at the same time have an &lt;code&gt;assert&lt;/code&gt; block that expects a plan to finish, then the &lt;code&gt;assert&lt;/code&gt; block would never even be evaluated because the custom condition for the variable would halt the execution of the plan.&lt;/p&gt;

&lt;p&gt;For this reason I suggest you keep your tests to either use one or more &lt;code&gt;assert&lt;/code&gt; blocks, or use the &lt;code&gt;expect_failures = [ ... ]&lt;/code&gt; argument, but not both unless you really know what you are doing.&lt;/p&gt;

&lt;p&gt;Note that the array value to &lt;code&gt;expect_failures&lt;/code&gt; could contain multiple values. But you most likely would not want to mix the type of checkable objects you include in this array because of the reason discussed above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: Using helper modules
&lt;/h3&gt;

&lt;p&gt;Sometimes it is necessary to create supporting infrastructure before you can test your module. This could be the case if your module creates resources in Azure and it expects that you use an existing resource group for all the resources. In order to test a module like that there must be an existing resource group you can use. A simple solution to this is to create a resource group up front and just let it sit there in your cloud environment for as long as required. A better solution is to create the resource group when you launch the &lt;code&gt;terraform test&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;To illustrate what this looks like we have the following directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tree &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;.
&lt;/span&gt;&lt;span class="go"&gt;├── main.tf
├── outputs.tf
├── testing
│   └── setup
│       └── main.tf
├── tests
│   └── main.tftest.hcl
└── variables.tf

4 directories, 5 files
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have created a &lt;code&gt;testing&lt;/code&gt; directory that contains a &lt;code&gt;setup&lt;/code&gt; directory with a &lt;code&gt;main.tf&lt;/code&gt; file. The contents of this file is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// testing/setup/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"resource_group_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"location"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"rg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a simple file that uses the &lt;code&gt;azurerm&lt;/code&gt; provider to create a resource group. The module under test is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.tf&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/azurerm"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 3.0.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;resource_name_suffix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.name_suffix}-${var.location}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_service_plan"&lt;/span&gt; &lt;span class="s2"&gt;"plan"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"plan-${local.resource_name_suffix}"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;os_type&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Linux"&lt;/span&gt;
  &lt;span class="nx"&gt;sku_name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appservice_plan_sku&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_linux_web_app"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-${local.resource_name_suffix}"&lt;/span&gt;
  &lt;span class="nx"&gt;service_plan_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_service_plan&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;

  &lt;span class="nx"&gt;https_only&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;site_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;always_on&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"F1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"D1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appservice_plan_sku&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;http2_enabled&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;minimum_tls_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.2"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This module creates two resources. An App Service plan and a Linux Web App. The &lt;code&gt;variables.tf&lt;/code&gt; file has the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// variables.tf&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"name_suffix"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"resource_group_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"location"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"appservice_plan_sku"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"B1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"B2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"B3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"D1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"F1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"S1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"S2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"S3"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appservice_plan_sku&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Please provide a valid App Service Plan SKU"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How do we create the resource group module before we run our tests? The test file &lt;code&gt;main.tftest.hcl&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/main.tftest.hcl&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-app-service-test"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"swedencentral"&lt;/span&gt;
  &lt;span class="nx"&gt;appservice_plan_sku&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"F1"&lt;/span&gt;
  &lt;span class="nx"&gt;name_suffix&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apptest"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"setup"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./testing/setup"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"http_should_not_be_allowed"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_linux_web_app&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;https_only&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Web App accepts HTTP traffic"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"confirm_always_on"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name_suffix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"testingalwayson"&lt;/span&gt;
    &lt;span class="nx"&gt;appservice_plan_sku&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"S1"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_linux_web_app&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;always_on&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Always-On is off for S1 SKU"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are few new things to look at in this test file. Let's break it down.&lt;/p&gt;

&lt;p&gt;First of all we configure the &lt;code&gt;azurerm&lt;/code&gt; provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows you to configure the provider in any way that fits your tests. In this case I use default settings (an empty &lt;code&gt;features&lt;/code&gt; block is required). Note that you could also configure the provider to use a separate test subscription instead of any other default subscription you have configured.&lt;/p&gt;

&lt;p&gt;The next piece in the test file defines global variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-app-service-test"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"swedencentral"&lt;/span&gt;
  &lt;span class="nx"&gt;appservice_plan_sku&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"F1"&lt;/span&gt;
  &lt;span class="nx"&gt;name_suffix&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apptest"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These variables will be used for the setup module and all the following tests, unless the tests override these values.&lt;/p&gt;

&lt;p&gt;Next we have the setup module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"setup"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./testing/setup"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A setup (or helper) module is created in its own &lt;code&gt;run&lt;/code&gt; block. I set the label of this block to &lt;code&gt;setup&lt;/code&gt;, but you can set it to whatever fits your context. The &lt;code&gt;run&lt;/code&gt; block only contains a nested &lt;code&gt;module&lt;/code&gt; block that specifies the source of the module to be my module located in the &lt;code&gt;testing/setup&lt;/code&gt; directory. This &lt;code&gt;run&lt;/code&gt; block is the first &lt;code&gt;run&lt;/code&gt; block in the test file, so it will be run first (they are run in order). If I place the setup &lt;code&gt;run&lt;/code&gt; block somewhere else in the file then the tests defined above the setup block would fail because the resource group would not exist.&lt;/p&gt;

&lt;p&gt;The rest of the file contains two tests in two separate &lt;code&gt;run&lt;/code&gt; blocks. The first block is similar to what we have seen before, but notice that we have a nested &lt;code&gt;variables&lt;/code&gt; block in the other &lt;code&gt;run&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"confirm_always_on"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name_suffix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"testingalwayson"&lt;/span&gt;
    &lt;span class="nx"&gt;appservice_plan_sku&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"S1"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_linux_web_app&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;always_on&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Always-On is off for S1 SKU"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that for this test we override the &lt;code&gt;name_suffix&lt;/code&gt; and &lt;code&gt;appservice_plan_sku&lt;/code&gt; variables.&lt;/p&gt;

&lt;p&gt;I can run the tests with &lt;code&gt;terraform test&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;terraform &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="go"&gt;tests/main.tftest.hcl... in progress
  run "setup"... pass
  run "http_should_not_be_allowed"... pass
  run "confirm_always_on"... pass
tests/main.tftest.hcl... tearing down
tests/main.tftest.hcl... pass

Success! 3 passed, 0 failed.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the output says &lt;code&gt;3 passed&lt;/code&gt; even though we only really had two tests. How come? This is because the setup module runs inside a &lt;code&gt;run&lt;/code&gt; block, so it is considered to be a test from Terraform's point of view. I think this is a bit unfortunate, but for now we'll have to live with it.&lt;/p&gt;

&lt;p&gt;My tests in this case used &lt;code&gt;command = plan&lt;/code&gt;, so they are relatively fast to run. When you use &lt;code&gt;command = apply&lt;/code&gt; you have to prepare for a potentially long test run, depending on what resources your module creates. Imagine if you have a module that creates multiple Kubernetes clusters and installs various components in these clusters, then an &lt;code&gt;apply&lt;/code&gt; could take some time. Especially if you run multiple independent tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform test state file and resource destruction
&lt;/h2&gt;

&lt;p&gt;How does Terraform know what resources it should remove and in what order it should do it? If you are familiar with Terraform you know that there is usually a state file somewhere. When you run tests Terraform keeps the state files in memory, so you won't see any state files appearing in your module directory.&lt;/p&gt;

&lt;p&gt;Terraform creates one state file for the main configuration under test, and one state file for each alternate module that you create through a &lt;code&gt;run&lt;/code&gt; block. An example of an alternate module is the setup module we saw in an example above.&lt;/p&gt;

&lt;p&gt;The state files are created in the order of the tests, and they are destroyed in the reverse order. An illustrative sample of what state files are created, updated, and destroyed and in what order, is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// first alternate module call&lt;/span&gt;
&lt;span class="c1"&gt;// creates state file for modules/setup-1.tf&lt;/span&gt;
&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"setup-1"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"modules/setup-1.tf"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// second alternate module call&lt;/span&gt;
&lt;span class="c1"&gt;// creates state file for modules/setup-2.tf&lt;/span&gt;
&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"setup-2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"modules/setup-2.tf"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// test the main configuration&lt;/span&gt;
&lt;span class="c1"&gt;// creates the main statefile for the module's main.tf&lt;/span&gt;
&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"test-1"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// third alternate module call, once again to the setup-2 module&lt;/span&gt;
&lt;span class="c1"&gt;// updates the state file for modules/setup-2.tf&lt;/span&gt;
&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"setup-2-again"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"modules/setup-2.tf"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// second test for the main configuration&lt;/span&gt;
&lt;span class="c1"&gt;// updates the main statefile for the module's main.tf&lt;/span&gt;
&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"test-2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// After everything is run clean-up starts:&lt;/span&gt;
&lt;span class="c1"&gt;// 1. The module's main.tf state file is destroyed&lt;/span&gt;
&lt;span class="c1"&gt;// 2. The alternate modules state files are destroyed in&lt;/span&gt;
&lt;span class="c1"&gt;//    reverse order from how they were created&lt;/span&gt;
&lt;span class="c1"&gt;//      - first the modules/setup-2 state file is destroyed&lt;/span&gt;
&lt;span class="c1"&gt;//      - then the modules/setup-1 state file is destroyed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A question that came to my mind when I first heard about the test framework was &lt;em&gt;what happens if the test fails the destruction of resources&lt;/em&gt;? Let's see what happens!&lt;/p&gt;

&lt;p&gt;I will run a test where an Azure App Service is created. I will use the setup module from before where I created a resource group. To make the test fail I will issue the following Azure CLI command in order to lock the resource group so that Terraform can't destroy it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;az lock create &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  --name failure \
  --resource-group rg-app-service-test \
  --lock-type ReadOnly
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test output is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;terraform &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="go"&gt;tests/main.tftest.hcl... in progress
  run "setup"... pass
&lt;/span&gt;&lt;span class="c"&gt;  .
&lt;/span&gt;&lt;span class="go"&gt;  . (output truncated)
&lt;/span&gt;&lt;span class="c"&gt;  .
&lt;/span&gt;&lt;span class="go"&gt;Terraform left the following resources in state after executing
tests/main.tftest.hcl/http_should_not_be_allowed, and they need to
be cleaned up manually:
  - azurerm_linux_web_app.app
  - azurerm_service_plan.plan
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There we go!&lt;/p&gt;

&lt;p&gt;We are instructed that a number of resources need to be cleaned up manually. In Azure this is usually relatively simple if you have put all resources in the same resource group. However, if you are working with AWS you might be in for a tedious cleanup process if your module created a lot of resources!&lt;/p&gt;

&lt;p&gt;I can see that this behavior could be an issue during development of your module and tests where you are not sure if everything works as intended. You will most likely end up with a few failing test cleanups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring the test command in the CLI
&lt;/h2&gt;

&lt;p&gt;To cover everything we can about the test framework let's see what else we can do with the &lt;code&gt;terraform test&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;terraform &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="go"&gt;Usage: terraform [global options] test [options]

[ background description truncated for brevity ... ]

Options:

  -cloud-run=source     If specified, Terraform will execute this test run
                        remotely using Terraform Cloud. You must specify the
                        source of a module registered in a private module
                        registry as the argument to this flag. This allows
                        Terraform to associate the cloud run with the correct
                        Terraform Cloud module and organization.

  -filter=testfile      If specified, Terraform will only execute the test files
                        specified by this flag. You can use this option multiple
                        times to execute more than one test file.

  -json                 If specified, machine readable output will be printed in
                        JSON format

  -no-color             If specified, output won't contain any color.

  -test-directory=path  Set the Terraform test directory, defaults to "tests".

  -var 'foo=bar'        Set a value for one of the input variables in the root
                        module of the configuration. Use this option more than
                        once to set more than one variable.

  -var-file=filename    Load variable values from the given file, in addition
                        to the default files terraform.tfvars and *.auto.tfvars.
                        Use this option more than once to include more than one
                        variables file.

  -verbose              Print the plan or state for each test run block as it
                        executes.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few interesting flags we can use. I want to highlight a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-cloud-run=source&lt;/code&gt; is useful if you have your module in a private registry in Terraform Cloud, and you want to trigger a test run in the cloud. I will cover testing in Terraform Cloud in a future post.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-filter&lt;/code&gt; is useful if you have a lot of test files and you would only want one or a few of the files to run. This is especially useful if you are testing a large module where your tests execute apply operations that take a long time.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-test-directory&lt;/code&gt; is useful if you place your test files somewhere else than in the &lt;code&gt;tests&lt;/code&gt; directory. But as I mentioned earlier in this article you should probably stick to using the &lt;code&gt;tests&lt;/code&gt; directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post we have looked at most of what the new testing framework for Terraform 1.6 has to offer. That is in fact not true. There are more we can say about the testing framework when it comes to Terraform Cloud. In a future post I will cover how we run tests in Terraform Cloud and some of the unique features that are available there.&lt;/p&gt;

&lt;p&gt;The example patterns in this post have intentionally been left relatively simple. In reality creating good tests for your modules will require a lot of work. My purpose of this post has been to illustrate what we can do, what syntax is available, and a few of the behaviors we can expect from this framework.&lt;/p&gt;

&lt;p&gt;I expect that there will be additional features added as HashiCorp receives feedback from users of this framework. We live in exciting times!&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Apart from this single new feature there were enhancements and bug fixes included as well. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;I call them patterns here to use a familiar nomenclature. As with all patterns you will most likely not see them isolated in the real world. All patterns I present are most likely mixed and matched for real Terraform modules. The idea with patterns here is to introduce the testing framework piece by piece. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>hashicorp</category>
      <category>testing</category>
      <category>terraform</category>
    </item>
    <item>
      <title>The issue of recursive module calls in declarative infrastructure-as-code</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Thu, 09 Nov 2023 14:15:39 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/the-issue-of-recursive-module-calls-in-declarative-infrastructure-as-code-3fk</link>
      <guid>https://dev.to/mattiasfjellstrom/the-issue-of-recursive-module-calls-in-declarative-infrastructure-as-code-3fk</guid>
      <description>&lt;p&gt;A few years ago when I was working exclusively with the AWS platform I was early to jump on the Cloud Development Kit (CDK) train. I had been using AWS CloudFormation and HashiCorp Terraform for a few years for all my infrastructure-as-code needs up until then. However, I never got comfortable using the CDK and abandoned it long before it reached 1.0.&lt;/p&gt;

&lt;p&gt;Why was the CDK making me uncomfortable? To me it just did not provide any benefits over the traditional declarative approach. In the end the CDK code looked like slightly more complex declarative code. It could be mapped one-to-one with an equivalent declarative template. There were a few things that were better than the declarative approach though, but keep in mind that I was comparing CDK primarily to CloudFormation at that time. Perhaps the best benefit with the CDK was that you could do any kind of string and array manipulation your chosen programming language offered. This was (is) a desperately needed feature in CloudFormation.&lt;/p&gt;

&lt;p&gt;Fast-forward a few years and enter Azure Bicep! Bicep provides many things that CloudFormation does not have (ignoring platform-specific things in this comparison of course). There is not much feature-wise I am missing from Azure Bicep. We can discuss what I do miss in Bicep in another post.&lt;/p&gt;

&lt;p&gt;However, I recently tried to achieve something involving recursive module calls in Bicep and quickly realized that it is not possible. The Bicep language server even warns you in your editor if you are trying to create a recursive loop of module calls. I turned to Terraform to see if HashiCorp has introduced support for recursive module calls. Initially the HashiCorp Developer AI actually told me that it should indeed be possible, so I was hopeful. It turns out that you can try to make recursive module calls, there is no immediate warning in your editor. However, once you run a &lt;code&gt;terraform init&lt;/code&gt; you realize that Terraform is trying to dig an infinitely deep hole of module reference in module reference and it eventually errors out.&lt;/p&gt;

&lt;p&gt;So what is it I am trying to do with recursive module calls?&lt;/p&gt;

&lt;h2&gt;
  
  
  A use-case for recursive module calls
&lt;/h2&gt;

&lt;p&gt;The use-case I am trying to solve is that I want to define a structure of Azure management groups and subscriptions in a simple YAML file, or something similar. I picked YAML at first, but JSON would work too as well as defining the structure in the chosen declarative language (Bicep, Terraform).&lt;/p&gt;

&lt;p&gt;For this post let's concentrate only on management groups. My idea was to define the structure like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-root&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tenant Root Group&lt;/span&gt;
&lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-contoso&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Contoso&lt;/span&gt;
    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-platform&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Platform&lt;/span&gt;
        &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-identity&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Identity&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-management&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Management&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-connectivity&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Connectivity&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-landing-zones&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Landing Zones&lt;/span&gt;
        &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-sap&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SAP&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-corp&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Corp&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-online&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Online&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-decommissioned&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Decommissioned&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mg-sandbox&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sandbox&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sample structure is fetched from the &lt;a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/landing-zone/design-area/resource-org-management-groups#management-groups-in-the-azure-landing-zone-accelerator-and-alz-bicep-repository"&gt;Azure Landing Zone documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I would then like to read this file in Bicep (or Terraform) and through clever recursive module calls create this structure of management groups.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposed solution with Azure Bicep
&lt;/h2&gt;

&lt;p&gt;To solve this with Bicep my approach was to have the following &lt;code&gt;main.bicep&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;targetScope = 'tenant'

var data = loadYamlContent('data.yaml')

resource root 'Microsoft.Management/managementGroups@2023-04-01' existing = {
  name: data.id
}

module recursive 'modules/recursive.bicep' = [for (child, index) in data.children: {
  name: 'child-module-${index}'
  params: {
    name: child.name
    children: child.children
    id: child.id
    parentId: root.id
  }
}]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recursive module file &lt;code&gt;modules/recursive.bicep&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;targetScope = 'tenant'

param parentId string
param id string
param name string
param children array = []

resource mg 'Microsoft.Management/managementGroups@2023-04-01' = {
  name: id
  properties: {
    details: {
      parent: {
        id: parentId
      }
    }
    displayName: name
  }
}

module childMgs 'recursive.bicep' = [for (child, index) in children: {
  name: 'child-module-${id}-${index}'
  params: {
    name: child.name
    children: child.children
    id: child.id
    parentId: mg.id
  }
}]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I thought it was a good idea, but Bicep did not agree. I have submitted a &lt;a href="https://github.com/Azure/bicep/issues/12211"&gt;proposal&lt;/a&gt; to the Bicep team for how this can be allowed. Vote for this issue if you agree!&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposed solution with HashiCorp Terraform
&lt;/h2&gt;

&lt;p&gt;To solve this with Terraform my approach was to have the following &lt;code&gt;main.tf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/azurerm"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsondecode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data.json"&lt;/span&gt;&lt;span class="err"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"children"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/recursive"&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;parent&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform has no built-in support to read YAML so I converted the file to JSON and read it using &lt;code&gt;jsondecode(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The recursive module file &lt;code&gt;modules/recursive/main.tf&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/azurerm"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"parent"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"children"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;id&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_management_group"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;parent_management_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"recursive"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./"&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;parent&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_management_group&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be honest I am not sure this would have worked even if recursive module calls were allowed, but at least my editor is not complaining at this point. When I run &lt;code&gt;terraform init&lt;/code&gt; however:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;terraform init
&lt;span class="go"&gt;Initializing the backend...
Initializing modules...
╷
│ Error: Failed to remove local module cache
│
│ Terraform tried to remove
│ .terraform/modules/children.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive
│ in order to reinstall this module, but encountered an error: unlinkat
│ .terraform/modules/children.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive.recursive:
│ file name too long
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform does not know I make recursive module calls, but it does its best to find the end of the recursive calls but eventually ends up complaining about the length of a filename.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solving the problem using the Cloud Development Kit for Terraform
&lt;/h2&gt;

&lt;p&gt;In the introduction I spoke about the CDK. CDK is specifically for AWS infrastructure. However, a few years ago a new tool called Cloud Development Kit for Terraform (CDKTF) arrived. CDKTF follows the same structure as the CDK. I recommend that you read through the &lt;a href="https://developer.hashicorp.com/terraform/cdktf"&gt;CDKTF documentation&lt;/a&gt; if you are interested to learn more, because I will not explain the details of CDKTF in this post.&lt;/p&gt;

&lt;p&gt;I wrote my CDKTF code using TypeScript, but there are other alternatives available.&lt;/p&gt;

&lt;p&gt;I defined the management group structure in TypeScript in &lt;code&gt;managementGroups.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ManagementGroupDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;ManagementGroupDefinition&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;managementGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ManagementGroupDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pseudo Root Group&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/providers/Microsoft.Management/managementGroups/&amp;lt;my tenant id&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contoso&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Platform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Identity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Management&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connectivity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Landing Zones&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SAP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Corp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Online&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Decommissioned&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sandbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could define the structure in YAML as before, but for simplicity I defined it as TypeScript. One thing to note is that I have not included the &lt;code&gt;id&lt;/code&gt; field in this structure. This is because Terraform does not allow me to define a custom id for my management groups. This is unfortunately not ideal, but I'll let it slide for now. Another thing to note is that I have included a &lt;code&gt;parent&lt;/code&gt; field in the root management group. The parent is my actual tenant root group, but I decided to create a pseudo root group instead of working directly in the actual root group.&lt;/p&gt;

&lt;p&gt;Next I have my CDKTF application in &lt;code&gt;main.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;constructs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TerraformStack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cdktf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AzurermProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@cdktf/provider-azurerm/lib/provider&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ManagementGroup&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@cdktf/provider-azurerm/lib/management-group&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;managementGroups&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ManagementGroupDefinition&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./managementGroups&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;TerraformStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AzurermProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;azurerm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;makeLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;managementGroups&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;makeLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ManagementGroupDefinition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ManagementGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parentManagementGroupId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;makeLayer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cdktf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;synth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic happens in the &lt;code&gt;makeLayer&lt;/code&gt; method of my &lt;code&gt;Stack&lt;/code&gt; class. This is where I create a new &lt;code&gt;ManagementGroup&lt;/code&gt; with a given &lt;code&gt;displayName&lt;/code&gt; and an optional &lt;code&gt;parentManagementGroupId&lt;/code&gt;. Next I loop over each child to this management group and once again call &lt;code&gt;makeLayer&lt;/code&gt;, and here we have the recursion!&lt;/p&gt;

&lt;p&gt;CDKTF constructs a valid Terraform template from this &lt;code&gt;main.ts&lt;/code&gt; file. No need for infinite recursive module calls because CDKTF knows the recursive loop has an end.&lt;/p&gt;

&lt;p&gt;To be honest, the resulting Terraform configuration does not actually use modules. So the result is not a "solution" to the recursion problem. What CDKTF does here is generate a single configuration with all my management groups defined, it does not introduce a module and make recursive calls to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Currently neither Azure Bicep or HashiCorp Terraform supports recursive module calls out of the box. This is one use-case where an imperative approach to infrastructure-as-code wins. I am still not convinced the imperative approach is worth it in the long run, I prefer the clarity of the declarative approach.&lt;/p&gt;

&lt;p&gt;Of course there is the middle ground, you could use an imperative approach to generate a declarative template which you can then deploy. And this is what happens under the hood with CDKTF anyway.&lt;/p&gt;

&lt;p&gt;As with everything else it depends on what you want to do. So far in my career this was the first time I tried to do something in a declarative way that was just not supported.&lt;/p&gt;

&lt;p&gt;Oh and by the way, I know one could argue for that I am trying to write imperative code using a declarative language when I do recursive module calls - but that is another discussion.&lt;/p&gt;

</description>
      <category>infrastructureascode</category>
      <category>terraform</category>
      <category>azure</category>
      <category>bicep</category>
    </item>
    <item>
      <title>HashiCorp Developer AI</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Wed, 25 Oct 2023 04:15:46 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/hashicorp-developer-ai-3m5k</link>
      <guid>https://dev.to/mattiasfjellstrom/hashicorp-developer-ai-3m5k</guid>
      <description>&lt;p&gt;During the keynote at HashiConf 2023 Armon Dadgar, HashiCorp's Co-Founder and CTO, humorously quipped:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nowadays there wouldn't be a keynote unless you mention AI at least once&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To be fair, I don't remember the quote verbatim so take it with a grain of salt. However, the message he conveyed is captured in my rendition of the quote. &lt;/p&gt;

&lt;p&gt;There were two AI-related announcements made during the keynote. The first was about automatic test generation for a module inside of Terraform Cloud. This was a part of the new test framework announcement, one that I covered in my previous post. The other announcement, which I will focus on in this post, was the &lt;strong&gt;Developer AI&lt;/strong&gt;. The Developer AI is part of &lt;a href="https://developer.hashicorp.com/"&gt;developer.hashicorp.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Developer AI introduces itself as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your AI-powered companion for finding reference materials, architectural guidance, and product examples from HashiCorp. Ask me anything to get started.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To get started using Developer AI you need to sign-up for the private beta[^beta]. Once you are in the private beta you can head on over to &lt;a href="https://developer.hashicorp.com/"&gt;developer.hashicorp.com&lt;/a&gt; or specifically to your favorite product documentation. I am a huge fan of &lt;strong&gt;Terraform&lt;/strong&gt;, so to try out Developer AI I pointed my browser at &lt;a href="https://developer.hashicorp.com/terraform"&gt;developer.hashicorp.com/terraform&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Developer AI hides in the search field in the upper right corner:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7U1_85K7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/posts/hashicorp-developer-ai/01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7U1_85K7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/posts/hashicorp-developer-ai/01.png" alt="search box 1" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To open the search interface I can click on the search box, or press &lt;code&gt;Command + K&lt;/code&gt; on my keyboard (since I am using a Mac, but you would press &lt;code&gt;ctrl + K&lt;/code&gt; on a different kind of computer). The search interface contains a lot more than the Developer AI, but that is currently of no interest to me so instead I click on the &lt;code&gt;Ask Developer&lt;/code&gt; button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--itjx6M4m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/posts/hashicorp-developer-ai/02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--itjx6M4m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/posts/hashicorp-developer-ai/02.png" alt="search box 2" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally I am ready to interact with Developer AI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XvIiXRST--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/posts/hashicorp-developer-ai/03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XvIiXRST--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/posts/hashicorp-developer-ai/03.png" alt="search box 3" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You are informed in the UI that Developer AI have the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personalized recommendations&lt;/li&gt;
&lt;li&gt;Natural language conversations&lt;/li&gt;
&lt;li&gt;Tap into HashiCorp knowledge base&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point about personalized recommendations will probably not work out-of-the-box since this is the first time I am using Developer AI. Perhaps if I use it a lot it will learn my personal preferences. As with any AI these days there is also a disclaimer in the UI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Content generated by Developer AI may contain errors, inconsistencies, or outdated information. It is provided as-is without any warranties or guarantees of accuracy. Use of this feature and related content is governed by HashiCorp’s AI Policy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As with other AIs this one might hallucinate, but unless you have lived under a rock the past year you are most likely aware of this. Always keep this in mind when communicating with an AI!&lt;/p&gt;

&lt;p&gt;What do I want to ask Developer AI? Let's see if it can suggest any good way of parsing data stored in a CSV file for further use in Terraform! This is a problem I happened to encounter once[^csv]. I enter my question in the text box and press send:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sf0tY4jq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/posts/hashicorp-developer-ai/04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sf0tY4jq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/posts/hashicorp-developer-ai/04.png" alt="asking" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Developer AI immediately generates the following reply for me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To parse data stored in CSV files for use in Terraform, you can use the &lt;code&gt;csvdecode&lt;/code&gt; function provided by Terraform. This function allows you to read and parse CSV files directly within your Terraform configuration.&lt;/p&gt;

&lt;p&gt;Here's an example of how you can use the csvdecode function:&lt;/p&gt;

&lt;p&gt;First, you need to read the CSV file using the file function and assign it to a variable:&lt;/p&gt;


&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;csv_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/data.csv"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Next, you can use the &lt;code&gt;csvdecode&lt;/code&gt; function to parse the CSV data:&lt;/p&gt;


&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;parsed_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;csvdecode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;csv_data&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Now, you can access the parsed data in your Terraform configuration. For example, if your CSV file has columns named &lt;code&gt;"name"&lt;/code&gt; and &lt;code&gt;"age"&lt;/code&gt;, you can access the values like this:&lt;/p&gt;


&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parsed_data&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parsed_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parsed_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;In this example, we assume that the CSV file has a header row with column names &lt;code&gt;"name"&lt;/code&gt; and &lt;code&gt;"age"&lt;/code&gt;. The &lt;code&gt;csvdecode&lt;/code&gt; function will parse the CSV data and create a list of objects, where each object represents a row in the CSV file. You can then access the values of each column using dot notation.&lt;/p&gt;

&lt;p&gt;Remember to adjust the file path in the file function to match the location of your CSV file.&lt;/p&gt;

&lt;p&gt;By using the &lt;code&gt;csvdecode&lt;/code&gt; function, you can easily parse CSV data and use it in your Terraform configuration. This can be useful for dynamically generating resources or configuring variables based on data stored in CSV files.&lt;/p&gt;

&lt;p&gt;For more information, you can refer to the Terraform documentation on the &lt;code&gt;csvdecode&lt;/code&gt; function: &lt;a href="https://developer.hashicorp.com/terraform/language/functions/csvdecode"&gt;Terraform csvdecode function&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This might sound fabricated to increase the legitimacy of Developer AI, but I genuinely did not know the &lt;code&gt;csvdecode&lt;/code&gt; function existed! I would call this response a great success! I also feel a little dumb because that function might have made my life easier back when I was in the situation where I needed it[^csv].&lt;/p&gt;

&lt;p&gt;Since I am a responsible private beta participant I provide feedback to the response by clicking the thumbs-up button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YfEUQ1Yt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/posts/hashicorp-developer-ai/05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YfEUQ1Yt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/posts/hashicorp-developer-ai/05.png" alt="asking" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next time I am performing complicated manipulations in Terraform, or doing anything that I don't know by heart, I will take Developer AI for another spin. This has been my first interaction and I am pleased with the results!&lt;/p&gt;


&lt;p&gt;[^csv]: If you know, you know.&lt;br&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>terraform</category>
      <category>hashicorp</category>
    </item>
    <item>
      <title>A Comprehensive Guide to Testing in Terraform: Keep your tests, validations, checks, and policies in order</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Fri, 20 Oct 2023 14:53:04 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/a-comprehensive-guide-to-testing-in-terraform-keep-your-tests-validations-checks-and-policies-in-order-543h</link>
      <guid>https://dev.to/mattiasfjellstrom/a-comprehensive-guide-to-testing-in-terraform-keep-your-tests-validations-checks-and-policies-in-order-543h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post discusses testing and validation for infrastructure-as-code (IaC) with &lt;strong&gt;HashiCorp Terraform&lt;/strong&gt;. The insights and ideas presented here can surely be extended to IaC in general.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;With the release of the new &lt;strong&gt;testing framework&lt;/strong&gt; for Terraform 1.6 presented at HashiConf 2023 in San Francisco&lt;sup id="fnref1"&gt;1&lt;/sup&gt;, there is now the possibility to test your Terraform configurations using Terraform itself.&lt;/p&gt;

&lt;p&gt;Testing IaC has always been a pain point. If you have been using IaC for any amount of time you may know that the only true test is to run &lt;code&gt;terraform apply&lt;/code&gt; and see what happens. Test frameworks for IaC come and go, some are more successful than others. My belief is that there is no mock that will ever give you enough confidence that your IaC will behave as intended&lt;sup id="fnref2"&gt;2&lt;/sup&gt;. For this reason I am glad the new test framework in Terraform does exactly that, it runs &lt;code&gt;plan&lt;/code&gt; and &lt;code&gt;apply&lt;/code&gt; commands for you, and it creates actual infrastructure.&lt;/p&gt;

&lt;p&gt;But wait, there is more! The new testing framework is &lt;em&gt;declarative&lt;/em&gt;, meaning that you write tests using the same language that you use for the rest of your infrastructure in Terraform. There is no need to install a third-party tool to keep track of, or to learn a new language.&lt;/p&gt;

&lt;p&gt;The new testing framework is not your only option when it comes to testing and validating your infrastructure with Terraform. Terraform 1.5 introduced the concept of &lt;strong&gt;checks&lt;/strong&gt; together with the new &lt;code&gt;check&lt;/code&gt; block. Checks allow you to validate your infrastructure outside of the usual resource lifecycle. If you are using Terraform Cloud you can even set up &lt;a href="https://developer.hashicorp.com/terraform/cloud-docs/workspaces/health#continuous-validation"&gt;continuous validations&lt;/a&gt; to regularly verify custom assertions. This post will not focus on Terraform Cloud, instead I will focus on what you can do with Terraform locally&lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Going even further back in time, Terraform introduced &lt;strong&gt;custom conditions&lt;/strong&gt; for validating input variables, output values, and running validation checks at given points in a resource lifecycle. This is another way to test parts of your infrastructure directly in Terraform. Custom validation of input variables allow you to check that the provided input value fulfils given constraints. These constrains could be to limit the length of a supplied string to less than ten characters, or to restrict the allowed values of a variable to a predefined list of values. Resources and data sources have the &lt;code&gt;lifecycle&lt;/code&gt; block where you can add &lt;code&gt;precondition&lt;/code&gt; and &lt;code&gt;postcondition&lt;/code&gt; blocks to validate things before, and after, running an apply, respectively. Similarly, you can add &lt;code&gt;precondition&lt;/code&gt; blocks to your &lt;code&gt;output&lt;/code&gt; blocks, to validate something about the output value.&lt;/p&gt;

&lt;p&gt;With all the above mentioned options for testing and validation you can create robust and well-tested Terraform configurations and modules.&lt;/p&gt;

&lt;p&gt;There is a related concept to testing that you should also consider using. I am talking about &lt;strong&gt;policies&lt;/strong&gt;, and &lt;strong&gt;policy-as-code&lt;/strong&gt;. It does not make sense to write tests for all the properties of every resource in your modules, because most properties have no designated wrong values that you need to test for. However, there are insecure or incompliant values for properties that your organization  might not allow. Besides, maybe you allow insecure property values in your development environments but not in your production environments. Instead of having to write separate test files or custom validations depending on what environment you are testing, you should instead use policies.&lt;/p&gt;

&lt;p&gt;In this blog post I will introduce each of the concepts discussed above through examples in Terraform. I will also discuss the important question of when you should use a given approach: a test, a check, a custom condition, or a policy.&lt;/p&gt;

&lt;p&gt;All the source code from this post is available at my GitHub repo: &lt;a href="https://github.com/mattias-fjellstrom/terraform-testing"&gt;github.com/mattias-fjellstrom/terraform-testing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Terraform for testing and validation
&lt;/h2&gt;

&lt;p&gt;This section will introduce each of the concepts discussed above, and in the following section I will go through two example Terraform configuration and include tests, checks, custom conditions, and policies, where appropriate.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI validation command
&lt;/h3&gt;

&lt;p&gt;Let's start at the beginning.&lt;/p&gt;

&lt;p&gt;When you design your test and validation strategy you can choose how deep into the rabbit hole you want to go.&lt;/p&gt;

&lt;p&gt;If you are constrained on time but would like to include basic validation of your Terraform configuration, you can use the &lt;code&gt;terraform validate&lt;/code&gt; command. This command captures trivial mistakes in your configuration, like typos in resource properties and erroneous syntax in your HashiCorp Configuration Language (HCL). &lt;/p&gt;

&lt;p&gt;During development of your Terraform configuration it is a sensible step to run &lt;code&gt;terraform validate&lt;/code&gt; often&lt;sup id="fnref4"&gt;4&lt;/sup&gt;. You might think that trivial mistakes like typos would be caught by your editor, but you would be surprised by how many mistakes can slip by. HashiCorp recently announced better support for editors like VS Code, so things are getting better in this area.&lt;/p&gt;

&lt;p&gt;A good rule of thumb is that your deployment pipeline should never fail on the &lt;code&gt;terraform validate&lt;/code&gt; command. You should catch these errors during development. However, your pipeline should definitely include the &lt;code&gt;terraform validate&lt;/code&gt; command, because we all do make mistakes and forget things!&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;terraform init&lt;/code&gt; also captures some basic mistakes in your configuration. This include things like using an invalid version number for a given provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom conditions
&lt;/h3&gt;

&lt;p&gt;There are three kinds of &lt;strong&gt;custom conditions&lt;/strong&gt; to discuss. The first custom condition is for &lt;strong&gt;variable validation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We want to catch errors as soon as possible. If our modules include &lt;code&gt;variable&lt;/code&gt; blocks, we would be naive to assume the user will only provide sensible values as input to these variables. Erroneous input values will most likely result in errors later in the plan or apply phases. The worst possible type of error is when a bad input value results in a failed apply 45 minutes into the operation.&lt;/p&gt;

&lt;p&gt;How do we mitigate this?&lt;/p&gt;

&lt;p&gt;The solution is to add &lt;code&gt;validation&lt;/code&gt; blocks nested inside of our &lt;code&gt;variable&lt;/code&gt; blocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"name_prefix"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;

    &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name_prefix&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
        &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Variable length should be 20 characters or less"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;validation&lt;/code&gt; block has no label, and it takes two arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;condition&lt;/code&gt; which is a boolean expression that evaluates to &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; to indicate if the validation succeeded or failed.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;error_message&lt;/code&gt; that is presented to the user if the validation fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let us move on to the second kind of custom condition.&lt;/p&gt;

&lt;p&gt;We can include validations that run before or after changes are applied to a resource, or after a data source is read. We do this by including an appropriate &lt;code&gt;precondition&lt;/code&gt; or &lt;code&gt;postcondition&lt;/code&gt; block nested inside of a &lt;code&gt;lifecycle&lt;/code&gt; block in our resource or data source.&lt;/p&gt;

&lt;p&gt;An example of what this looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// read an existing Azure resource group&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"rg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-storage-resource-group"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// create a new Azure storage account in the resource group&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"stg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stsamplestorage"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;access_tier&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Hot"&lt;/span&gt;
  &lt;span class="nx"&gt;account_replication_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LRS"&lt;/span&gt;
  &lt;span class="nx"&gt;account_tier&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard"&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;precondition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;managed_by&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&lt;/span&gt;
      &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Resource group must be managed by Terraform!"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to the &lt;code&gt;validation&lt;/code&gt; block for variables, both the &lt;code&gt;precondition&lt;/code&gt; and &lt;code&gt;postcondition&lt;/code&gt; blocks takes two arguments: a &lt;code&gt;condition&lt;/code&gt; and an &lt;code&gt;error_message&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the previous code snippet I declare a data source where I read an existing resource group from Azure (&lt;code&gt;azurerm_resource_group&lt;/code&gt;). I then would like to create a new storage account in the resource group, but only if the &lt;code&gt;precondition&lt;/code&gt; validation of the resource &lt;code&gt;lifecycle&lt;/code&gt; passes.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;precondition&lt;/code&gt; block I check that the &lt;code&gt;managed_by&lt;/code&gt; property of the resource group is set to the value &lt;code&gt;terraform&lt;/code&gt;. If the &lt;code&gt;managed_by&lt;/code&gt; property has any other value, the &lt;code&gt;condition&lt;/code&gt; will evaluate to false and the &lt;code&gt;precondition&lt;/code&gt; fails. The operation will halt and the &lt;code&gt;error_message&lt;/code&gt; is displayed to the user in the console.&lt;/p&gt;

&lt;p&gt;The third, and last, custom condition to discuss is for &lt;strong&gt;output validation&lt;/strong&gt;. We can add a &lt;code&gt;precondition&lt;/code&gt; block nested inside of our &lt;code&gt;output&lt;/code&gt; blocks. This allows us to verify something that the output depends on. This could be used to avoid outputting values from the &lt;code&gt;output&lt;/code&gt; block into our state file and in our logs when the value would not be useful. Personally I think this custom condition has less obvious usecases, and I have not used it for anything myself.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;precondition&lt;/code&gt; block inside of the &lt;code&gt;output&lt;/code&gt; block looks the same as for resources, it has one &lt;code&gt;condition&lt;/code&gt; argument and one &lt;code&gt;error_message&lt;/code&gt; argument.&lt;/p&gt;

&lt;p&gt;An example of what a custom condition of outputs look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"website_endpoint"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stg&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary_web_endpoint&lt;/span&gt;

  &lt;span class="nx"&gt;precondition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startswith&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stg&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary_web_endpoint&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"https://"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Website URL uses HTTP, you should instead configure HTTPS"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This custom condition checks that a website deployed to an Azure storage account exposes a website URL starting with &lt;code&gt;https://&lt;/code&gt;, and if this is not the case an error message is raised and the deployment fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test framework
&lt;/h3&gt;

&lt;p&gt;The Terraform test framework is new since Terraform 1.6. It was release a few days before HashiConf 2023 in San Francisco, and I believe it is a welcome change to Terraform.&lt;/p&gt;

&lt;p&gt;Tests are written using HCL, so you do not need to learn a new language in order to test your IaC. Test files use the &lt;code&gt;.tftest.hcl&lt;/code&gt; file ending.&lt;/p&gt;

&lt;p&gt;By default, all test files stored in the same directory as your module, or stored in a &lt;code&gt;tests&lt;/code&gt; directory, are run when you issue the new &lt;code&gt;terraform test&lt;/code&gt; command. If you place your tests in a different directory you can add the &lt;code&gt;-test-directory=path/to/my/test/directory&lt;/code&gt; flag to the command.&lt;/p&gt;

&lt;p&gt;With all the necessary details out of the way, a typical setup of a simple module with tests could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tree &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;.
&lt;/span&gt;&lt;span class="go"&gt;├── main.tf
├── variables.tf
├── outputs.tf
└── tests
    ├── testfile1.tftest.hcl
    ├── testfile2.tftest.hcl
    └── testfile3.tftest.hcl
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A test file consists of one or more &lt;code&gt;run&lt;/code&gt; blocks. Each &lt;code&gt;run&lt;/code&gt; block should be thought of as a test. A &lt;code&gt;run&lt;/code&gt; block includes a number of arguments and blocks to configure what the test should do. Each test executes a &lt;code&gt;command&lt;/code&gt;, which is either &lt;code&gt;plan&lt;/code&gt; or &lt;code&gt;apply&lt;/code&gt;. If you do not specify the &lt;code&gt;command&lt;/code&gt; argument it defaults to &lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An example of what a &lt;code&gt;run&lt;/code&gt; block looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/main.tftest.hcl&lt;/span&gt;
&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"file_contents_should_be_valid_json"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;try&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsondecode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local_file&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="err"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The file is not valid JSON"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;run&lt;/code&gt; block label should use a descriptive name to explain what the test is about. In this case I have set the label to &lt;code&gt;file_contents_should_be_valid_json&lt;/code&gt; to indicate that this tests validates that the content of a file that is produced by Terraform contains valid JSON content. The test runs a &lt;code&gt;plan&lt;/code&gt; as indicated by the &lt;code&gt;command&lt;/code&gt; argument. The meat of the test is in the &lt;code&gt;assert&lt;/code&gt; statement. The &lt;code&gt;condition&lt;/code&gt; argument evaluates to &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;, which results in the test passing or failing, respectively.&lt;/p&gt;

&lt;p&gt;Note that you can add additional &lt;code&gt;assert&lt;/code&gt; blocks to make several assertions in the same test. It is also possible to not use any &lt;code&gt;assert&lt;/code&gt; block, but instead use the &lt;code&gt;expect_failures&lt;/code&gt; argument where you can indicate if you expect the &lt;code&gt;plan&lt;/code&gt; or &lt;code&gt;apply&lt;/code&gt; to fail, and if so what will make it fail. Further down in this post I will show examples of what this looks like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checks
&lt;/h3&gt;

&lt;p&gt;The concept of &lt;strong&gt;checks&lt;/strong&gt; together with the &lt;code&gt;check&lt;/code&gt; block was introduced in Terraform 1.5 and serve a purpose similar to custom validations for resources and data sources, but with one important difference. The &lt;code&gt;check&lt;/code&gt; block is ideal for validations you want to make that should not stop a plan and apply from finishing. If a check fails the deployment will still continue. You will get a warning in the output indicating that the check failed.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;check&lt;/code&gt; block can include at most one scoped &lt;code&gt;data&lt;/code&gt; block, and one or more &lt;code&gt;assert&lt;/code&gt; blocks. Each &lt;code&gt;assert&lt;/code&gt; block has a &lt;code&gt;condition&lt;/code&gt; that evaluates to &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;, and an &lt;code&gt;error_message&lt;/code&gt; that is displayed if the assertion fails. The purpose of the scoped &lt;code&gt;data&lt;/code&gt; block is to read data about a resource, or perhaps an external website, that you want to include in the check.&lt;/p&gt;

&lt;p&gt;Imagine that our Terraform configuration sets up a publicly available website (among other things), and we would like to check that the website responds to requests after it is set up. This is an ideal situation for a &lt;code&gt;check&lt;/code&gt; block. An example of what this might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.tf&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"website"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/web"&lt;/span&gt;

  &lt;span class="c1"&gt;// ... the rest of the arguments are left out for brevity&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="s2"&gt;"validate_website_response"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt; &lt;span class="s2"&gt;"static_website"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web_endpoint&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;static_website&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status_code&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The website returned an unhealthy status code"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main differences between &lt;code&gt;check&lt;/code&gt; blocks and custom conditions for resources and data sources is that the latter is tied to the lifecycle (hence they are part of the &lt;code&gt;lifecycle&lt;/code&gt; block) of the resource or data source, while &lt;code&gt;check&lt;/code&gt; blocks are not. Also, &lt;code&gt;check&lt;/code&gt; blocks do not fail an apply if the &lt;code&gt;assert&lt;/code&gt; evaluates to false, while custom conditions for resources and data sources do.&lt;/p&gt;

&lt;p&gt;This makes &lt;code&gt;check&lt;/code&gt; blocks useful for continuous validation in Terraform Cloud. Continuous validation specifically runs the &lt;code&gt;check&lt;/code&gt; blocks in your configuration on a schedule. This could be useful for basic health checks for your infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Policies
&lt;/h3&gt;

&lt;p&gt;Policies are closely related to tests, but they serve a different purpose.&lt;/p&gt;

&lt;p&gt;There are two popular frameworks for &lt;strong&gt;policy-as-code&lt;/strong&gt; that works well with Terraform, and they are both available in Terraform Cloud.&lt;/p&gt;

&lt;p&gt;The first framework is the &lt;strong&gt;Open Policy Agent (OPA)&lt;/strong&gt; together with its &lt;strong&gt;Rego&lt;/strong&gt; language for policies, it has risen in popularity in recent years and is used for many types of policies. The second framework is &lt;strong&gt;HashiCorp Sentinel&lt;/strong&gt;, which uses HCL. In this post I will use Sentinel, since it is most closely related to HashiCorp and Terraform and in my opinion it is also a little bit easier to learn.&lt;/p&gt;

&lt;p&gt;The purpose of policies with Terraform is to validate that the changes you are introducing follow rules defined by you or your organization. These rules are most likely related to compliance or security.&lt;/p&gt;

&lt;p&gt;An example of a Sentinel policy document is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;"tfplan/v2"&lt;/span&gt; &lt;span class="nx"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;tfplan&lt;/span&gt;

&lt;span class="c1"&gt;// filter out all storage containers&lt;/span&gt;
&lt;span class="nx"&gt;containers&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="nx"&gt;tfplan&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_changes&lt;/span&gt; &lt;span class="nx"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_container"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// filter out containers that are created or updated&lt;/span&gt;
&lt;span class="nx"&gt;createdOrUpdated&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="nx"&gt;containers&lt;/span&gt; &lt;span class="nx"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"create"&lt;/span&gt; &lt;span class="nx"&gt;or&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// make sure each container has an access type set to private&lt;/span&gt;
&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;createdOrUpdated&lt;/span&gt; &lt;span class="nx"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;containers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;containers&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container_access_type&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time you see a Sentinel policy you might get puzzled at what it does. This post is not about explaining the syntax of Sentinel, the comments in the code snippet should be explanation enough. In essence, Sentinel takes the JSON-representation of a Terraform plan file from a &lt;code&gt;terraform plan&lt;/code&gt; command, and searches for something specific in the plan, and finally evaluates the &lt;code&gt;rule&lt;/code&gt; named &lt;code&gt;main&lt;/code&gt; that is either &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;. If the rule evaluates to &lt;code&gt;true&lt;/code&gt; the change is allowed by the policy.&lt;/p&gt;

&lt;p&gt;In the policy example above I first apply a &lt;code&gt;filter&lt;/code&gt; to find all the resources of type &lt;code&gt;azurerm_storage_container&lt;/code&gt;, then I have another &lt;code&gt;filter&lt;/code&gt; to find all the containers where the planned actions are &lt;code&gt;create&lt;/code&gt; or &lt;code&gt;update&lt;/code&gt;. Finally, I have my &lt;code&gt;rule&lt;/code&gt; named &lt;code&gt;main&lt;/code&gt; that checks that the &lt;code&gt;container_access_type&lt;/code&gt; property is set to &lt;code&gt;private&lt;/code&gt; for the containers that I have filtered out. If one or more containers has set &lt;code&gt;container_access_type&lt;/code&gt; to something other than &lt;code&gt;private&lt;/code&gt;, then this policy evaluation will fail, and I should not allow the change to be deployed.&lt;/p&gt;

&lt;p&gt;The trick to learn how to write policies is to make sure you have a plan file in JSON format available, then you know what to look for and what all the fields are named. It also helps to start with an existing example and modify it to your needs.&lt;/p&gt;

&lt;p&gt;You should run all your policies against each planned change to make sure you are not introducing anything that violates your compliance and security rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying testing and validation in the real world
&lt;/h2&gt;

&lt;p&gt;In this section I want to go through two small sample Terraform configurations, and use the different options from above to test and validate the code. In the first sample I take on the role as  a module developer, crafting a module for other developers to use. In the second sample I imagine I am part of a standalone team building infrastructure for our own applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample 1: Module producer
&lt;/h3&gt;

&lt;p&gt;In this sample we are producers of a simple module that other teams in our organization will use in their own infrastructure. Many teams depend on our module, so we must make sure we test it thoroughly. The sample configuration might be a bit contrived, but bear with me!&lt;/p&gt;

&lt;p&gt;The module we have produced takes three inputs, it creates a local file containing some JSON data, and it outputs the file content in base64 encoded format. This configuration file is part of a larger infrastructure where the file will be used to configure a separate system (not included in this sample). The system has constraints about the configuration file that must be respected. These constraints might be that the JSON file should be valid, it should include certain fields, and it must be at most a given size.&lt;/p&gt;

&lt;p&gt;Our module has the following directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tree &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;.
&lt;/span&gt;&lt;span class="go"&gt;├── main.tf
├── outputs.tf
├── tests
│   └── main.tftest.hcl
└── variables.tf

1 directories, 4 files
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first file we will look at is &lt;code&gt;variables.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// variables.tf&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"config_url"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startswith&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config_url&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"https://"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"config_url should start with https://"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"port"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1023&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;65535&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Use an unprivileged port number (1024-65535)"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"service_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three variables defined: &lt;code&gt;config_url&lt;/code&gt;, &lt;code&gt;port&lt;/code&gt;, and &lt;code&gt;service_name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Exactly what these variables mean in a larger context is out of scope but we imagine they are important! A brief description of what we do know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;config_url&lt;/code&gt; should be a valid URL. Currently the &lt;code&gt;condition&lt;/code&gt; in the &lt;code&gt;validation&lt;/code&gt; block makes sure that the URL starts with &lt;code&gt;https://&lt;/code&gt;. We could change this &lt;code&gt;condition&lt;/code&gt; to compare the provided value with a regex describing a valid URL if we want to. For simplicity I have left the validation as it is.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;port&lt;/code&gt; should be a valid port number. The &lt;code&gt;condition&lt;/code&gt; in the &lt;code&gt;validation&lt;/code&gt; block checks that the user has provided un unprivileged port in the range 1024-65535.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;service_name&lt;/code&gt; is the name of a service of some kind. Currently there is no validation of this variable, this means we have no known restrictions of the value the user can supply. This is a common situation in the real world, where we might not always know what to allow or not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next we take a look at &lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.tf&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;local&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/local"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2.4.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"local_file"&lt;/span&gt; &lt;span class="s2"&gt;"config"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"config.json"&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
{
    "endpoint": "${var.config_url}",
    "port": "${var.port}",
    "service": "${var.service_name}"
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple Terraform configuration specifies one required provider, the &lt;code&gt;local&lt;/code&gt; provider. It creates one resource of type &lt;code&gt;local_file&lt;/code&gt;. This resource is the configuration file that we are creating. The &lt;code&gt;content&lt;/code&gt; argument of the file is constructed as JSON, using the three supplied variables.&lt;/p&gt;

&lt;p&gt;Next we look at &lt;code&gt;outputs.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// outputs.tf&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"file_content_base64"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64encode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local_file&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;precondition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64encode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local_file&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="err"&gt;))&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Config file is too large."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single &lt;code&gt;output&lt;/code&gt; block is defined. The &lt;code&gt;value&lt;/code&gt; argument is the base64-encoded value of the file content. There is a specific restriction on the length of this base64-encoded value, it can be at most 149 characters long. Don't ask me why, only the &lt;em&gt;system&lt;/em&gt; knows. We use a &lt;code&gt;precondition&lt;/code&gt; block to verify that the size restriction is fulfilled.&lt;/p&gt;

&lt;p&gt;Could we have validated our variables in some way to avoid the &lt;code&gt;precondition&lt;/code&gt; block for the output value? The problem is that the size of the JSON file depends on all three variables, and currently the &lt;code&gt;service_name&lt;/code&gt; variable has no restrictions on its own. The &lt;code&gt;config_url&lt;/code&gt; variable could also be almost any length. In this case it makes sense to check the size of the generated JSON as a &lt;code&gt;precondition&lt;/code&gt; in the output instead.&lt;/p&gt;

&lt;p&gt;When it comes to testing this module we should explicitly test the interface we are exposing. What is included in the interface of this module? It is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The three variables &lt;code&gt;config_url&lt;/code&gt;, &lt;code&gt;port&lt;/code&gt;, and &lt;code&gt;service_name&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The generated JSON-file &lt;code&gt;config.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The output &lt;code&gt;file_content_base64&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the things that a user of our module will see and interact with.&lt;/p&gt;

&lt;p&gt;Below I will discuss one test at a time, all the tests I show are part of the file named &lt;code&gt;tests/main.tftest.hcl&lt;/code&gt;. The first test I write is for the &lt;code&gt;config_url&lt;/code&gt; variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/main.tftest.hcl&lt;/span&gt;
&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;config_url&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://example.com"&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1414&lt;/span&gt;
  &lt;span class="nx"&gt;service_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-service"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"bad_input_url_should_fail"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;config_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.com"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;expect_failures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config_url&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;variables&lt;/code&gt; block defined in the root of the file provides default values for all the variables. These can be overridden in each test if desired. For this test I override the &lt;code&gt;config_url&lt;/code&gt; variable inside of the &lt;code&gt;run&lt;/code&gt; block with a URL starting with &lt;code&gt;http://&lt;/code&gt;, which I expect should not be allowed. For this particular test I am not using an &lt;code&gt;assert&lt;/code&gt; block to assert something about the test outcome, instead I use the &lt;code&gt;expect_failures&lt;/code&gt; argument to tell this test that I expect it to fail, and I expect that the variable named &lt;code&gt;config_url&lt;/code&gt; will be the reason for the failure.&lt;/p&gt;

&lt;p&gt;The next test is a similar test but for the variable named &lt;code&gt;port&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ... continuation of tests/main.tftest.hcl&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"bad_input_port_should_fail"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;expect_failures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test overrides the &lt;code&gt;port&lt;/code&gt; variable with a value of &lt;code&gt;80&lt;/code&gt;. This is a privileged port, so I expect it will not be allowed. Once again I use the &lt;code&gt;expect_failures&lt;/code&gt; argument to tell the test I expect it to fail and that the variable named &lt;code&gt;port&lt;/code&gt; will be the reason for the failure. Note that to be thorough I should add a test that makes sure I can't provide a port number larger than 65535 but I will skip it for brevity.&lt;/p&gt;

&lt;p&gt;As discussed above there is no wrong input value for the last variable named &lt;code&gt;service_name&lt;/code&gt;. So there is no good test we can write for it. However, in the next test we will use the &lt;code&gt;service_name&lt;/code&gt; variable in order to make the size of the JSON content too big. This is a test for our output validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ... continuation of tests/main.tftest.hcl&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"too_large_output_file_should_fail_deployment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;service_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"this-service-name-creates-a-too-big-file"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;expect_failures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file_content_base64&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test overrides the &lt;code&gt;service_name&lt;/code&gt; variable with a long name, and for the third time in a row I use the &lt;code&gt;expect_failures&lt;/code&gt; argument to tell the test I expect it to fail on the output named &lt;code&gt;file_content_base64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The last test I have added for this module concerns the validity of the content of the JSON file. When you construct a JSON file inline of your Terraform configuration like this it is easy to make mistakes. A trailing comma or a missing colon would create a broken JSON file. A good test for this is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ... continuation of tests/main.tftest.hcl&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"file_contents_should_be_valid_json"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;try&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsondecode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local_file&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="err"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The file is not valid JSON"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test uses the &lt;code&gt;assert&lt;/code&gt; block. The &lt;code&gt;condition&lt;/code&gt; argument uses the &lt;code&gt;try(...)&lt;/code&gt; function. This function will select the first valid value from its list of arguments. The first argument in this function is &lt;code&gt;jsondecode(local_file.config.content)&lt;/code&gt;, which encodes the string content of the file as JSON. If this encoding fails, the &lt;code&gt;try(...)&lt;/code&gt; function will instead select its second argument: &lt;code&gt;null&lt;/code&gt;. The &lt;code&gt;condition&lt;/code&gt; argument checks that the result of &lt;code&gt;try(...)&lt;/code&gt; is not equal to &lt;code&gt;null&lt;/code&gt;. If it is, the assertion fails and so does the test.&lt;/p&gt;

&lt;p&gt;To run the tests we issue the new &lt;code&gt;terraform test&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;terraform &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="go"&gt;tests/main.tftest.hcl... in progress
  run "bad_input_url_should_fail"... pass
  run "bad_input_port_should_fail"... pass
  run "too_large_output_file_should_fail_deployment"... pass
  run "file_contents_should_be_valid_json"... pass
tests/main.tftest.hcl... tearing down
tests/main.tftest.hcl... pass

Success! 4 passed, 0 failed.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see what a failing test looks like, let's change the &lt;code&gt;port&lt;/code&gt; number in the test named &lt;code&gt;bad_input_port_should_fail&lt;/code&gt; to a valid number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ... temporary change in tests/main.tftest.hcl to trigger a failing test&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"bad_input_port_should_fail"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;expect_failures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rerunning the tests produces the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;terraform &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="go"&gt;tests/main.tftest.hcl... in progress
  run "bad_input_url_should_fail"... pass
  run "bad_input_port_should_fail"... fail
╷
│ Error: Missing expected failure
│
│   on tests/main.tftest.hcl line 27, in run "bad_input_port_should_fail":
│   27:     var.port
│
│ The checkable object, var.port, was expected to report an error but did not.
╵
  run "too_large_output_file_should_fail_deployment"... skip
  run "file_contents_should_be_valid_json"... skip
tests/main.tftest.hcl... tearing down
tests/main.tftest.hcl... fail

Failure! 1 passed, 1 failed, 2 skipped.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There we have it. Four simple tests to validate that the interface of our module behaves as we intend it to do. During continued development of this module we would add additional tests when it makes sense.&lt;/p&gt;

&lt;p&gt;This module included tests and validations, but no policies. Does it make sense to talk about policies during module development? Not really. As a module developer you are not sure in what context your module will be used, so it is unlikely the policies you add would make any sense to the user of your module.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample 2: Module consumer
&lt;/h3&gt;

&lt;p&gt;In this sample we put ourselves in the shoes of a module consumer. In reality we might be both a module consumer and module producer at the same time, but in this case we imagine we are writing a Terraform configuration for our own application, and we are using a module provided for us by a platform team. To have a complete example to show I will include the web module as a local module, but note that this module could be hosted in an internal Terraform registry for our organization.&lt;/p&gt;

&lt;p&gt;The file structure of this example configuration looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tree &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;.
&lt;/span&gt;&lt;span class="go"&gt;├── index.html
├── main.tf
├── modules
│   └── web
│       ├── main.tf
│       ├── outputs.tf
│       └── variables.tf
└── policies
    ├── policy.sentinel
    └── sentinel.hcl

4 directories, 9 files
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will not go into the details of the web module itself, the source code is available at the GitHub repo for this post. Our main concern is the &lt;code&gt;main.tf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.tf&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/azurerm"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3.76.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"website"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/web"&lt;/span&gt;
  &lt;span class="nx"&gt;providers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;location&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"swedencentral"&lt;/span&gt;
  &lt;span class="nx"&gt;name_prefix&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web531"&lt;/span&gt;
  &lt;span class="nx"&gt;index_file_content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="s2"&gt;"validate_website_response"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt; &lt;span class="s2"&gt;"static_website"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web_endpoint&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;static_website&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status_code&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${data.http.static_website.url} returned an unhealthy status code"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;static_website&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response_headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"text/html"&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${data.http.static_website.url} returned a wrong content-type header"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;strcontains&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;static_website&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response_body&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Hello World"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${data.http.static_website.url} did not respond with expected content"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration lists &lt;code&gt;azurerm&lt;/code&gt; as a required provider. This provider is configured according to our teams specification, and then provided to the module. The &lt;code&gt;module&lt;/code&gt; block takes three variables: &lt;code&gt;location&lt;/code&gt;, &lt;code&gt;name_prefix&lt;/code&gt;, and &lt;code&gt;index_file_content&lt;/code&gt;. The website content is read from a local file &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Apart from the module there is one large &lt;code&gt;check&lt;/code&gt; block with the label &lt;code&gt;validate_website_response&lt;/code&gt;. Our team has included this &lt;code&gt;check&lt;/code&gt; block in order to verify a few basic things about the website we are creating. In the &lt;code&gt;check&lt;/code&gt; block there is one nested &lt;code&gt;data&lt;/code&gt; block that uses the &lt;code&gt;http&lt;/code&gt; provider in order to send a GET request to our websites endpoint. The endpoint is read from the module output named &lt;code&gt;web_endpoint&lt;/code&gt;. Following the data source there are three &lt;code&gt;assert&lt;/code&gt; blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first &lt;code&gt;assert&lt;/code&gt; block checks that the website responds with a 200 OK response code. If it does not it indicates that the website is not functioning correctly.&lt;/li&gt;
&lt;li&gt;The second &lt;code&gt;assert&lt;/code&gt; block checks that the content-type of the returned page is &lt;code&gt;text/html&lt;/code&gt;. If it is not then the website will not be displayed correctly in a browser.&lt;/li&gt;
&lt;li&gt;The third &lt;code&gt;assert&lt;/code&gt; block checks that the returned content contains the string &lt;code&gt;Hello World&lt;/code&gt;. If it foes not then the right content has not been uploaded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Appropriate &lt;code&gt;error_message&lt;/code&gt; arguments are included for each &lt;code&gt;assert&lt;/code&gt; block.&lt;/p&gt;

&lt;p&gt;Notice that our directory structure does not include any tests. This is because tests are primarily for module producers, and in this case we are consuming a module. We hope that the producers of the module we are using have included tests though!&lt;/p&gt;

&lt;p&gt;Also notice that our team have decided to use a &lt;code&gt;check&lt;/code&gt; block instead of &lt;code&gt;precondition&lt;/code&gt; or &lt;code&gt;postcondition&lt;/code&gt; blocks. This is because our team would like to have the possibility to set up continuous checks via Terraform Cloud. The team also decided that if these simple checks fail we don't want to halt the &lt;code&gt;terraform apply&lt;/code&gt; command, because in this case the website is not business critical. Remember that &lt;code&gt;precondition&lt;/code&gt; and &lt;code&gt;postcondition&lt;/code&gt; validations will halt the &lt;code&gt;apply&lt;/code&gt; if they fail, but &lt;code&gt;check&lt;/code&gt; blocks will only report a warning.&lt;/p&gt;

&lt;p&gt;Looking back at the directory structure for this example we notice that we have included a &lt;code&gt;policies&lt;/code&gt; directory. Currently there is a single policy document in this directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// policy.sentinel&lt;/span&gt;
&lt;span class="nx"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;"tfplan/v2"&lt;/span&gt; &lt;span class="nx"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;tfplan&lt;/span&gt;

&lt;span class="c1"&gt;// filter out storage accounts that are created or updated&lt;/span&gt;
&lt;span class="nx"&gt;storageAccounts&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="nx"&gt;tfplan&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_changes&lt;/span&gt; &lt;span class="nx"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resourceChange&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;resourceChange&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt;
        &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resourceChange&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"create"&lt;/span&gt; &lt;span class="nx"&gt;or&lt;/span&gt;
            &lt;span class="nx"&gt;resourceChange&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"update"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;storageAccounts&lt;/span&gt; &lt;span class="nx"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;storageAccount&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;storageAccount&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_network_access_enabled&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This policy filters out all Azure storage accounts that are about to be created or updated, it then checks that the &lt;code&gt;public_network_access_enabled&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt;. If it is not, the policy will fail. This policy should be run as part of Terraform Cloud or whatever deployment pipeline the team will be using. &lt;/p&gt;

&lt;p&gt;To run the policy locally we have to perform the following steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;terraform show &lt;span class="nt"&gt;-json&lt;/span&gt; tfplan | jq &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; policies/plan.json
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;policies
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sentinel apply policy.sentinel
&lt;span class="go"&gt;Pass - policy.sentinel
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where to learn more
&lt;/h2&gt;

&lt;p&gt;If you want to learn more about the topics presented in this post I suggest you head over to the official documentation. For your convenience I have gathered the relevant links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/checks"&gt;Terraform checks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/tests"&gt;Terraform tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.hashicorp.com/sentinel"&gt;Sentinel language and framework&lt;/a&gt; for policy-as-code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/expressions/custom-conditions"&gt;Custom conditions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We have seen that there are many options for testing and validating Terraform configuration, so that we can build robust modules and configurations that will stand the test of time.&lt;/p&gt;

&lt;p&gt;The new testing framework is intended for module producers. Add tests to verify the behavior of your module interface: the variables, outputs, and any other externally visible behavior such as files.&lt;/p&gt;

&lt;p&gt;For module consumers and general Terraform configurations there are plenty of validation options such as variable validation, output validation, lifecycle conditions in resources and data sources, as well as &lt;code&gt;check&lt;/code&gt; blocks. &lt;/p&gt;

&lt;p&gt;Let us summarize the different available options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We run &lt;code&gt;terraform init&lt;/code&gt; and &lt;code&gt;terraform validate&lt;/code&gt; to check that the HCL we are writing follows proper syntax.&lt;/li&gt;
&lt;li&gt;We use tests with &lt;code&gt;run&lt;/code&gt; blocks to verify that our contract to our users, our module interfaces, work as intended.&lt;/li&gt;
&lt;li&gt;We use variable &lt;code&gt;validation&lt;/code&gt; blocks to verify that the input provided by users of our modules and configurations adhere to allowed values.&lt;/li&gt;
&lt;li&gt;We use &lt;code&gt;precondition&lt;/code&gt; and &lt;code&gt;postcondition&lt;/code&gt; blocks in our resources and data sources to verify conditions before or after applying changes.&lt;/li&gt;
&lt;li&gt;We use &lt;code&gt;check&lt;/code&gt; blocks to verify external behavior outside of resource lifecycles, and could even set up continuous validations in Terraform Cloud.&lt;/li&gt;
&lt;li&gt;We use policies with Sentinel or OPA to make sure the changes we are applying follow our organizations compliance and security requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It will be interesting to see how the new test framework for Terraform evolves over time, and to see what tests people will be writing for their modules.&lt;/p&gt;

&lt;p&gt;I hope this blog post has helped you starting to think about how to use tests and validations, as well as introducing policy-as-code, for your Terraform configurations!&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Actually, it was announced a few days before the conference. Why wait when you have something good to announce? ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;I challenge you all to tell me how a mock, or any other means of not running a real apply (or whatever term your IaC tool use) gives you confidence that your IaC will work as intended. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;But I will mention Terraform Cloud a few times! ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;While you are at it, also run &lt;code&gt;terraform fmt&lt;/code&gt; often. If you, like me, are using format-on-save in VS Code, then &lt;code&gt;terraform fmt&lt;/code&gt; is superfluous. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>hashicorp</category>
      <category>terraform</category>
      <category>testing</category>
      <category>policy</category>
    </item>
    <item>
      <title>Vault Associate Certification (Part 10): Explain encryption as a service</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Fri, 15 Sep 2023 16:13:55 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-10-explain-encryption-as-a-service-4dk4</link>
      <guid>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-10-explain-encryption-as-a-service-4dk4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is the last part of this series, thank you for reading!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We've already seen the &lt;strong&gt;transit secrets engine&lt;/strong&gt; in one of the previous posts in this series, but we will revisit the topic in this post.&lt;/p&gt;

&lt;p&gt;The transit secrets engine handles encryption on data in-transit. No data handled by this secrets engine is persisted to storage in Vault. Apart from encrypting and decrypting data the transit engine can also sign and verify data, generate random bytes, and more.&lt;/p&gt;

&lt;p&gt;There are several key types (or encryption algorithms) supported by the transit secrets engine: &lt;code&gt;aes128-gcm96&lt;/code&gt;, &lt;code&gt;aes256-gcm96&lt;/code&gt;, &lt;code&gt;chacha20-poly1305&lt;/code&gt;, &lt;code&gt;ed25519&lt;/code&gt;, &lt;code&gt;ecdsa-p256&lt;/code&gt;, &lt;code&gt;ecdsa-p384&lt;/code&gt;, &lt;code&gt;ecdsa-p521&lt;/code&gt;, &lt;code&gt;rsa-2048&lt;/code&gt;, &lt;code&gt;rsa-3072&lt;/code&gt;, &lt;code&gt;rsa-4096&lt;/code&gt;, and &lt;code&gt;hmac&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explaining encryption as a service&lt;/strong&gt; make up the tenth and last objective in the Vault certification journey. This objective covers the following sub-objectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure transit secret engine&lt;/li&gt;
&lt;li&gt;Encrypt and decrypt secrets&lt;/li&gt;
&lt;li&gt;Rotate the encryption key&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configure transit secret engine
&lt;/h2&gt;

&lt;p&gt;To start off we should enable the transit secrets engine on our Vault server. We've seen how to enable secrets engines before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets &lt;span class="nb"&gt;enable &lt;/span&gt;transit
&lt;span class="go"&gt;
Success! Enabled the transit secrets engine at: transit/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main use-case for the transit secrets engine is to encrypt and decrypt data, thus we will create a key to do just that. To create a key we run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write &lt;span class="nt"&gt;-f&lt;/span&gt; transit/keys/demo-key &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;    type=rsa-4096 \
    auto_rotate_period=1h

Key                       Value
---                       -----
allow_plaintext_backup    false
auto_rotate_period        1h
deletion_allowed          false
derived                   false
exportable                false
imported_key              false
keys                      map[1:map[creation_time:2023-09-15T17:47:46.020802+02:00 name:rsa-4096 public_key:-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqF4TxwdIpmVcXTri+Y1a
MGL7fM2xUeGDpDMfxnR1gJwjhda2zFud07TZ9RwmeFgxnbj9VwYRu9ThL1tAZ+32
72nZjizb2rJWp0UUYSWk5BcgUCa1NPM7gTcrgp9INXidHeTnC5TxUC4N2alUoPsG
r5opxHEVrBWQZxRx6AuOnP6YzmQSJMS4pv+0cH+dP0HUj1X2Kso3OuFNFSplsSPm
I9bZxOazmPNz9x4MYRfp3a5EOp6FmE/EKd2kOZ6EYaD0R+oNsI1lg79lpsDckIHY
ISFZd9sM5xVZTrXhBng2MMfpWwjSqoaJVeycQ91RYr3KzkmUW5UrXN3ksq/ncLBM
SXOcfhFgkLAXUbsEaMQE08ug4J+hEJj5Jh9By9DlNS2UMASySAwuUTJ1c78SE3fs
1YGaxZkt88mEuLOY0wUD0/0gEMshn/Lh3pjJvlYxMtwsdtVMx/zUsdaa6ialAFFl
6aKMCnvaQHtwS3O3oPma6c/qX714AKGmceZswxp8IhErafG0Wca7/JHTrUQNasWZ
HzDFqS2S5F1bu7oKICl+JELnpHu570MjtYXAomw3EHqNvPvw72ihdZ5tBX/QH1l1
iwIv+a76bfM3F5myoPP5uzNk6fPt73u0zLCKYfVK4fvr/V8CtCML6foUFtXGn9OQ
0vS2bGk64K2fTNfcSbC+P6kCAwEAAQ==
-----END PUBLIC KEY-----
]]
latest_version            1
min_available_version     0
min_decryption_version    1
min_encryption_version    0
name                      demo-key
supports_decryption       true
supports_derivation       false
supports_encryption       true
supports_signing          true
type                      rsa-4096
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the previous command I specified that the key type should be &lt;code&gt;rsa-4096&lt;/code&gt; and I want the key to automatically rotate every hour. To see all the available configuration options when creating a key see the &lt;a href="https://developer.hashicorp.com/vault/api-docs/secret/transit#create-key"&gt;API documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encrypt and decrypt secrets
&lt;/h2&gt;

&lt;p&gt;In this section I continue where I ended the last section. The status is that we have a key named &lt;code&gt;demo-key&lt;/code&gt;. We could read information about our key using &lt;code&gt;vault read transit/keys/demo-key&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Now I want to encrypt some data using my new key. Remember from earlier posts that the data I want to encrypt must be base64-encoded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write transit/encrypt/demo-key &lt;span class="nv"&gt;plaintext&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"s3cr3tm3ss4g3"&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;
Key            Value
---            -----
ciphertext     vault:v1:N+bgVOhRl5xrwcZ7Aovlg2CF9I+sB9eHt8Yspxw0CrGnR/llTp+9KPZjrA8ou2pznNZ20RdVI291TB2e3jKNLVR36pPQ8iEwfm4HNv+TVS+KJl0CvCweBGa84RrDeIUauwSgHx4vWV2SSz6qJSV8qBrwXKRSq6GPWS4bAu6U9+H2Yq6Zpvr3/mpSX1oZz4HL1PP/zds0taYNWaRJBUr9TDbgRQVto57Swy408+x1Fn9tq670go/ghYbXBV5idL+8O5IndKVB5xukwXNVvrXI4fO/lMBBkSfnGsqWUiACqCzYRPABEN2MHI6Zgfjdfi3CEr2XNCMf/dqEyK01WC9S0ERJM064dT+5oCUQzsll8e0dOGSkbFrKfTGWZExMBHrSHDrGREUEf1h/fT4gNf0udg9V5SUt+dniVkKWXQt6JqUhwIMzEglkJmsmrPDOQxXt9nziiO3mUvI1f2d99nYDicdFgCBULP9jQ7EpLM8V0NC1F/J+Rqk663ZHHZrHotfK9hDk54wfWzTfqKj+XYtqmr5SneRePOyCCtB1bOSkulgrPJQpkvrWowMCGz5eiaTrbGZjPORnkYdX8nEqv3kGIrDR6ZiP+HANzU9/BQBalr05A+UWY8XMa/4P9nPCH7cnah/vZxDicvkEF8ROxoslVfZcPPhQwxSCeRwRdX7kDK4=
key_version    1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get my data back again I can do so using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write transit/decrypt/demo-key &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;    ciphertext=vault:v1:N+bgVOhRl5xrwcZ7Aovlg2CF9I+sB9eHt8Yspxw0CrGnR/llTp+9KPZjrA8ou2pznNZ20RdVI291TB2e3jKNLVR36pPQ8iEwfm4HNv+TVS+KJl0CvCweBGa84RrDeIUauwSgHx4vWV2SSz6qJSV8qBrwXKRSq6GPWS4bAu6U9+H2Yq6Zpvr3/mpSX1oZz4HL1PP/zds0taYNWaRJBUr9TDbgRQVto57Swy408+x1Fn9tq670go/ghYbXBV5idL+8O5IndKVB5xukwXNVvrXI4fO/lMBBkSfnGsqWUiACqCzYRPABEN2MHI6Zgfjdfi3CEr2XNCMf/dqEyK01WC9S0ERJM064dT+5oCUQzsll8e0dOGSkbFrKfTGWZExMBHrSHDrGREUEf1h/fT4gNf0udg9V5SUt+dniVkKWXQt6JqUhwIMzEglkJmsmrPDOQxXt9nziiO3mUvI1f2d99nYDicdFgCBULP9jQ7EpLM8V0NC1F/J+Rqk663ZHHZrHotfK9hDk54wfWzTfqKj+XYtqmr5SneRePOyCCtB1bOSkulgrPJQpkvrWowMCGz5eiaTrbGZjPORnkYdX8nEqv3kGIrDR6ZiP+HANzU9/BQBalr05A+UWY8XMa/4P9nPCH7cnah/vZxDicvkEF8ROxoslVfZcPPhQwxSCeRwRdX7kDK4=

Key          Value
---          -----
plaintext    czNjcjN0bTNzczRnMwo=
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I get the base64-encoded value back, which I can decode to my secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"czNjcjN0bTNzczRnMwo="&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;span class="go"&gt;
s3cr3tm3ss4g3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rotate the encryption key
&lt;/h2&gt;

&lt;p&gt;When we encrypted data in the previous section the resulting ciphertext was prepended with &lt;code&gt;vault:v1&lt;/code&gt;. This tells us that the data has been encrypted by the transit secrets engine in Vault and the key version uses was &lt;code&gt;v1&lt;/code&gt;. What happens if we rotate our key and get a new version of it?&lt;/p&gt;

&lt;p&gt;Let's do just that. I can rotate my key using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write &lt;span class="nt"&gt;-f&lt;/span&gt; transit/keys/demo-key/rotate
&lt;span class="go"&gt;
Key                       Value
---                       -----
allow_plaintext_backup    false
auto_rotate_period        1h
deletion_allowed          false
derived                   false
exportable                false
imported_key              false
keys                      map[1:map[creation_time:2023-09-15T17:47:46.020802+02:00 name:rsa-4096 public_key:-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqF4TxwdIpmVcXTri+Y1a
MGL7fM2xUeGDpDMfxnR1gJwjhda2zFud07TZ9RwmeFgxnbj9VwYRu9ThL1tAZ+32
72nZjizb2rJWp0UUYSWk5BcgUCa1NPM7gTcrgp9INXidHeTnC5TxUC4N2alUoPsG
r5opxHEVrBWQZxRx6AuOnP6YzmQSJMS4pv+0cH+dP0HUj1X2Kso3OuFNFSplsSPm
I9bZxOazmPNz9x4MYRfp3a5EOp6FmE/EKd2kOZ6EYaD0R+oNsI1lg79lpsDckIHY
ISFZd9sM5xVZTrXhBng2MMfpWwjSqoaJVeycQ91RYr3KzkmUW5UrXN3ksq/ncLBM
SXOcfhFgkLAXUbsEaMQE08ug4J+hEJj5Jh9By9DlNS2UMASySAwuUTJ1c78SE3fs
1YGaxZkt88mEuLOY0wUD0/0gEMshn/Lh3pjJvlYxMtwsdtVMx/zUsdaa6ialAFFl
6aKMCnvaQHtwS3O3oPma6c/qX714AKGmceZswxp8IhErafG0Wca7/JHTrUQNasWZ
HzDFqS2S5F1bu7oKICl+JELnpHu570MjtYXAomw3EHqNvPvw72ihdZ5tBX/QH1l1
iwIv+a76bfM3F5myoPP5uzNk6fPt73u0zLCKYfVK4fvr/V8CtCML6foUFtXGn9OQ
0vS2bGk64K2fTNfcSbC+P6kCAwEAAQ==
-----END PUBLIC KEY-----
] 2:map[creation_time:2023-09-15T18:00:01.621354+02:00 name:rsa-4096 public_key:-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAymFiLHw/G5jZN+xtEBlb
ILhBMmn8F0CchTbKjr/cm8n5vus1OonamkuWY9Zk23al9Rf9mOYg2ZP453fJ8W0z
ZrrUJQxGsEV0T2xmjab5Xk7nUh2cDwhHGZu1t27z0up3F5XKezknokyB+EPAH33H
UrUB/AHMDGRcR0Gb0qNum0k6N2w9Y4iUO9Hq9UjnkpqoqAqY6CFScdl+QKu+kYj3
AlucROc0wR4XhhxAvl6CX25g/0kmonUjLZFylilS6qzqVQMcg7jgHqC8JwJ0S9Ds
RZr65IV4YLw5xBzZhooLEUT8E/8pIZxwJ3UmrR5EZkjkewU+D8YVJ5qYM7S785Cu
kKWB1QwGZ4Bmoue4n3Nw7y5YPGpv4KUrTB8/Kusl420LcgCRA3zENIV9zg+QAoPb
b7JJ4TV3P18uxfbNYn6v/N/GKjaCWYtCMGEKAjhzNZkvtmbGK3zZoE+HhvKb3Z7y
2D5ARe1Hu2e+z9r0PL+LH8ELQT+9kExwVBoMrxcNJq1fPtWSj2szGcq2YJMip91g
G7HQ2p28MVMkkFHSPUsDLPnBcpVs0lOb8mjwU+zeHGTTigH79R3XXsxZ0gwbSPxH
VeFcv3tFAPcQ+suyM2kpwrfFzvGopJSZuL/oUoqMHKdzrSbQy/c2t4W5pVUoHXga
2iEE0ALm/6V1W/3eqP2AIAMCAwEAAQ==
-----END PUBLIC KEY-----
]]
latest_version            2
min_available_version     0
min_decryption_version    1
min_encryption_version    0
name                      demo-key
supports_decryption       true
supports_derivation       false
supports_encryption       true
supports_signing          true
type                      rsa-4096
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The metadata in the output says that &lt;code&gt;latest_version&lt;/code&gt; is now &lt;code&gt;2&lt;/code&gt;. However, &lt;code&gt;min_decryption_version&lt;/code&gt; is &lt;code&gt;1&lt;/code&gt; which means that even if some ciphertext is encrypted using version one of our key it will still be possible for us to decrypt the value. You can configure what the &lt;code&gt;min_decryption_version&lt;/code&gt; should be. Perhaps you don't want to allow any other key than version two to be able to decrypt values, then you can configure this.&lt;/p&gt;

&lt;p&gt;Something else you might want to do is to re-encrypt your data with the latest version of the key. You can do this without exposing the secret data at any stage. To do this run the following command together with the ciphertext from before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write transit/rewrap/demo-key &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;    ciphertext=vault:v1:N+bgVOhRl5xrwcZ7Aovlg2CF9I+sB9eHt8Yspxw0CrGnR/llTp+9KPZjrA8ou2pznNZ20RdVI291TB2e3jKNLVR36pPQ8iEwfm4HNv+TVS+KJl0CvCweBGa84RrDeIUauwSgHx4vWV2SSz6qJSV8qBrwXKRSq6GPWS4bAu6U9+H2Yq6Zpvr3/mpSX1oZz4HL1PP/zds0taYNWaRJBUr9TDbgRQVto57Swy408+x1Fn9tq670go/ghYbXBV5idL+8O5IndKVB5xukwXNVvrXI4fO/lMBBkSfnGsqWUiACqCzYRPABEN2MHI6Zgfjdfi3CEr2XNCMf/dqEyK01WC9S0ERJM064dT+5oCUQzsll8e0dOGSkbFrKfTGWZExMBHrSHDrGREUEf1h/fT4gNf0udg9V5SUt+dniVkKWXQt6JqUhwIMzEglkJmsmrPDOQxXt9nziiO3mUvI1f2d99nYDicdFgCBULP9jQ7EpLM8V0NC1F/J+Rqk663ZHHZrHotfK9hDk54wfWzTfqKj+XYtqmr5SneRePOyCCtB1bOSkulgrPJQpkvrWowMCGz5eiaTrbGZjPORnkYdX8nEqv3kGIrDR6ZiP+HANzU9/BQBalr05A+UWY8XMa/4P9nPCH7cnah/vZxDicvkEF8ROxoslVfZcPPhQwxSCeRwRdX7kDK4=

Key            Value
---            -----
ciphertext     vault:v2:aEu3sdXwj3Wp1gcu3YEwqnQ4iINzH8ZBqzpUZFuLFJjxIGHEs0W9nbeRFdn3ertys6M0yR6D+gOrl+te2tqXuwVmvmbskT9shQYFPzjVfI4L6DpjNWpOvGqXb2A+GS86UJIxwNK/5Y/S+WizSFnQ7BXpMunKvan6lIP0SPSfqfCYzWuqcWfKkftTnzLiddWYxKk8AJHd2B3z+ahSM9pkAtjmxm7I6+SCwJ+VcJRR1UMpz/4/Z9QDgzU2xEMV1Nk3VMdUppJseavXzdl98zkB6X4ulOj9zMDMLwJHpLCVTjnrENhrui4sLWqZAhzTFI5joydpqzSC3mGwHaxrMyGqJ/CQS5WivU05euiQC+QyyziPPoff1ltmgt+OkQoKzjZfgG9Vy/B2wz71NqmFY/zpF+XNrkUVs9G7TlUFCZXwwL0mqzCitUPmP8UllnWVx+s9XoZ3dtdvwHtMPoSlUmjMeftZmxUJXglA6YZXvsI1vhDY7jpornJmlwz2a2L0HsK0zVFGwJE7DKAFBeR85U7XQZmXl+CYS0WE/Mm3VycwQWdxfUpAz7niYz3bfKbSIGJ5b/fsMvHvDQc4nPszdVMqEe5NV6RPFlR0APfl/ys64Izl+GLagitrMzc89PJzj8Y/Z6umz/29hkBb8uom6YTf3cgHLIQEC4HTCV7KZ7wTGzA=
key_version    2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new ciphertext is prepended with &lt;code&gt;vault:v2&lt;/code&gt; which indicates that it is now encrypted with the latest version of the key. Let's verify that we can still decrypt the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write transit/decrypt/demo-key &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;    ciphertext=vault:v2:aEu3sdXwj3Wp1gcu3YEwqnQ4iINzH8ZBqzpUZFuLFJjxIGHEs0W9nbeRFdn3ertys6M0yR6D+gOrl+te2tqXuwVmvmbskT9shQYFPzjVfI4L6DpjNWpOvGqXb2A+GS86UJIxwNK/5Y/S+WizSFnQ7BXpMunKvan6lIP0SPSfqfCYzWuqcWfKkftTnzLiddWYxKk8AJHd2B3z+ahSM9pkAtjmxm7I6+SCwJ+VcJRR1UMpz/4/Z9QDgzU2xEMV1Nk3VMdUppJseavXzdl98zkB6X4ulOj9zMDMLwJHpLCVTjnrENhrui4sLWqZAhzTFI5joydpqzSC3mGwHaxrMyGqJ/CQS5WivU05euiQC+QyyziPPoff1ltmgt+OkQoKzjZfgG9Vy/B2wz71NqmFY/zpF+XNrkUVs9G7TlUFCZXwwL0mqzCitUPmP8UllnWVx+s9XoZ3dtdvwHtMPoSlUmjMeftZmxUJXglA6YZXvsI1vhDY7jpornJmlwz2a2L0HsK0zVFGwJE7DKAFBeR85U7XQZmXl+CYS0WE/Mm3VycwQWdxfUpAz7niYz3bfKbSIGJ5b/fsMvHvDQc4nPszdVMqEe5NV6RPFlR0APfl/ys64Izl+GLagitrMzc89PJzj8Y/Z6umz/29hkBb8uom6YTf3cgHLIQEC4HTCV7KZ7wTGzA=

Key          Value
---          -----
plaintext    czNjcjN0bTNzczRnMwo=
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we must remember to decode the base64-encoded value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"czNjcjN0bTNzczRnMwo="&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;span class="go"&gt;
s3cr3tm3ss4g3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>vault</category>
      <category>hashicorp</category>
      <category>security</category>
      <category>learning</category>
    </item>
    <item>
      <title>Vault Associate Certification (Part 9): Explain Vault architecture</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Fri, 15 Sep 2023 13:09:16 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-9-explain-vault-architecture-5ek4</link>
      <guid>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-9-explain-vault-architecture-5ek4</guid>
      <description>&lt;p&gt;In this course I have demonstrated everything using the Vault development server. The development server is excellent for learning and for quick prototyping. It is not intended for production use. Apart from using Vault to configure auth methods, store secrets, write policies, etc, there is a lot to do when it comes to the Vault architecture. How to configure Vault in a production scenario, as well as how to actually start up a server or cluster, is a big topic. In this post we will look at a few things related to the architecture of Vault and a bit about how Vault functions under the hood.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explain Vault architecture&lt;/strong&gt; makes up the ninth objective in the Vault certification journey. This objective covers the following sub-objectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Describe Shamir secret sharing and unsealing&lt;/li&gt;
&lt;li&gt;Describe seal/unseal&lt;/li&gt;
&lt;li&gt;Describe storage backends&lt;/li&gt;
&lt;li&gt;Describe the encryption of data stored by Vault&lt;/li&gt;
&lt;li&gt;Describe cluster strategy&lt;/li&gt;
&lt;li&gt;Be aware of replication&lt;/li&gt;
&lt;li&gt;Describe the Vault agent&lt;/li&gt;
&lt;li&gt;Describe secrets caching&lt;/li&gt;
&lt;li&gt;Be aware of identities and groups&lt;/li&gt;
&lt;li&gt;Explain response wrapping&lt;/li&gt;
&lt;li&gt;Explain the value of short-lived, dynamically generated secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have rearranged the order of the sub-objectives to make this post a more coherent story. I have also combined a few sub-objectives into one because they were basically covering the same thing.&lt;/p&gt;




&lt;p&gt;Before I start to go through the sub-objectives I would like to briefly introduce how you start a Vault server that is not the development server, because we will see this at a few places in this post.&lt;/p&gt;

&lt;p&gt;You write one or more Vault server configuration files in HashiCorp Configuration Language (HCL). Then you can start the server using a modified &lt;code&gt;vault server&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault server &lt;span class="nt"&gt;-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-server-config.hcl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could provide multiple &lt;code&gt;-config&lt;/code&gt; flags to load multiple configuration files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault server &lt;span class="nt"&gt;-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;config01.hcl &lt;span class="nt"&gt;-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;config02.hcl &lt;span class="nt"&gt;-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;config03.hcl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What goes inside a configuration file? This depends on how you want to configure your Vault server, and we will not go into depth on all the various options. A sample configuration file is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;            &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;cluster_addr&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://127.0.0.1:8201"&lt;/span&gt;
&lt;span class="nx"&gt;api_addr&lt;/span&gt;      &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://127.0.0.1:8200"&lt;/span&gt;

&lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="s2"&gt;"consul"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1:8500"&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vault/"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;listener&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1:8200"&lt;/span&gt;
  &lt;span class="nx"&gt;tls_cert_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/full-chain.pem"&lt;/span&gt;
  &lt;span class="nx"&gt;tls_key_file&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/private-key.pem"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As in all HCL there are arguments and blocks&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. The configuration starts with a few arguments specifying that we would like to enable the UI (&lt;code&gt;ui = true&lt;/code&gt;), and we say that our cluster address is &lt;code&gt;https://127.0.0.1:8201&lt;/code&gt; and API address is &lt;code&gt;https://127.0.0.1:8200&lt;/code&gt;. Following the arguments we have two common blocks seen in most (all?) configuration files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;storage&lt;/code&gt; block specifies what storage backend (more on storage backends later in the post) we want to use. In this case we use the &lt;code&gt;consul&lt;/code&gt; storage backend. The arguments in this block depends on what type of storage backend we select.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;listener&lt;/code&gt; block specifies how we can reach this specific Vault server. In this case we want to use TLS so we need to provide details on where the TLS certificate and key is located.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was a very short introduction to starting up a Vault server for real, but we'll see some more examples further down in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Describe Shamir secret sharing and unsealing and Describe seal/unseal
&lt;/h2&gt;

&lt;p&gt;When you initialize a new production Vault server it starts up in a &lt;strong&gt;sealed&lt;/strong&gt; state. It is not possible to perform any actions in the Vault cluster when it is in the sealed state, except for one important operation: &lt;strong&gt;unsealing&lt;/strong&gt; the Vault server. When the server is sealed it knows where its physical storage is and how to access it, but it does not know how to decrypt the data in this storage.&lt;/p&gt;

&lt;p&gt;The data in the storage is encrypted by a key, the &lt;em&gt;data encryption key&lt;/em&gt; (DEK). This key is in turn encrypted by the &lt;em&gt;root key&lt;/em&gt;, or &lt;em&gt;master key&lt;/em&gt;. Unsealing Vault is the process of obtaining the plaintext root key in order to decrypt the DEK and in turn to decrypt the data stored on disk.&lt;/p&gt;

&lt;p&gt;How does the unseal process work? By default, Vault uses the Shamir's Secret Sharing algorithm in order to split the root key into a number of &lt;em&gt;unseal keys&lt;/em&gt;. In order to reconstruct the root key you need a certain number of these unseal keys. The idea here is that you would give each unseal key to different people, and no single person would be able to get ahold of the root key. Here I would like to mention that there is a process known as &lt;strong&gt;auto-unseal&lt;/strong&gt; which involves using a trusted cloud provider key store to automate the unseal process. I will not cover auto-unseal in this post, but you can read more about it &lt;a href="https://developer.hashicorp.com/vault/docs/concepts/seal#auto-unseal"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To demonstrate how the unseal process works we'll start up a Vault server. In order to do this we need a Vault server configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config.hcl&lt;/span&gt;

&lt;span class="c1"&gt;// use in-memory storage, not intended for production!&lt;/span&gt;
&lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="s2"&gt;"inmem"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;// http listener on 127.0.0.1:8200&lt;/span&gt;
&lt;span class="nx"&gt;listener&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1:8200"&lt;/span&gt;
  &lt;span class="nx"&gt;tls_disable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// we can communicate with Vault on 127.0.0.1:8200&lt;/span&gt;
&lt;span class="nx"&gt;api_addr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://127.0.0.1:8200"&lt;/span&gt;

&lt;span class="c1"&gt;// let's enable the UI&lt;/span&gt;
&lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using my configuration file I can start up my Vault server locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt; vault server &lt;span class="nt"&gt;-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;config.hcl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I have a running Vault server, but it is sealed and I do not have any token to authenticate to Vault with. I can check the status of my server using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault status
&lt;span class="go"&gt;
Key                Value
---                -----
Seal Type          shamir
Initialized        false
Sealed             true
Total Shares       0
Threshold          0
Unseal Progress    0/0
Unseal Nonce       n/a
Version            1.14.0
Build Date         2023-06-19T11:40:23Z
Storage Type       inmem
HA Enabled         false
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output tells me that my server is not initialized (&lt;code&gt;Initialized=false&lt;/code&gt;), and that it is sealed (&lt;code&gt;Sealed=true&lt;/code&gt;). To initialize our Vault server we run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault operator init
&lt;span class="go"&gt;
Unseal Key 1: eu0+iP+Fs71au7YwZN2lo/UpAJDEGdolCLMCiwl0WKCX
Unseal Key 2: 1wYpo/9jyzQSelnXzK4SkSfhpFQe2K+jNSnRGrqQUE1u
Unseal Key 3: 7NXC2Gvded4qyPukMqcYKJRcsj5OVA5KSmdQ45vqcSg8
Unseal Key 4: U25S/3rHC9m/GoRqOs5MoAkAv023W4kcFemzfmfMYGlq
Unseal Key 5: XCrlRf1z3eDBoudSRUwxDPAvyM5dGvwwe7mHy1TjLHal

Initial Root Token: hvs.c3esiYi0AJNSzdn1YxYsJtjx
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output contains five &lt;strong&gt;unseal keys&lt;/strong&gt; and one initial root token. If we check the status of our server again we see the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault status
&lt;span class="go"&gt;
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    0/3
Unseal Nonce       n/a
Version            1.14.0
Build Date         2023-06-19T11:40:23Z
Storage Type       inmem
HA Enabled         false
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server is now initialized, but it is still sealed. We see that &lt;code&gt;Total Shares&lt;/code&gt; is set to &lt;code&gt;5&lt;/code&gt; and &lt;code&gt;Threshold&lt;/code&gt; is set to &lt;code&gt;3&lt;/code&gt;. The &lt;code&gt;Unseal Progress&lt;/code&gt; is &lt;code&gt;0/3&lt;/code&gt;. What this information tells us is that to unseal Vault we must use three of the five available unseal keys. To start the unseal process, copy one of the unseal keys and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault operator unseal
&lt;span class="go"&gt;
Unseal Key (will be hidden): *****

Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    1/3
Unseal Nonce       11a275e7-0e20-262c-7c65-30d4859c8357
Version            1.14.0
Build Date         2023-06-19T11:40:23Z
Storage Type       inmem
HA Enabled         false
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Unseal Progress&lt;/code&gt; changed to &lt;code&gt;1/3&lt;/code&gt;. We can continue the process by issuing a new &lt;code&gt;vault operator unseal&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault operator unseal
&lt;span class="go"&gt;
Unseal Key (will be hidden): *****

Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    2/3
Unseal Nonce       11a275e7-0e20-262c-7c65-30d4859c8357
Version            1.14.0
Build Date         2023-06-19T11:40:23Z
Storage Type       inmem
HA Enabled         false
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Almost there, the &lt;code&gt;Unseal Progress&lt;/code&gt; is now &lt;code&gt;2/3&lt;/code&gt;. We can run the last unseal command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault operator unseal
&lt;span class="go"&gt;
Unseal Key (will be hidden): *****

Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    5
Threshold       3
Version         1.14.0
Build Date      2023-06-19T11:40:23Z
Storage Type    inmem
Cluster Name    vault-cluster-206551a5
Cluster ID      d15ae1cf-bff4-c53d-8131-c037cea78999
HA Enabled      false
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;Sealed&lt;/code&gt; reports &lt;code&gt;false&lt;/code&gt;, and our Vault server is ready to go. We can set our root token in an environment variable and see if we can interact with Vault:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hvs.c3esiYi0AJNSzdn1YxYsJtjx
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets list
&lt;span class="go"&gt;
Path          Type         Accessor              Description
----          ----         --------              -----------
cubbyhole/    cubbyhole    cubbyhole_0bce5c18    per-token private secret storage
identity/     identity     identity_bb8c3d8d     identity store
sys/          system       system_4afad89f       system endpoints
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To recap, what we did was to use three unseal keys to reconstruct the root key in order to decrypt the DEK and finally be able to read the data and configuration stored in Vault.&lt;/p&gt;

&lt;h2&gt;
  
  
  Describe storage backends
&lt;/h2&gt;

&lt;p&gt;As you might have realized Vault needs to store its configuration and data somewhere. This is the purpose of a &lt;strong&gt;storage backend&lt;/strong&gt;. HashiCorp officially supports two storage backends: &lt;strong&gt;integrated storage (Raft)&lt;/strong&gt; and &lt;strong&gt;HashiCorp Consul&lt;/strong&gt;. For the record it is possible to use other storage backends. In this post I will only mention integrated storage and Consul.&lt;/p&gt;

&lt;p&gt;When I have been using the development cluster for demonstrations all the configuration and data have been stored in-memory. This is a bad idea because as soon as I shut my development server down all the configuration and data is gone. This is the reason why we need a storage backend: we want to be able to persist all the important things we do in Vault so that a simple shutdown of a server doesn't wipe our Vault history clean.&lt;/p&gt;

&lt;p&gt;The storage backend is untrusted storage. This might sound like a strange statement, but it simply means that the storage backend is external to Vault proper and thus not trusted. It is used purely for storing encrypted data.&lt;/p&gt;

&lt;p&gt;If you use integrated storage with Raft the data is persisted on some volume attached to the machine where Vault is running. This could be a virtual hard-drive attached to a virtual machine running in the cloud, or a persistent volume attached to a pod in a Kubernetes cluster. To configure your Vault server to use integrated storage you would add a block similar to the following to your cluster configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config.hcl&lt;/span&gt;
&lt;span class="c1"&gt;// ... the rest of the config omitted ...&lt;/span&gt;

&lt;span class="nx"&gt;cluster_addr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://localhost:8201"&lt;/span&gt;

&lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="s2"&gt;"raft"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/opt/vault/data"&lt;/span&gt;
  &lt;span class="nx"&gt;node_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-first-node"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have specified that the data should be stored at the path &lt;code&gt;/opt/vault/data&lt;/code&gt;. Note that this path is on the OS your Vault server is running, it is not a Vault path. We must also provide the cluster address in &lt;code&gt;cluster_addr&lt;/code&gt;. This is because Raft is created for a distributed system, i.e. for running Vault as a cluster of nodes. The &lt;code&gt;cluster_addr&lt;/code&gt; is the address where the nodes in the cluster can communicate with each other.&lt;/p&gt;

&lt;p&gt;It is not clear from my example configuration file above what kind of storage media will be used. It is recommended to use a storage media with high IOPS to handle everything Vault throws at it. You will probably have to run tests to measure the performance of your storage media to determine how much IOPS your environment demands.&lt;/p&gt;

&lt;p&gt;HashiCorp Consul is a separate tool offered by HashiCorp. The setup is more complex than integrated storage because here you will need to run a separate Consul cluster together with your Vault cluster. I will not go into any details of Consul&lt;sup id="fnref2"&gt;2&lt;/sup&gt;. In fact, in the Vault documentation you can find many recommendations to use integrated storage with Raft instead of using Consul. This is simply because the setup with Consul will be more complex, with more moving parts that can break and cause problems in your environments. Also note that if you go with Consul, it is recommended to use a dedicated Consul cluster specifically as the storage backend for Vault. So if you are already running Consul for another purpose do not reuse the same cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Describe the encryption of data stored by Vault
&lt;/h2&gt;

&lt;p&gt;All data stored by Vault is encrypted using the Data Encryption Key (DEK). Vault stores data in a &lt;strong&gt;storage backend&lt;/strong&gt;. The storage backend is not part of the core Vault architecture, it lives outside of Vault. Vault encrypts the data before it leaves Vault proper and ends up at the storage backend. This is because the storage backend is not trusted, it should never see any unencrypted data.&lt;/p&gt;

&lt;p&gt;The concept of Vault not sending out data unencrypted outside its core is known as the &lt;strong&gt;cryptographic barrier&lt;/strong&gt;. Data that passes the cryptographic barrier is encrypted using the DEK. Note however that this does not apply for data that we request when we do e.g. &lt;code&gt;vault kv get kv/database/password&lt;/code&gt; to read a secret - because as we have seen we get the plaintext data back.&lt;/p&gt;

&lt;p&gt;The point of the cryptographic barrier is to protect the data at rest. It would be bad if someone would get ahold of the storage media where we store all our Vault data! Thus the data must be encrypted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Describe cluster strategy
&lt;/h2&gt;

&lt;p&gt;This subsection will briefly outline the recommended cluster architecture for Vault in production. As mentioned in the previous section HashiCorp recommends that you use integrated storage with Raft as your storage backend instead of Consul. We'll use Raft in this subsection.&lt;/p&gt;

&lt;p&gt;Today it is common to run Vault in a public cloud environment, so the following discussion applies to that situation.&lt;/p&gt;

&lt;p&gt;A typical cloud provider (e.g. AWS, Azure, GCP) has a presence in several locations across the globe. These locations are called &lt;strong&gt;regions&lt;/strong&gt;. Each region usually consists of three or more &lt;strong&gt;zones&lt;/strong&gt;. A zone is a data center consisting of one or more buildings housing the networking, CPU, memory, and storage, that make up a cloud provider. The zones are separated by a certain distance from each other, far enough to guarantee that a natural disaster or infrastructure disturbance won't take out two or more zones at the same time. The purpose of zones is to allow you to design highly available and redundant architectures.&lt;/p&gt;

&lt;p&gt;HashiCorp recommends that you run five Vault nodes in a cluster, spread out over at least three zones in a region. An example of what this looks like in Azure is shown in the following figure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Wf76PuEo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/09/cluster01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Wf76PuEo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/09/cluster01.png" alt="test" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this architecture we have five nodes (virtual machines) divided among three subnets in a virtual network (the virtual network itself is not shown in the figure). Each subnet is in a different zone of the region&lt;sup id="fnref3"&gt;3&lt;/sup&gt;. In front of the cluster we have a separate subnet containing an application gateway load balancer (AGW) with a web application firewall (WAF). The AGW is zone-redundant, meaning it is present in each zone of the region. Each subnet has its own network security group (NSG) to control what traffic is allowed to flow in and out of the subnet. The Vault nodes are not directly reachable from the outside, all traffic must go through the AGW/WAF. This is a good base design that follows HashiCorp recommendations but in a real environment this would probably be a small piece of a much larger infrastructure.&lt;/p&gt;

&lt;p&gt;Why is this a recommended architecture? It allows for the loss of a whole zone, e.g. due to a power outage or a natural disaster that renders the data center unreachable. If we lose a zone we lose up to two Vault nodes, leaving us with at least three remaining nodes. This is important because part of the internal machinery of Raft requires at least three nodes to be able to elect a new leader node if the current leader node is lost. This architecture also allows for up to two simultaneous node failures to occur. These kinds of failures do happen, so you must be prepared for them.&lt;/p&gt;

&lt;p&gt;What happens if the whole region would fail? Is this even something that could happen? It can indeed happen. The most likely scenario would be that the cloud provider make a bad deployment that affects a whole region at once. It is unlikely that all the zones would be affected by random events that take them offline at the same time. How should you prepare for a regional failure? The solution involves having multiple clusters, spread out in different regions. The details are beyond the scope of what we need to learn, but it involves setting up clusters and two or more regions and configuring replication between these clusters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be aware of replication
&lt;/h2&gt;

&lt;p&gt;At the end of the last section I mentioned the word &lt;strong&gt;replication&lt;/strong&gt;. There are two purposes of replication that you should be are of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disaster recovery replication&lt;/li&gt;
&lt;li&gt;Performance replication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No matter what type of replication we consider the main principles are the same. The unit of replication is a cluster. There is one leader, or primary, cluster and any number of follower, or secondary, clusters. Most data is replicated asynchronously from the primary cluster to the secondary clusters.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;disaster recovery replication&lt;/strong&gt; most data, including configuration, policies, tokens, leases are shared between the primary and the secondary clusters. This type of replication is meant to support continuous operation in case of failure in the primary cluster. Traffic could quickly be shifted over to the secondary cluster and clients would only detect a minor glitch before things start working again. A disaster recovery replication cluster should not receive any traffic during normal primary cluster operation.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;performance replication&lt;/strong&gt; the underlying configuration, policies, and some secrets are shared among primary and secondary clusters. What is not shared however is tokens and secret leases. The secondary clusters can serve client requests. Each cluster in this setup keeps track of their own tokens and leases. This could at times lead to the need to reauthenticate to Vault in case traffic is shifted from one cluster to another for some reason.&lt;/p&gt;

&lt;p&gt;In summary, I recreate the table from the Vault documentation that highlights the differences between the two types of replication:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Disaster Recovery&lt;/th&gt;
&lt;th&gt;Performance Replication&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mirrors the configuration of a primary cluster&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mirrors the configuration of a primary cluster’s backends (i.e., auth methods, secrets engines, audit devices, etc.)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mirrors the tokens and leases for applications and users interacting with the primary cluster&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No. Secondaries keep track of their own tokens and leases. When the secondary is promoted, applications must reauthenticate and obtain new leases from the newly-promoted primary.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allows the secondary cluster to handle client requests&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Describe the Vault agent
&lt;/h2&gt;

&lt;p&gt;The purpose of the Vault agent is to simplify the integration between your applications and Vault. The Vault agent can handle the authentication to Vault, the administration of token renewals, and the retrieval of secrets. This allows you to use Vault with your application without necessarily making any changes to your application code. The agent could run as a sidecar to your application, or as a separate process on a virtual machine.&lt;/p&gt;

&lt;p&gt;The details of how to configure the Vault agent is beyond the scope of this post, and it will depend a lot on how you deploy and run your application. To get familiar with the Vault agent I recommend doing one of the ten available official tutorials on the subject. For the full list see the &lt;a href="https://developer.hashicorp.com/vault/tutorials/vault-agent"&gt;official page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Describe secrets caching
&lt;/h2&gt;

&lt;p&gt;This sub-objective discuss one important feature of the Vault agent: &lt;strong&gt;secrets caching&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your application sends a large volume of requests to Vault to retrieve secrets and renew tokens, you might experience performance issues with Vault. This could show up as latency in your requests or the need to constantly scale up your Vault cluster to keep up with the demand.&lt;/p&gt;

&lt;p&gt;To alleviate part of this problem the Vault agent can cache tokens and leased secrets. This minimizes the amount of traffic that goes directly to Vault, and instead keeps most of the traffic between your application and the Vault agent.&lt;/p&gt;

&lt;p&gt;There is a specific official tutorial on how to configure secrets caching in Vault, I recommend you take a look at it &lt;a href="https://developer.hashicorp.com/vault/tutorials/vault-agent/agent-caching"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be aware of identities and groups
&lt;/h2&gt;

&lt;p&gt;I have mentioned the identity secrets engine in previous posts. In this sub-section I will repeat some of what I have said and we will see how to use the concept of entities in our policies.&lt;/p&gt;

&lt;p&gt;The identity secrets engine is Vault's internal identity management solution. Our Vault server might have multiple auth methods enabled, and for a given user there might be more than one way to sign-in. Let's say we have a user named Bob who has a GitHub account &lt;code&gt;bob-github&lt;/code&gt; and an account in an unspecified external OIDC system named &lt;code&gt;bob-oidc&lt;/code&gt;. Our Vault server has both the GitHub and OIDC auth methods enabled. Bob could sign in using any of the two available auth methods.&lt;/p&gt;

&lt;p&gt;We can assign Bob an &lt;strong&gt;entity&lt;/strong&gt; in Vault, and we can create &lt;strong&gt;aliases&lt;/strong&gt; for each of Bob's external accounts. So we can have one entity that represents Bob, and one alias connected to this entity for Bob's GitHub account &lt;code&gt;bob-github&lt;/code&gt;, and another alias connected to this entity for Bob's OIDC account &lt;code&gt;bob-oidc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We could assign policies to Bob's entity. This means that no matter what auth method Bob uses to sign in, he would get a token with those policies attached. We could also assign different policies to each of Bob's aliases. This means when Bob signs in using GitHub, he would get some additional policies. When Bob signs in using OIDC he would get a different set of additional policies.&lt;/p&gt;

&lt;p&gt;This is one of the strengths of the identity secrets engine, but there is more.&lt;/p&gt;

&lt;p&gt;Imagine we would like to assign entities the permissions to administrate secrets under their own name. For instance, when Bob signs in we would like to provide him a policy that gives him full access to secrets under &lt;code&gt;secrets/bob/*&lt;/code&gt;. Similarly when Alice signs in she should get a policy that gives her full access to secrets under &lt;code&gt;secrets/alice/*&lt;/code&gt;. Do we need to create a policy for each entity in our system, and make sure to assign the correct policy to the correct entity? We could use the concept of identities to do this in a better way.&lt;/p&gt;

&lt;p&gt;Take a look at the following policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// policy.hcl&lt;/span&gt;
&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="s2"&gt;"secrets/data/{{identity.entity.name}}/*"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;capabilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"create"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"patch"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="s2"&gt;"secrets/metadata/{{identity.entity.name}}/*"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;capabilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"create"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"patch"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have used a special template syntax to make dynamic policies. When this policy is applied, the entity associated with the token is made available to the policy, and the corresponding values are replaced in the policy document. In this case, when Bob signs in &lt;code&gt;{{identity.entity.name}}&lt;/code&gt; is replaced with &lt;code&gt;bob&lt;/code&gt;. Similarly when Alice signs in &lt;code&gt;{{identity.entity.name}}&lt;/code&gt; is replaced with &lt;code&gt;alice&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;identity&lt;/code&gt; object contains a lot of useful information, see the &lt;a href="https://developer.hashicorp.com/vault/docs/concepts/policies#parameters"&gt;documentation&lt;/a&gt; for a list of what is available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explain response wrapping
&lt;/h2&gt;

&lt;p&gt;Let's say you want to share a secret from Vault with someone. You could retrieve the secret value and send it in plaintext to your recipient. This is usually a bad idea because most likely the secret is logged somewhere, at least in your chat or email history.&lt;/p&gt;

&lt;p&gt;Vault has the concept of &lt;strong&gt;response wrapping&lt;/strong&gt;, which allows you to wrap the response to a request. This could be a GET request to retrieve a secret value. The response wrapping hides the actual response to this request, and instead provides you with a single-use token that your recipient can use to retrieve the response. Why is the token single-use? This is a safety mechanism to allow only a single recipient to unwrap the response. If your intended recipient can't unwrap the response because the token has already been used, then you know some man-in-the-middle attack has taken place. You should also make the time-to-live (TTL) of the wrapped response short, preferably seconds or minutes at most.&lt;/p&gt;

&lt;p&gt;To see this in action, let's start a Vault development server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault server &lt;span class="nt"&gt;-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's use the default key/value secrets engine mounted at &lt;code&gt;secret/&lt;/code&gt; and add a new secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv put &lt;span class="nt"&gt;-mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret database/password &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;s3cr3t
&lt;span class="go"&gt;
======== Secret Path ========
secret/data/database/password

======= Metadata =======
Key                Value
---                -----
created_time       2023-09-15T12:55:24.243192Z
&lt;/span&gt;&lt;span class="gp"&gt;custom_metadata    &amp;lt;nil&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;deletion_time      n/a
destroyed          false
version            1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To read this secret back I would run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv get &lt;span class="nt"&gt;-mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret database/password
&lt;span class="go"&gt;
======== Secret Path ========
secret/data/database/password

======= Metadata =======
Key                Value
---                -----
created_time       2023-09-15T12:55:24.243192Z
&lt;/span&gt;&lt;span class="gp"&gt;custom_metadata    &amp;lt;nil&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
password    s3cr3t
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's run that command again, but this time we add the &lt;code&gt;-wrap-ttl&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv get &lt;span class="nt"&gt;-mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret &lt;span class="nt"&gt;-wrap-ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5m database/password
&lt;span class="go"&gt;
Key                              Value
---                              -----
&lt;/span&gt;&lt;span class="gp"&gt;wrapping_token:                  hvs.CAESIMuy&amp;lt;truncated&amp;gt;&lt;/span&gt;c3JWRml3TW1SOWdzMTk
&lt;span class="go"&gt;wrapping_accessor:               Zr7aVEftkByAKPTl3KeZJR2P
wrapping_token_ttl:              5m
wrapping_token_creation_time:    2023-09-15 14:56:59.423872 +0200 CEST
wrapping_token_creation_path:    secret/data/database/password
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We received a &lt;code&gt;wrapping_token&lt;/code&gt; that is valid for a duration of &lt;code&gt;wrapping_token_ttl&lt;/code&gt; (in this case &lt;code&gt;5m&lt;/code&gt;). How do we use this token? First of all we would probably send the wrapping token to the intended receiver, then that person/system would issue the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt; vault unwrap hvs.CAESIMuy&amp;lt;truncated&amp;gt;c3JWRml3TW1SOWdzMTk
&lt;span class="go"&gt;
Key         Value
---         -----
data        map[password:s3cr3t]
&lt;/span&gt;&lt;span class="gp"&gt;metadata    map[created_time:2023-09-15T12:55:24.243192Z custom_metadata:&amp;lt;nil&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deletion_time: destroyed:false version:1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The values are shown as Go maps, but we can identify that the response contains the password. What happens if I try to use the token again? The following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt; vault unwrap hvs.CAESIMuy&amp;lt;truncated&amp;gt;c3JWRml3TW1SOWdzMTk
&lt;span class="go"&gt;
Error unwrapping: Error making API request.

URL: PUT http://127.0.0.1:8200/v1/sys/wrapping/unwrap
Code: 400. Errors:

* wrapping token is not valid or does not exist
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token has expired, because it was only possible to use it once! This is how response wrapping works, and although I showed it with the key/value secrets engine you can use it at a lot of places apart from this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explain the value of short-lived, dynamically generated secrets
&lt;/h2&gt;

&lt;p&gt;This sub-objective was covered in part five of this series. To summarize that discussion briefly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dynamically generated secrets are created when requested, which means they don't exist all the time and get lost and spread around like long-lived static secrets. Dynamic secrets together with a short time-to-live means the secret only exists for a brief period of time when it is needed, and no longer. If the dynamic secret value gets highjacked there is only a small window of time when the attacker could cause harm using the secret.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;It seems like the blocks are called &lt;em&gt;stanzas&lt;/em&gt; in Vault. Not sure why this is! It would be better to use the same nomenclature in all HCL, but that is just my opinion. I will stick to using &lt;strong&gt;blocks&lt;/strong&gt; in this course, because I am not sure it matters all that much what you call it. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;But I will probably write a blog series about the Consul certification in the future! ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;In Azure you actually don't place a subnet in a given zone like you do in AWS, you would rather have to make sure the resources (e.g. virtual machines) that you place in your subnets are in a given zone. For simplicity I drew it as if it works similar to AWS. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>vault</category>
      <category>hashicorp</category>
      <category>security</category>
      <category>learning</category>
    </item>
    <item>
      <title>Vault Associate Certification (Part 8): Be aware of the Vault API</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Mon, 11 Sep 2023 16:02:41 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-8-be-aware-of-the-vault-api-lp3</link>
      <guid>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-8-be-aware-of-the-vault-api-lp3</guid>
      <description>&lt;p&gt;Being aware of the Vault API make up the eight objective in the Vault certification journey. This objective covers the following sub-objectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate to Vault via cURL&lt;/li&gt;
&lt;li&gt;Access Vault secrets via cURL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As is clear from the number of sub-objectives in this part this is not a huge topic. It is important to be aware of a few general things related to using the API that is implicitly handled for you when using the Vault CLI. You are not expected to be able to perform every common action using the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authenticate to Vault via cURL
&lt;/h2&gt;

&lt;p&gt;This sub-objective covers how to authenticate to Vault when running any operation that requires a token.&lt;/p&gt;

&lt;p&gt;The Vault token must be provided as a header in all requests to Vault that requires authentication. There are two options for providing the token:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As the &lt;code&gt;X-Vault-Token&lt;/code&gt; HTTP header with a value of &lt;code&gt;&amp;lt;token&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;As the &lt;code&gt;Authorization&lt;/code&gt; header with a value of &lt;code&gt;Bearer &amp;lt;token&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How do you obtain a token to begin with using the API? This depends on the authentication method you want to use. As an example, using the userpass auth method for a user named &lt;code&gt;bob&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="gp"&gt;    --header "X-Vault-Token: $&lt;/span&gt;VAULT_TOKEN&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;    --request POST \
    --data '{"password": "S3cr3t"}' \
    http://127.0.0.1:8200/v1/auth/userpass/login/bob
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that all requests are sent to &lt;code&gt;&amp;lt;server url&amp;gt;:8200/v1/&amp;lt;path&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The response from this request looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"efa4b077-5820-1682-6c52-d30e8b48cccb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lease_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"renewable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lease_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"wrap_info"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"warnings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"client_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hvs.CAESIPi7&amp;lt;truncated&amp;gt;Qza1ZVQ29LUGs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"accessor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Y13YI0FyHxNm3zXl6i0OtpFI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"policies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"token_policies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bob"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"lease_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2764800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"renewable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"entity_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cdb98f68-38b8-ecbb-7979-2ad1b9818f76"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"orphan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"mfa_requirement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"num_uses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example we also saw how the &lt;code&gt;X-Vault-Token&lt;/code&gt; header is provided in the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access Vault secrets via cURL
&lt;/h2&gt;

&lt;p&gt;For this sub-section I assume you have a development server running with the key/value secrets engine (version 2) enabled at &lt;code&gt;kv/&lt;/code&gt;. The specifics of how to write and read secrets from a secrets engine depends on what secrets engine you are using, read the API documentation for your secrets engine of choice.&lt;/p&gt;

&lt;p&gt;Let us first write a new secret to &lt;code&gt;database/password&lt;/code&gt; with some data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="gp"&gt;    --header "X-Vault-Token: $&lt;/span&gt;VAULT_TOKEN&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;    --request POST \
    --data '{"data":{"password": "S3cr3t"}}' \
    http://127.0.0.1:8200/v1/kv/data/database/password
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how I write this data to &lt;code&gt;kv/data/&amp;lt;my path&amp;gt;&lt;/code&gt;. The response to this request is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b9891d16-779e-af48-7e37-02deaf786e4d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lease_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"renewable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lease_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"created_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-09-11T15:47:09.14348Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"custom_metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"deletion_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"destroyed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"wrap_info"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"warnings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can read this data back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="gp"&gt;    --header "X-Vault-Token: $&lt;/span&gt;VAULT_TOKEN&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;    --request GET \
    http://127.0.0.1:8200/v1/kv/data/database/password
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"af5770e1-3d10-f0be-5536-4b0ab25eafc2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lease_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"renewable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lease_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"S3cr3t"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"created_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-09-11T15:47:09.14348Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"custom_metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"deletion_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"destroyed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wrap_info"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"warnings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We see that the response contains details about the request, details about the lease, and all the &lt;code&gt;data&lt;/code&gt; and &lt;code&gt;metadata&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This was a short post, but it is unlikely to be a big topic on the exam. Remember the following things and you should be good:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The request URL has the format &lt;code&gt;&amp;lt;server URL&amp;gt;:8200/v1/&amp;lt;path&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The token is provided in a header:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;X-Vault-Token&lt;/code&gt; with the value of &lt;code&gt;&amp;lt;token&amp;gt;&lt;/code&gt;, or&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Authorization&lt;/code&gt; with the value of &lt;code&gt;Bearer &amp;lt;token&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vault</category>
      <category>hashicorp</category>
      <category>security</category>
      <category>learning</category>
    </item>
    <item>
      <title>Vault Associate Certification (Part 7): Utilize Vault UI</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Mon, 11 Sep 2023 13:30:09 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-7-utilize-vault-ui-4cge</link>
      <guid>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-7-utilize-vault-ui-4cge</guid>
      <description>&lt;p&gt;Utilizing the Vault UI make up the seventh objective in the Vault certification journey. This objective covers the following sub-objectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate to Vault&lt;/li&gt;
&lt;li&gt;Configure authentication methods&lt;/li&gt;
&lt;li&gt;Configure Vault policies&lt;/li&gt;
&lt;li&gt;Enable Secret engines&lt;/li&gt;
&lt;li&gt;Access Vault secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you start a Vault development server (&lt;code&gt;vault server -dev&lt;/code&gt;) the UI is enabled by default. However, this is not the case for &lt;em&gt;real&lt;/em&gt; Vault servers. We'll discuss this further in part nine of the series, for this part we'll stick to the development server. By default you can reach the UI on your development instance on &lt;code&gt;http://127.0.0.1:8200/ui/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authenticate to Vault
&lt;/h2&gt;

&lt;p&gt;When you first enter the Vault UI you meet the following sign-in page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c3dCM_iJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/signin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c3dCM_iJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/signin.png" alt="signin" width="800" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the drop-down you select the authentication method you would like to use to sign in. Note that the values in the drop-down might show more auth methods than what have been enabled on your Vault server.&lt;/p&gt;

&lt;p&gt;Unless you first enable a few auth methods using the CLI your only option right now is to use the root token to sign in. Copy the root token from the terminal window where you started your Vault development server and paste it into the token input field, then click &lt;em&gt;Sign in&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ErjEHPKd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/signin02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ErjEHPKd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/signin02.png" alt="signin" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you have successfully authenticated to Vault using the UI! The process is more or less the same for any of the other auth methods. You select the auth method you want to use, and you provide the required information to sign in. For some auth methods you will be re-directed to the auth methods own page and then re-directed back to Vault after successful authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure authentication methods
&lt;/h2&gt;

&lt;p&gt;To configure a new auth method click on &lt;em&gt;Access&lt;/em&gt; in the main menu on the left. You come to a page showing all the currently enabled auth methods. If you are using a brand new development server you will only see the &lt;em&gt;token&lt;/em&gt; auth method in the list. To enable a new auth method click on &lt;em&gt;Enable new method&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8prGHCLt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/auth01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8prGHCLt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/auth01.png" alt="auth" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You come to a page showing all the available auth methods that you can enable. For the purpose of this post we will enable the &lt;strong&gt;userpass&lt;/strong&gt; auth method. Select &lt;strong&gt;userpass&lt;/strong&gt; and then click &lt;em&gt;Next&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---CJFsvVg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/auth02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---CJFsvVg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/auth02.png" alt="auth" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you have the option to specify which path the auth method should be enabled on. If you expand the &lt;em&gt;Method Options&lt;/em&gt; section you can also do advanced configuration of the auth method, this includes things such as specifying the default lease TTL, the max TTL, and more. We'll ignore the advanced configurations for now. Accept the default path and click on &lt;em&gt;Enable Method&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NAjW_P3g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/auth03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NAjW_P3g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/auth03.png" alt="auth" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the method is enabled, click on &lt;em&gt;Go to method&lt;/em&gt;. You come to a page listing all the users that are configured in the userpass auth method. Currently we have no user. To add a new user, click on &lt;em&gt;Create user&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4sflH2ji--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/auth04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4sflH2ji--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/auth04.png" alt="auth" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Specify a username (I specify &lt;code&gt;bob&lt;/code&gt;) and a password. Expand the &lt;em&gt;Tokens&lt;/em&gt; section to specify advanced settings for this user, such as which policies to attach to the tokens for this user. For now, let's ignore the advanced settings and click on &lt;em&gt;Save&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RWV4skwu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/auth05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RWV4skwu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/auth05.png" alt="auth" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can sign out of Vault and sign back in using the userpass method and your new user to verify that it works.&lt;/p&gt;

&lt;p&gt;Configuring other auth methods follow the same basic steps, but the configuration for each method will differ. The best approach is to read the documentation for the auth method you want to enable to know what configuration options to provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Vault policies
&lt;/h2&gt;

&lt;p&gt;To configure a new policy click on &lt;em&gt;Policies&lt;/em&gt; in the main menu on the left. You come to a page listing all the policies on the Vault server. If you are using a brand new development server this list will only contain the &lt;em&gt;default&lt;/em&gt; and &lt;em&gt;root&lt;/em&gt; policies. Note that you can't view the root policy, but you can view and even edit the default policy. Click on &lt;em&gt;Create ACL policy&lt;/em&gt; to create a new policy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---E8dc5ox--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/policy01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---E8dc5ox--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/policy01.png" alt="auth" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the &lt;em&gt;Create ACL policy&lt;/em&gt; page enter a name for your policy (I use &lt;code&gt;my-policy&lt;/code&gt;) and enter the policy itself, then click on &lt;em&gt;Create policy&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2Soygh6J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/policy02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2Soygh6J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/policy02.png" alt="auth" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that you could upload a file instead of entering the policy directly on the page.&lt;/p&gt;

&lt;p&gt;Now the policy is available for use in the rest of the Vault UI. If you need to edit or delete a policy go back to the policy page and open the policy you want to update/delete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable Secret engines
&lt;/h2&gt;

&lt;p&gt;To configure a new secrets engine click on &lt;em&gt;Secrets engines&lt;/em&gt; in the main menu on the left. You come to a page listing all the currently enabled secrets engines. My list contains two secrets engines, &lt;em&gt;cubbyhole&lt;/em&gt; mounted on &lt;code&gt;cubbyhole/&lt;/code&gt; and &lt;em&gt;key/value&lt;/em&gt; mounted on &lt;code&gt;secret/&lt;/code&gt;. To add a new secrets engine click on &lt;em&gt;Enable new engine&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aepDLcZ6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aepDLcZ6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets01.png" alt="auth" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the secrets engine you want to enable. For this demonstration I will use the key/value secrets engine. Select the engine and then click on &lt;em&gt;Next&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3I1f6CEO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3I1f6CEO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets02.png" alt="auth" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Specify the path where you want to enable the secrets engine and then click &lt;em&gt;Enable engine&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mfZPFSEx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mfZPFSEx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets03.png" alt="auth" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that you can expand the &lt;em&gt;Method Options&lt;/em&gt; section to reveal advanced settings for the secrets engine. For the key/value secrets engine part of these advanced settings look like the following image. This is where you could specify that you want version 1 of the key/value secrets engine instead of the (default) version 2:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FiyZqey7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FiyZqey7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets04.png" alt="auth" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adding other secrets engines will follow the same pattern, but the various settings might differ between the different secrets engines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access Vault secrets
&lt;/h2&gt;

&lt;p&gt;Now let's continue the last section by adding secrets to our secrets engine. Go to the secrets engine page by clicking on &lt;em&gt;Secrets engines&lt;/em&gt; in the main menu on the left, then select the &lt;code&gt;kv/&lt;/code&gt; secrets engine in the list. Click on &lt;em&gt;Create secret&lt;/em&gt; to add a new secret:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U1nxjAF1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U1nxjAF1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets05.png" alt="auth" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Provide a path for the secret and enter the secret data. I add my secret at the path of &lt;code&gt;database/password&lt;/code&gt; and the secret data consists of &lt;code&gt;password=&amp;lt;my secret password&amp;gt;&lt;/code&gt;. To create the secret click on &lt;em&gt;Save&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4cgh-i_x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4cgh-i_x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets06.png" alt="auth" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that there was an option to add the secret data as JSON instead of adding them as key/value pairs in the UI.&lt;/p&gt;

&lt;p&gt;If we want to read or update our secret we can find it by going back to the secrets engine page and click on &lt;code&gt;database/&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--84hplEZm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--84hplEZm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets07.png" alt="auth" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click on &lt;code&gt;password&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ixn2oJFp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ixn2oJFp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets08.png" alt="auth" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally we arrive at the page showing the secret:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UqXxT0fW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UqXxT0fW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.mattias.engineer/vault/07/secrets09.png" alt="auth" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also see that we can create a new version of the secret, we can delete it, make a copy, view different versions, and more. Also note that there is a separate tab for &lt;em&gt;Metadata&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Working with secrets using the other secrets engines follows similar patterns but will vary depending on the type of secrets engine.&lt;/p&gt;

</description>
      <category>vault</category>
      <category>hashicorp</category>
      <category>security</category>
      <category>learning</category>
    </item>
    <item>
      <title>Vault Associate Certification (Part 6): Utilize Vault CLI</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Fri, 08 Sep 2023 15:17:15 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-6-utilize-vault-cli-487a</link>
      <guid>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-6-utilize-vault-cli-487a</guid>
      <description>&lt;p&gt;In the previous parts of this series we have seen many examples of how to use the Vault CLI to perform various tasks in Vault. In fact, we have only used the CLI to interact with Vault. In the next part of this series we will see the first non-CLI way of interacting with Vault (spoiler: we will use the Vault UI).&lt;/p&gt;

&lt;p&gt;Much of the content in this part will be repetition. But &lt;em&gt;repetition is the mother of learning&lt;/em&gt; is what they say! &lt;/p&gt;

&lt;p&gt;Using the CLI make up the sixth objective in the Vault certification journey. This objective covers the following sub-objectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure authentication methods&lt;/li&gt;
&lt;li&gt;Authenticate to Vault&lt;/li&gt;
&lt;li&gt;Configure Vault policies&lt;/li&gt;
&lt;li&gt;Enable Secret engines&lt;/li&gt;
&lt;li&gt;Access Vault secrets&lt;/li&gt;
&lt;li&gt;Configure environment variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we dig into the sub-objectives let's start with a short CLI-101.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vault CLI 101
&lt;/h2&gt;

&lt;p&gt;I begin with some basics around Vault CLI commands that is common for every command you might run.&lt;/p&gt;

&lt;p&gt;Each (most) command follows this general form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault &amp;lt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;options] &lt;span class="o"&gt;[&lt;/span&gt;path] &lt;span class="o"&gt;[&lt;/span&gt;arguments]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Each command we'll see starts with &lt;code&gt;vault&lt;/code&gt;, because that is the Vault CLI that we all know and love.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;command&amp;gt;&lt;/code&gt; might consist of one or two pieces that determines what operation we want to perform in Vault, e.g. &lt;code&gt;secrets enable&lt;/code&gt;, &lt;code&gt;auth enable&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;token revoke&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;[options]&lt;/code&gt; include flags (i.e. &lt;code&gt;-flag1=value&lt;/code&gt;, &lt;code&gt;-flag2=value&lt;/code&gt;, etc). There are some global flags that are available for all commands, and some specific flags depending on the type of command.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;[path]&lt;/code&gt; specifies the path (remember that everything in Vault exists at a path) where you want to perform the action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using the help flag &lt;code&gt;-h&lt;/code&gt; for the command you would like to run is one of the best way of getting information for a given command. There is also a &lt;code&gt;path-help&lt;/code&gt; command that gives you specific help for a path, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault path-help /sys/capabilities
&lt;span class="go"&gt;
Request:        capabilities
&lt;/span&gt;&lt;span class="gp"&gt;Matching Route: ^capabilities$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;
Fetches the capabilities of the given token on the given path.

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;# PARAMETERS&lt;/span&gt;
&lt;span class="go"&gt;
    path (slice)

        (DEPRECATED) Use 'paths' instead.

    paths (slice)

        Paths on which capabilities are being queried.

    token (string)

        Token for which capabilities are being queried.

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;# DESCRIPTION&lt;/span&gt;
&lt;span class="go"&gt;
Returns the capabilities of the given token on the path.
The path will be searched for a path match in all the policies
associated with the token.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure authentication methods
&lt;/h2&gt;

&lt;p&gt;To enable a new auth method, in this case the &lt;code&gt;userpass&lt;/code&gt; auth method, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault auth &lt;span class="nb"&gt;enable &lt;/span&gt;userpass
&lt;span class="go"&gt;
Success! Enabled userpass auth method at: userpass/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see what auth methods you currently have enabled run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault auth list
&lt;span class="go"&gt;
Path         Type        Accessor                  Description                Version
----         ----        --------                  -----------                -------
token/       token       auth_token_5b8eda92       token based credentials    n/a
userpass/    userpass    auth_userpass_20319701    n/a                        n/a
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuring default settings for an auth method is done using the &lt;code&gt;tune&lt;/code&gt; subcommand. In the following example I change the default lease TTL for tokens coming from the &lt;code&gt;userpass&lt;/code&gt; auth method from 32 days (the default value) to 12 hours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault auth tune &lt;span class="nt"&gt;-default-lease-ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12h userpass/
&lt;span class="go"&gt;
Success! Tuned the auth method at: userpass/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to move an enabled auth method to another path you can do so using the &lt;code&gt;move&lt;/code&gt; subcommand. In the following example I move the &lt;code&gt;userpass&lt;/code&gt; auth method from &lt;code&gt;userpass/&lt;/code&gt; to &lt;code&gt;up/&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault auth move auth/userpass/ auth/up/
&lt;span class="go"&gt;
Started moving auth method auth/userpass/ to auth/up/, with migration ID 3548ea1b-a039-99bc-85c9-94a2578d8d65
Success! Finished moving auth method auth/userpass/ to auth/up/, with migration ID 3548ea1b-a039-99bc-85c9-94a2578d8d65
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that you must specify the full path of &lt;code&gt;auth/userpass/&lt;/code&gt; etc. This is a bit confusing, but all the auth methods are really mounted under &lt;code&gt;auth/&lt;/code&gt; and not directly at &lt;code&gt;userpass/&lt;/code&gt; and so on.&lt;/p&gt;

&lt;p&gt;When you no longer need an auth method you can disable it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault auth disable up/
&lt;span class="go"&gt;
Success! Disabled the auth method (if it existed) at: up/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Authenticate to Vault
&lt;/h2&gt;

&lt;p&gt;In the previous posts we've always had a root token set up with our local Vault CLI so we've not had to authenticate to Vault. If we're a user, or application, we must first authenticate using one of the enabled auth methods.&lt;/p&gt;

&lt;p&gt;To illustrate how to authenticate to vault using the &lt;code&gt;userpass&lt;/code&gt; auth method (to continue from the previous section) first enable the auth method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault auth &lt;span class="nb"&gt;enable &lt;/span&gt;userpass
&lt;span class="go"&gt;
Success! Enabled userpass auth method at: userpass/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next create a user named &lt;code&gt;alice&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write auth/userpass/users/alice &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;    password=S3cr3t123 \
    policies=my-policy

Success! Data written to: auth/userpass/users/alice
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To authenticate to Vault using the &lt;code&gt;userpass&lt;/code&gt; auth method and the user named &lt;code&gt;alice&lt;/code&gt; run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault login &lt;span class="nt"&gt;-method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;userpass &lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;alice
&lt;span class="go"&gt;
Password (will be hidden): ***

Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                    Value
---                    -----
&lt;/span&gt;&lt;span class="gp"&gt;token                  hvs.CAESIBZ7YFEM&amp;lt;truncated&amp;gt;&lt;/span&gt;PaXJwVFdaUDU
&lt;span class="go"&gt;token_accessor         WuKRVGKKGMlcRE3oBdHfnyqP
token_duration         768h
token_renewable        true
token_policies         ["default" "my-policy"]
identity_policies      []
policies               ["default" "my-policy"]
token_meta_username    alice
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar commands (&lt;code&gt;vault login -method=&amp;lt;auth method&amp;gt;&lt;/code&gt;) would be used for the other available auth methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Vault policies
&lt;/h2&gt;

&lt;p&gt;Do you remember policies from back in part two of this course? Policies are used to allow capabilities (actions) on a path(s) for a token. Policies are written in HashiCorp Configuration Language (HCL) or JSON and consist of one or more &lt;code&gt;path&lt;/code&gt; blocks.&lt;/p&gt;

&lt;p&gt;An example of a basic policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// policy.hcl&lt;/span&gt;

&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="s2"&gt;"kv/database/password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;capabilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"read"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="s2"&gt;"kv/api/key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;capabilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"read"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This policy allows a given token to read from two paths, &lt;code&gt;kv/database/password&lt;/code&gt; and &lt;code&gt;kv/api/key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once we have written our policy document (&lt;code&gt;policy.hcl&lt;/code&gt; in this case) we can create the actual policy in Vault:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault policy write my-policy policy.hcl
&lt;span class="go"&gt;
Success! Uploaded policy: my-policy
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can list all our available policies using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault policy list
&lt;span class="go"&gt;
default
my-policy
root
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To view a specific policy we can use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault policy &lt;span class="nb"&gt;read &lt;/span&gt;my-policy
&lt;span class="go"&gt;
path "kv/database/password" {
    capabilities = [ "read" ]
}

path "kv/api/key" {
    capabilities = [ "read" ]
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we no longer need to keep a policy we can delete it using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault policy delete my-policy
&lt;span class="go"&gt;
Success! Deleted policy: my-policy
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apart from that there is not much more to say about working with policies using the Vault CLI. To see all available sub-commands and options use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault policy &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enable Secret engines
&lt;/h2&gt;

&lt;p&gt;In the previous post we saw several examples of how to work with secrets engines using the Vault CLI. This section contains a summary of the common commands. I will illustrate the commands using the key/value (kv) secrets engine, both version 1 and 2.&lt;/p&gt;

&lt;p&gt;To enable the kv secrets engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# enabling version 1 at the path kv/&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt; vault secrets &lt;span class="nb"&gt;enable &lt;/span&gt;kv
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# enabling version 1 at the path kvv1/&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt; vault secrets &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kvv1 kv
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# enabling version 2 at the path kvv2/&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt; vault secrets &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kvv2 kv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have enabled version 1 at the path &lt;code&gt;kv/&lt;/code&gt; and would like to upgrade it to version 2 you could run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv enable-versioning kv
&lt;span class="go"&gt;
Success! Tuned the secrets engine at: kv/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see all the secrets engines I have enabled I can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets list
&lt;span class="go"&gt;
Path          Type         Accessor              Description
----          ----         --------              -----------
cubbyhole/    cubbyhole    cubbyhole_e99e381f    per-token private secret storage
identity/     identity     identity_146bba4e     identity store
kv/           kv           kv_cd4c8f19           n/a
secret/       kv           kv_5260e2d2           key/value secret storage
sys/          system       system_55d6ae73       system endpoints
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I would like additional details I can add the &lt;code&gt;-detailed&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets list &lt;span class="nt"&gt;-detailed&lt;/span&gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;output not shown due to size limits&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can move a secrets engine from one path to another. To move my kv secrets engine from &lt;code&gt;kv/&lt;/code&gt; to &lt;code&gt;kv-new/&lt;/code&gt; I run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets move kv/ kv-new/
&lt;span class="go"&gt;
Started moving secrets engine kv/ to kv-new/, with migration ID a63e75dd-b047-f095-6b67-de1fe27bef88
Success! Finished moving secrets engine kv/ to kv-new/, with migration ID a63e75dd-b047-f095-6b67-de1fe27bef88
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I no longer need a given secrets engine I can disable it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets disable kv-new
&lt;span class="go"&gt;
Success! Disabled the secrets engine (if it existed) at: kv-new/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Access Vault secrets
&lt;/h2&gt;

&lt;p&gt;We saw many examples of how to access Vault secrets using the Vault CLI in the previous post, but we'll repeat that material a bit here. For these examples I will continue using the key/value secrets engine, version 2. To start I enable the secrets engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secrets kv
&lt;span class="go"&gt;
Success! Enabled the kv secrets engine at: secrets/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next I store a secret at the path &lt;code&gt;database/passwords&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv put secrets/database/password &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;s3cr3tz
&lt;span class="go"&gt;
========= Secret Path =========
secrets/data/database/password

======= Metadata =======
Key                Value
---                -----
created_time       2023-09-08T15:05:57.185984Z
&lt;/span&gt;&lt;span class="gp"&gt;custom_metadata    &amp;lt;nil&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;deletion_time      n/a
destroyed          false
version            1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since I am using version 2 of the kv secrets engine I can update the password to a new version if I like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv put secrets/database/password &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;s3cr3tzhello
&lt;span class="go"&gt;
========= Secret Path =========
secrets/data/database/password

======= Metadata =======
Key                Value
---                -----
created_time       2023-09-08T15:07:03.875497Z
&lt;/span&gt;&lt;span class="gp"&gt;custom_metadata    &amp;lt;nil&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;deletion_time      n/a
destroyed          false
version            2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that &lt;code&gt;version&lt;/code&gt; increased to &lt;code&gt;2&lt;/code&gt;. To read my secret back I can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv get secrets/database/password
&lt;span class="go"&gt;
========= Secret Path =========
secrets/data/database/password

======= Metadata =======
Key                Value
---                -----
created_time       2023-09-08T20:07:03.875497Z
&lt;/span&gt;&lt;span class="gp"&gt;custom_metadata    &amp;lt;nil&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;deletion_time      n/a
destroyed          false
version            2

====== Data ======
Key         Value
---         -----
password    s3cr3tzhello
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This output is not very convenient for parsing, so let's specify that we want JSON output instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv get &lt;span class="nt"&gt;-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json secrets/database/password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The returned data has this format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"d39f28d7-14fc-2f40-5b8d-c7fac2c6cc9f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lease_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lease_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"renewable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3cr3tzhello"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"created_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-09-08T20:07:03.875497Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"custom_metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"deletion_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"destroyed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"warnings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To access a given version of the secret (i.e. an older version) I run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv get &lt;span class="nt"&gt;-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 secrets/database/password
&lt;span class="go"&gt;
========= Secret Path =========
secrets/data/database/password

======= Metadata =======
Key                Value
---                -----
created_time       2023-09-08T20:05:57.185984Z
&lt;/span&gt;&lt;span class="gp"&gt;custom_metadata    &amp;lt;nil&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
password    s3cr3tz
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I would like to only access the metadata for a given secret I can do so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv metadata get secrets/database/password
&lt;span class="go"&gt;
========== Metadata Path ==========
secrets/metadata/database/password

========== Metadata ==========
Key                     Value
---                     -----
cas_required            false
created_time            2023-09-08T20:05:57.185984Z
current_version         2
&lt;/span&gt;&lt;span class="gp"&gt;custom_metadata         &amp;lt;nil&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;delete_version_after    0s
max_versions            0
oldest_version          0
updated_time            2023-09-08T20:07:03.875497Z

====== Version 1 ======
Key              Value
---              -----
created_time     2023-09-08T20:05:57.185984Z
deletion_time    n/a
destroyed        false

====== Version 2 ======
Key              Value
---              -----
created_time     2023-09-08T20:07:03.875497Z
deletion_time    n/a
destroyed        false
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When working with version 2 of the kv secrets engine we can delete versions of our secrets and we can destroy them. Deleting a secret version just places a delete marker on it, but it is recoverable. Destroying a secret version permanently removes it. In the following command I destroy both version 1 and 2 of my secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv destroy &lt;span class="nt"&gt;-versions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1,2 secrets/database/password
&lt;span class="go"&gt;
Success! Data written to: secrets/destroy/database/password
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure environment variables
&lt;/h2&gt;

&lt;p&gt;There are a number of environment variables that Vault looks for. Most of them are not required at all but could be useful in certain situations. I'll explain a few of the most common environment variables below&lt;sup id="fnref1"&gt;1&lt;/sup&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;VAULT_TOKEN&lt;/code&gt; allows you to set the current Vault token which should be used for all the commands you execute.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VAULT_ADDR&lt;/code&gt; should contain the URL and port where Vault is running, e.g. &lt;code&gt;https://my-vault-server.com:8200/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VAULT_CLIENT_TIMEOUT&lt;/code&gt; is the timeout for commands issued to the Vault server, it defaults to 60 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VAULT_FORMAT&lt;/code&gt; allows you to select the output format for the CLI. The available values are &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;yaml&lt;/code&gt;, and &lt;code&gt;table&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VAULT_LOG_LEVEL&lt;/code&gt; allows you to control the verbosity of the logs that Vault reports. Available values are &lt;code&gt;trace&lt;/code&gt;, &lt;code&gt;debug&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;warn&lt;/code&gt;, and &lt;code&gt;err&lt;/code&gt;. The default log level is &lt;code&gt;info&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;For a complete list of environment variables see &lt;a href="https://developer.hashicorp.com/vault/docs/commands#environment-variables"&gt;https://developer.hashicorp.com/vault/docs/commands#environment-variables&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>vault</category>
      <category>hashicorp</category>
      <category>security</category>
      <category>learning</category>
    </item>
    <item>
      <title>Vault Associate Certification (Part 5): Compare and configure Vault secrets engines</title>
      <dc:creator>Mattias Fjellström</dc:creator>
      <pubDate>Fri, 08 Sep 2023 08:45:37 +0000</pubDate>
      <link>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-5-compare-and-configure-vault-secrets-engines-56m4</link>
      <guid>https://dev.to/mattiasfjellstrom/vault-associate-certification-part-5-compare-and-configure-vault-secrets-engines-56m4</guid>
      <description>&lt;p&gt;Secrets engines make up the fifth objective in the Vault certification journey. This objective covers the following sub-objectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define secrets engines&lt;/li&gt;
&lt;li&gt;Choose a secret method based on use case&lt;/li&gt;
&lt;li&gt;Contrast dynamic secrets vs. static secrets and their use cases&lt;/li&gt;
&lt;li&gt;Define transit engine&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Define secrets engines
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In the official list of sub-objectives for this certification objective the &lt;em&gt;Define secrets engines&lt;/em&gt; sub-objective is listed as the last of four sub-objectives. I think it makes more sense to go through it first because all of the other sub-objectives build upon what secrets engines are.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Secrets engines allow us to work with secrets in a third-party system. I say third-party system, because that will most likely be the case. There are secrets engines that do not involve a third-party system too, but they are fewer in number. Generally, a secrets engine store, generate, or encrypts data.&lt;/p&gt;

&lt;p&gt;There are a number of secrets engines that are enabled by default when you start up a Vault server. What secrets engines are available depends a bit on your server, but there are two which are always mounted by default and they are important to be aware about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;cubbyhole&lt;/code&gt; secrets engine is a special secrets engine where each Vault token has its own secrets storage. A secret stored in a cubbyhole for one token can only be retrieved by that same token.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;identity&lt;/code&gt; secrets engine is another special secrets engine used for managing identities (or &lt;em&gt;entities&lt;/em&gt;) in Vault. It is the internal identity management solution for Vault. If you have enabled multiple auth methods on your server your users might use any of them to sign in. An &lt;strong&gt;entity&lt;/strong&gt; is tied to a specific user no matter what auth method that user used to sign in to Vault. The &lt;code&gt;identity&lt;/code&gt; secrets engine is used to administer these entities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that none of these two special secrets engines can be disabled.&lt;/p&gt;

&lt;p&gt;To enable a new secrets engine using the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt; vault secrets &lt;span class="nb"&gt;enable&lt;/span&gt; &amp;lt;name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; with the secrets engine you want to enable. Currently the following official secrets engine are supported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active Directory&lt;/li&gt;
&lt;li&gt;AliCloud&lt;/li&gt;
&lt;li&gt;AWS&lt;/li&gt;
&lt;li&gt;Azure&lt;/li&gt;
&lt;li&gt;Consul&lt;/li&gt;
&lt;li&gt;Cubbyhole&lt;/li&gt;
&lt;li&gt;Databases (with plugins for e.g. Cassandra, Couchbase, Elasticsearch, MongoDB, MSSQL, Oracle, PostgreSQL, Redis, Snowflake, and more)&lt;/li&gt;
&lt;li&gt;Google Cloud&lt;/li&gt;
&lt;li&gt;Google Cloud KMS&lt;/li&gt;
&lt;li&gt;Identity&lt;/li&gt;
&lt;li&gt;Key Management (Azure Key Vault, AWS KMS, GCP Cloud KMS)&lt;/li&gt;
&lt;li&gt;Key/Value (version 1 and 2)&lt;/li&gt;
&lt;li&gt;KMIP&lt;/li&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;li&gt;MongoDB Atlas&lt;/li&gt;
&lt;li&gt;Nomad&lt;/li&gt;
&lt;li&gt;LDAP&lt;/li&gt;
&lt;li&gt;PKI (certificates)&lt;/li&gt;
&lt;li&gt;RabbitMQ&lt;/li&gt;
&lt;li&gt;SSH&lt;/li&gt;
&lt;li&gt;Terraform Cloud&lt;/li&gt;
&lt;li&gt;TOTP&lt;/li&gt;
&lt;li&gt;Transform&lt;/li&gt;
&lt;li&gt;Transit&lt;/li&gt;
&lt;li&gt;Venafi (certificates)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is clear that this is a long list. To see which secrets engines you have currently enabled in your Vault server run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets list 
&lt;span class="go"&gt;
Path          Type         Accessor              Description
----          ----         --------              -----------
azure/        azure        azure_2f066eac        n/a
cubbyhole/    cubbyhole    cubbyhole_255a0b95    per-token private secret storage
identity/     identity     identity_1b99e3f9     identity store
kv/           kv           kv_35836191           n/a
kvv2/         kv           kv_5beecabf           n/a
secret/       kv           kv_cdd3836c           key/value secret storage
sys/          system       system_2d023c0b       system endpoints used for control, policy and debugging
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have enabled a few secrets engines as you can tell from the output.&lt;/p&gt;

&lt;p&gt;Apart from the secrets engines listed above it is also possible to create your own plugins to extend the support in Vault to other systems. Of course I will not go through how to use each and every secrets engine in this post, but we'll see a few of them in use.&lt;/p&gt;

&lt;p&gt;The lifecycle of a secrets engine generally follows these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable the secrets engine in Vault.&lt;/li&gt;
&lt;li&gt;Tune the global settings of the secrets engine. This step is not required if you accept the default settings.&lt;/li&gt;
&lt;li&gt;Use the secrets engine to generate secrets, store secrets, and more, for users and applications.&lt;/li&gt;
&lt;li&gt;Disable the secrets engine when no longer needed. This step might not happen very often, usually you keep a secrets engine for a forseeable future once you have started using it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To get our hands dirty we will take a look at two common secrets engines that will most likely appear in the certification exam in one way or another: the key/value secrets engine. This secrets engine exists in two versions. The difference between the two versions is that version 2 supports versioned secrets. Let's begin by enabling version 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets &lt;span class="nb"&gt;enable &lt;/span&gt;kv
&lt;span class="go"&gt;
Success! Enabled the kv secrets engine at: kv/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key/value secrets engine allows me to store static secrets as key/value pairs at paths below the &lt;code&gt;kv/&lt;/code&gt; path. Let's store some data at &lt;code&gt;kv/database/password&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv put kv/database/password &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;p@ssw0rd
&lt;span class="go"&gt;
Success! Data written to: kv/database/password
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's read this data back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv get kv/database/password
&lt;span class="go"&gt;
====== Data ======
Key         Value
---         -----
password    p@ssw0rd
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was easy enough. Let's now perform the same steps using version 2 of the key/value secrets engine. I begin by enabling the secrets engine at the &lt;code&gt;kvv2/&lt;/code&gt; path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kvv2 kv
&lt;span class="go"&gt;
Success! Enabled the kv secrets engine at: kvv2/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next let's store my database password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt; vault kv put kvv2/database/password &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;p@ssw0rd
&lt;span class="go"&gt;
======= Secret Path =======
kvv2/data/database/password

======= Metadata =======
Key                Value
---                -----
created_time       2023-09-08T20:19:25.450796Z
&lt;/span&gt;&lt;span class="gp"&gt;custom_metadata    &amp;lt;nil&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;deletion_time      n/a
destroyed          false
version            1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output looks different than before. Notably we see that the output includes metadata about the secret, and among this metadata is the &lt;code&gt;version&lt;/code&gt;. Remember that version 2 of the key/value secrets engine supports versioned secrets. What this means in practice is that I can store new versions of the same secret (i.e. rotating the secret) at the same path, but still retrieve old versions if needed. This is not possible with version 1 of the key/value secrets engine. If I write to a path that already exists version 1 of key/value secrets engine would just replace the value stored at that path.&lt;/p&gt;

&lt;p&gt;Another thing to note is that although I wrote the data to &lt;code&gt;kvv2/database/password&lt;/code&gt; the actual data is available at &lt;code&gt;kvv2/data/database/password&lt;/code&gt; and the metadata is available at &lt;code&gt;kvv2/metadata/database/password&lt;/code&gt;. This is a bit confusing but this is how this secrets engine works. It is important to remember this when writing policies for applications that must use this secrets engine. We could write policies that allow access to the metadata but not the data, or vice versa.&lt;/p&gt;

&lt;p&gt;Now let's read this data back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault kv get kvv2/database/password
&lt;span class="go"&gt;
======= Secret Path =======
kvv2/data/database/password

======= Metadata =======
Key                Value
---                -----
created_time       2023-09-08T20:19:25.450796Z
&lt;/span&gt;&lt;span class="gp"&gt;custom_metadata    &amp;lt;nil&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
password    p@ssw0rd
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We see that I get both the metadata and data back when I read the secret path.&lt;/p&gt;

&lt;p&gt;HashiCorp has great tutorials on the key/value secrets engine that you should go through in order to get some hands-on practice using them, it will be beneficial for the certification. See the following tutorials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/vault/tutorials/secrets-management/static-secrets"&gt;Static secrets: Key/value secrets engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/vault/tutorials/secrets-management/versioned-kv"&gt;Versioned Key/value secrets engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/vault/tutorials/secrets-management/compare-kv-versions"&gt;Compare key/value secrets engine v1 and v2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have seen how to work with static secrets in Vault. In a later section we will see examples of dynamic secrets and how the concept of a lease exists for those kinds of secrets, similar to what we saw for tokens in the previous post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose a secret method based on use case
&lt;/h2&gt;

&lt;p&gt;Similarly to how we choose auth methods based on the use-case we have, or the available third-party systems that integrate with Vault, we also choose the secrets engines we want to use based on what our use-case is and which third-party systems we might already be using.&lt;/p&gt;

&lt;p&gt;There are obvious use-cases where the choice of secrets engine is easy to make:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let's say we want our application to be able to access blobs stored in Amazon S3. The AWS secrets engine seems apt in this case.&lt;/li&gt;
&lt;li&gt;If I run my applications in Azure and I need Azure credentials in order to access databases, key vaults, storage, or anything else in Azure then the Azure secrets engine is the obvious choice.&lt;/li&gt;
&lt;li&gt;If we are working with a given database system we will probably want to use database secrets engine with the correct plugin.&lt;/li&gt;
&lt;li&gt;If I need to be able to generate certificates for my applications and clients I will probably use the PKI secrets engine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We see that to choose a secrets engine is not that difficult. It depends on what types of secrets you want to work with and what third-party systems (e.g. AWS, Azure, GCP) you are already using.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contrast dynamic secrets vs. static secrets and their use cases
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Static secrets&lt;/strong&gt; are secrets that doesn't change. At lease they do not change very often. There is a big issue with static secrets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic secrets&lt;/strong&gt; are generated on-the-fly when we ask for them, and they expire once the secret lease expires. If regular secrets would be known as Secrets 1.0, then dynamic secrets would be Secrets 2.0. To illustrate the power of dynamic secrets we'll go through an example.&lt;/p&gt;

&lt;p&gt;I will use the Azure secrets engine to generate credentials for a service principal on-the-fly. I will not spend time on explaining the Azure-specific configuration in detail, we'll instead focus on what we need to configure in Vault. Let's begin by enabling the Azure secrets engine at the path &lt;code&gt;azure/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets &lt;span class="nb"&gt;enable &lt;/span&gt;azure
&lt;span class="go"&gt;
Success! Enabled the azure secrets engine at: azure/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We must allow Vault to administer Azure service principals for us, so we must configure the secrets engine with credentials to Azure. In Azure I configure a service principal that Vault can use, and I add this service principal as an owner of my subscription as well as give it permissions to create new service principals in my Azure AD tenant. I store some required values for this in environment variables &lt;code&gt;AZURE_SUBSCRIPTION_ID&lt;/code&gt;, &lt;code&gt;AZURE_TENANT_ID&lt;/code&gt;, &lt;code&gt;AZURE_CLIENT_ID&lt;/code&gt;, and &lt;code&gt;AZURE_CLIENT_SECRET&lt;/code&gt;. Now I am ready to configure the secrets engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write azure/config &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="gp"&gt;    subscription_id=$&lt;/span&gt;AZURE_SUBSCRIPTION_ID &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="gp"&gt;    tenant_id=$&lt;/span&gt;AZURE_TENANT_ID &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="gp"&gt;    client_id=$&lt;/span&gt;AZURE_CLIENT_ID &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="gp"&gt;    client_secret=$&lt;/span&gt;AZURE_CLIENT_SECRET
&lt;span class="go"&gt;
Success! Data written to: azure/config
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we technically know the password of this service principal that Vault will be using. That is not ideal. Most secrets engines allow us to rotate the credential that is used for administrative purposes, so that only Vault knows the secret value. To do this run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write &lt;span class="nt"&gt;-f&lt;/span&gt; azure/rotate-root
&lt;span class="go"&gt;
Success! Data written to: azure/rotate-root
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the old secret is actually not deleted, and you must go do that manually. The new secret is being used, and only Vault knows its value.&lt;/p&gt;

&lt;p&gt;Next I must configure a &lt;strong&gt;role&lt;/strong&gt; in the secrets engine. A role describes what permissions will be granted to the dynamic secrets that are generated. I will call my role &lt;code&gt;demo-role&lt;/code&gt; and it will get the contributor role (here the role refers to the &lt;em&gt;Azure RBAC role&lt;/em&gt;) on a specific resource group named &lt;code&gt;rg-vault-demo&lt;/code&gt; in my Azure subscription:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write azure/roles/demo-role &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5m &lt;span class="nv"&gt;azure_roles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="go"&gt;    [
        {
            "role_name": "Contributor",
&lt;/span&gt;&lt;span class="gp"&gt;            "scope":  "/subscriptions/$&lt;/span&gt;AZURE_SUBSCRIPTION_ID/resourceGroups/rg-vault-demo&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="go"&gt;        }
    ]
EOF

Success! Data written to: azure/roles/demo-role
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could have provided multiple Azure RBAC roles for various scopes if I wanted to. Note that I also configured that the time-to-live (TTL) of the dynamic secret should be five minutes (&lt;code&gt;ttl=5m&lt;/code&gt;). To have Vault generate dynamic secrets I run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault &lt;span class="nb"&gt;read &lt;/span&gt;azure/creds/demo-role
&lt;span class="go"&gt;
Key                Value
---                -----
lease_id           azure/creds/demo-role/HsUWYY9Ue37CXdI84bZ7e2UH
lease_duration     5m
lease_renewable    true
client_id          d4af1d42-d4c7-4c49-b8ec-9fb8920350e1
&lt;/span&gt;&lt;span class="gp"&gt;client_secret      wZP8Q&amp;lt;masked&amp;gt;&lt;/span&gt;NOadO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are provided with &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; that can be used to interact with Azure. Here we also see that I get a &lt;code&gt;lease_id&lt;/code&gt;, a &lt;code&gt;lease_duration&lt;/code&gt;, and a &lt;code&gt;lease_renewable&lt;/code&gt; property. The lease ID is the handle for this secret that allows me to perform actions similar to what we saw for tokens in the previous post. I can lookup a given lease ID to get information about it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault lease lookup azure/creds/demo-role/HsUWYY9Ue37CXdI84bZ7e2UH
&lt;span class="go"&gt;
Key             Value
---             -----
expire_time     2023-09-08T21:20:38.971253+02:00
id              azure/creds/demo-role/HsUWYY9Ue37CXdI84bZ7e2UH
issue_time      2023-09-08T21:15:38.971252+02:00
&lt;/span&gt;&lt;span class="gp"&gt;last_renewal    &amp;lt;nil&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;renewable       true
ttl             2m8s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can renew the lease as long as it is renewable (i.e. it has not expired and &lt;code&gt;renewable&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault lease renew azure/creds/demo-role/HsUWYY9Ue37CXdI84bZ7e2UH
&lt;span class="go"&gt;
Key                Value
---                -----
lease_id           azure/creds/demo-role/HsUWYY9Ue37CXdI84bZ7e2UH
lease_duration     5m
lease_renewable    true
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I am done using the secret and want to revoke it early I can do so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault lease revoke azure/creds/demo-role/HsUWYY9Ue37CXdI84bZ7e2UH
&lt;span class="go"&gt;
All revocation operations queued successfully!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I try to lookup my lease ID now I get an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault lease lookup azure/creds/demo-role/HsUWYY9Ue37CXdI84bZ7e2UH
&lt;span class="go"&gt;
error looking up lease id azure/creds/demo-role/HsUWYY9Ue37CXdI84bZ7e2UH: Error making API request.

URL: PUT http://127.0.0.1:8200/v1/sys/leases/lookup
Code: 400. Errors:

* invalid lease
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is clear that working with leases for dynamic secrets seems a bit better than working with leases for tokens due to the fact that there is a separate &lt;code&gt;lease_id&lt;/code&gt; for dynamic secrets. This means we could delegate the administration of the lease to a separate user or application, without giving away the actual secret value.&lt;/p&gt;

&lt;p&gt;If the TTL expires by itself the secret is revoked, and the generated service principal in Azure is deleted. This is the power of dynamic secrets compared to static secrets with a possibly infinite lifetime.&lt;/p&gt;

&lt;p&gt;So when should we use dynamic secrets? My answer is that use them whenever you can. Sometimes it might not be possible to do so, if the secrets engine you use does not support it (e.g. key/value secrets engine) or if you have a legacy system where it is difficult to implement the handling of dynamic secrets. Before you decide that it is too difficult to implement dynamic secrets in your legacy app, read up on the sidecar pattern and the Vault agent to see if they might help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define transit engine
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;transit engine&lt;/strong&gt; is used for &lt;strong&gt;cryptography-as-a-service&lt;/strong&gt; (CaaS) or &lt;strong&gt;encryption-as-a-service&lt;/strong&gt; (EaaS). It is used for various encryption services on data in-transit (thus the name). This secrets engine have a few different use-cases but the primary use-case concerns offloading cryptographic operations from applications, and allowing applications to encrypt data before it is stored in the application's primary data store. The transit secrets engine allows you to standardize how you handle encryption in all of your applications.&lt;/p&gt;

&lt;p&gt;An important point to note is that the transit secrets engine does not itself store any data in Vault! It is your applications responsibility to persist the encrypted values to secure storage.&lt;/p&gt;

&lt;p&gt;Enabling the transit secrets engine is done using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt; vault secrets &lt;span class="nb"&gt;enable &lt;/span&gt;transit
&lt;span class="go"&gt;
Success! Enabled the transit secrets engine at: transit/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could also have enabled the transit secrets engine at a non-default path by adding the &lt;code&gt;-path=&amp;lt;path&amp;gt;&lt;/code&gt; flag like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault secrets &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mytransit transit
&lt;span class="go"&gt;
Success! Enabled the transit secrets engine at: mytransit/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the secrets engine enabled I can create an encryption-key named &lt;code&gt;my-encryption-key&lt;/code&gt; with default settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write &lt;span class="nt"&gt;-f&lt;/span&gt; transit/keys/my-encryption-key
&lt;span class="go"&gt;
Key                       Value
---                       -----
allow_plaintext_backup    false
auto_rotate_period        0s
deletion_allowed          false
derived                   false
exportable                false
imported_key              false
keys                      map[1:1694077952]
latest_version            1
min_available_version     0
min_decryption_version    1
min_encryption_version    0
name                      my-encryption-key
supports_decryption       true
supports_derivation       true
supports_encryption       true
supports_signing          false
type                      aes256-gcm96
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default &lt;code&gt;type&lt;/code&gt; of the encryption key is &lt;code&gt;aes256-gcm96&lt;/code&gt;, if I wanted to I could provide a different type by adding the &lt;code&gt;-type=&amp;lt;type&amp;gt;&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;Finally I can use my new encryption-key to encrypt some data using the &lt;code&gt;transit/encrypt/&lt;/code&gt; path for my key. Note that the data we want to encrypt must be base64-encoded due to the fact that Vault accepts any binary data and not just text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write transit/encrypt/my-encryption-key &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="gp"&gt;    plaintext=$&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"s3cr3t p4ssw0rd"&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;
Key            Value
---            -----
ciphertext     vault:v1:mMzuz5ZvqPmfupPUYplgKcwSWzu2XSpOoybXrgSqfYW9T3f8qBBH6XZbAW0=
key_version    1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We got back &lt;code&gt;ciphertext&lt;/code&gt; which is the encrypted value. The &lt;code&gt;ciphertext&lt;/code&gt; value starts with &lt;code&gt;vault&lt;/code&gt; which indicates that Vault has handled the encryption of this value. Following this is &lt;code&gt;v1&lt;/code&gt; which indicates that the first version of my key (I only have one version at this time) was used to encrypt the data.&lt;/p&gt;

&lt;p&gt;Once again, note that Vault has not stored this &lt;code&gt;ciphertext&lt;/code&gt; value. It is our application's responsibility to store this value wherever it needs to be stored.&lt;/p&gt;

&lt;p&gt;To get my plaintext data back I first decrypt the data using the &lt;code&gt;transit/decrypt/&lt;/code&gt; path for my key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vault write transit/decrypt/my-encryption-key &lt;span class="se"&gt;\ &lt;/span&gt;
&lt;span class="go"&gt;    ciphertext=vault:v1:mMzuz5ZvqPmfupPUYplgKcwSWzu2XSpOoybXrgSqfYW9T3f8qBBH6XZbAW0=

Key          Value
---          -----
plaintext    czNjcjN0IHA0c3N3MHJkCg==
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I decode the value from base64:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;&lt;span class="nv"&gt;czNjcjN0IHA0c3N3MHJkCg&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="go"&gt;
s3cr3t p4ssw0rd
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was a short look at the transit secrets engine and what it is mainly used for. We will revisit this topic in part 10 of this course: &lt;strong&gt;Explain encryption as a service&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>vault</category>
      <category>hashicorp</category>
      <category>security</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
