<?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: Denis Rendler</title>
    <description>The latest articles on DEV Community by Denis Rendler (@denisrendler).</description>
    <link>https://dev.to/denisrendler</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%2F42672%2Fd10ffc36-65af-4ec4-a8e8-761eda738175.jpg</url>
      <title>DEV Community: Denis Rendler</title>
      <link>https://dev.to/denisrendler</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/denisrendler"/>
    <language>en</language>
    <item>
      <title>OpenTofu's advanced features like workspaces, modules, and data transformations enable efficient infrastructure management through better organization and code reusability.</title>
      <dc:creator>Denis Rendler</dc:creator>
      <pubDate>Thu, 19 Dec 2024 12:30:17 +0000</pubDate>
      <link>https://dev.to/denisrendler/opentofus-advanced-features-like-workspaces-modules-and-data-transformations-enable-efficient-51lg</link>
      <guid>https://dev.to/denisrendler/opentofus-advanced-features-like-workspaces-modules-and-data-transformations-enable-efficient-51lg</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/denisrendler" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F42672%2Fd10ffc36-65af-4ec4-a8e8-761eda738175.jpg" alt="denisrendler"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/denisrendler/opentofu-infrastructure-configuration-management-34em" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;OpenTofu - Infrastructure configuration management&lt;/h2&gt;
      &lt;h3&gt;Denis Rendler ・ Dec 16 '24&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#terraform&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#opentofu&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#iac&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>terraform</category>
      <category>discuss</category>
    </item>
    <item>
      <title>OpenTofu - Infrastructure configuration management</title>
      <dc:creator>Denis Rendler</dc:creator>
      <pubDate>Mon, 16 Dec 2024 10:21:00 +0000</pubDate>
      <link>https://dev.to/denisrendler/opentofu-infrastructure-configuration-management-34em</link>
      <guid>https://dev.to/denisrendler/opentofu-infrastructure-configuration-management-34em</guid>
      <description>&lt;p&gt;OpenTofu is a great tool for managing infrastructure and in this notes series I am going to share a few tips I found useful as I find myself using it for more than just infrastructure management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workspace Management
&lt;/h2&gt;

&lt;p&gt;OpenTofu workspaces provide a mechanism for managing multiple environments of your infrastructure using the same OpenTofu code files.&lt;br&gt;
By default, OpenTofu operates in a '&lt;strong&gt;default&lt;/strong&gt;' workspace, but you can create additional workspaces to maintain separate states for different deployments.&lt;/p&gt;

&lt;p&gt;Think of it this way: when you write OpenTofu code to manage your resources, that code is like a blueprint.&lt;br&gt;
Workspaces allow you to use that same blueprint to manage multiple "instances" of your infrastructure, each with its own state file that tracks what's been managed.&lt;/p&gt;

&lt;p&gt;This is particularly valuable for scenarios like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;developing and testing new OpenTofu code in isolation and avoid disrupting production systems&lt;/li&gt;
&lt;li&gt;managing infrastructure in different regions just by using different variable files (ie. using different encryption keys)&lt;/li&gt;
&lt;li&gt;or to separate different projects that require deploying similar infrastructure (ie. deploying the same app for multiple clients)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OpenTofu's operations are marked in a &lt;code&gt;state&lt;/code&gt; file which it's like a ledger that keeps track of all resources OpenTofu manages.&lt;br&gt;
When you run OpenTofu commands, it first checks this state file to understand what exists and what needs to change.&lt;br&gt;
After making changes, OpenTofu updates the state file to reflect the new reality of the managed resources.&lt;/p&gt;

&lt;p&gt;While all this state management happens automatically in the default workspace, creating additional workspaces provides a way to maintain separate states for independent operations, all while using the same OpenTofu code. This approach provides isolation without requiring duplicated code or complicated workflows.&lt;/p&gt;

&lt;p&gt;Let's take as example using OpenTofu to manage a MikroTik RouterOS configuration.&lt;/p&gt;

&lt;p&gt;The resources file would look 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;# main.tf&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;########## configure VLANs&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"routeros_interface_vlan"&lt;/span&gt; &lt;span class="s2"&gt;"interface_vlan"&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;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vlans&lt;/span&gt;

  &lt;span class="nx"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&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;interface&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="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;name&lt;/span&gt;
  &lt;span class="nx"&gt;vlan_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&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;vlan_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;########## end configure VLANs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the code remais the same, the variables would be different between environments, like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for testing environment or for developing the OpenTofu code we would have this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# env/testing.tfvars&lt;/span&gt;
&lt;span class="nx"&gt;vlans&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"VNET_TEST_1"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ether1"&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;"VNET_TEST_1"&lt;/span&gt;
    &lt;span class="nx"&gt;vlan_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="s2"&gt;"VNET_TEST_2"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ether2"&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;"VNET_TEST_2"&lt;/span&gt;
    &lt;span class="nx"&gt;vlan_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&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;ul&gt;
&lt;li&gt;while for the production systems the variables would look similar to this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# env/production.tfvars&lt;/span&gt;
&lt;span class="nx"&gt;vlans&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"PRIVNET"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ether1"&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;"PRIVNET"&lt;/span&gt;
    &lt;span class="nx"&gt;vlan_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="s2"&gt;"DMZ"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ether2"&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;"DMZ"&lt;/span&gt;
    &lt;span class="nx"&gt;vlan_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&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;Creating the different workspaces requires using the &lt;code&gt;workspace&lt;/code&gt; comamnd:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tofu workspace new testing &lt;span class="c"&gt;# where testing is the workspace name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then simply issue an apply command using the &lt;code&gt;testing&lt;/code&gt; variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tofu plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;plan.out &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;/&lt;span class="si"&gt;$(&lt;/span&gt;tofu workspace show&lt;span class="si"&gt;)&lt;/span&gt;.tfvars
&lt;span class="nv"&gt;$ &lt;/span&gt;tofu apply plan.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the testing processes are successful the new RouterOS config can be easily applied by simply switching to the &lt;code&gt;production&lt;/code&gt; workspace and issueing the exact same commands.&lt;/p&gt;

&lt;p&gt;When using workspaces, it's important to parameterize your OpenTofu configuration to handle differences between environments.&lt;br&gt;
Use OpenTofu variables to define values that may change across workspaces. This allows you to maintain a single source of truth while still customizing each environment as needed.&lt;/p&gt;

&lt;p&gt;In this way you can safely develop and test your OpenTofu code in isolation and "promote" the code to production just by using a different variables file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Organize Your Modules Like a Pro
&lt;/h2&gt;

&lt;p&gt;As OpenTofu codebases grow, managing infrastructure code can become a challenge, especially when the same resource blocks are used across multiple files and projects.&lt;/p&gt;

&lt;p&gt;This is where OpenTofu modules come in handy – they're reusable building blocks that encapsulate groups of resource blocks dedicated to specific tasks, making the infrastructure code easier to maintain and consistent.&lt;/p&gt;

&lt;p&gt;When organizing modules, it's helpful to group them based on functionality or architectural components – for example, separate modules for networking, compute, and storage components.&lt;/p&gt;

&lt;p&gt;This modular approach will reduce code duplication while making the OpenTofu code more readable and maintainable.&lt;/p&gt;
&lt;h3&gt;
  
  
  Root and child modules
&lt;/h3&gt;

&lt;p&gt;An OpenTofu module is basically a directory containing OpenTofu files. Interestingly, every OpenTofu project is itself a module - which is called the "&lt;strong&gt;root module&lt;/strong&gt;". This root module includes all resources and variables defined in the .tf and .tfvars files of the main OpenTofu code.&lt;/p&gt;

&lt;p&gt;For OpenTofu, the concept of modules is recursive - meaning that any module can call other modules which are known as "child modules" for the root module. These child modules can even be reused multiple times within the same module code. Think of it like nesting building blocks, where each block (module) can contain other blocks to complete a task.&lt;/p&gt;

&lt;p&gt;When using a module block in the OpenTofu code to reference another module, OpenTofu will include all the resources defined in that module as part of the current OpenTofu code.&lt;/p&gt;

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

&lt;p&gt;Using this approach the code can be organized hierarchically and efficiently while drastically lowering the number of bugs and improving code quality and reusability.&lt;/p&gt;
&lt;h3&gt;
  
  
  Module sharing
&lt;/h3&gt;

&lt;p&gt;There are several ways to share modules, the more common ones being:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local paths - where the modules are stored in a folder inside the main OpenTofu project&lt;/li&gt;
&lt;li&gt;module registry - a remote registry for distributing modules as well as providers&lt;/li&gt;
&lt;li&gt;Git - any accessible Git repository can be used as a module (ie. Github, Gitlab, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Building a module
&lt;/h3&gt;

&lt;p&gt;To create a module start by creating a new folder and add the following files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;netbox-customization
├── LICENSE.md
├── README.md
├── example
│   └── data.tfvars
├── main.tf
├── outputs.tf
└── variables.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are no constraints on the file names within an OpenTofu module. You can use any file name with a &lt;code&gt;.tf&lt;/code&gt; or &lt;code&gt;.tfvars&lt;/code&gt; extension, and OpenTofu will process all files in a directory, irrespective of their names. The order in which files are named doesn't matter either since OpenTofu loads all files and combines them into a single configuration.&lt;/p&gt;

&lt;p&gt;However, there are common conventions that I recommend to be followed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;main.tf - primary configuration file&lt;/li&gt;
&lt;li&gt;variables.tf - variable declarations&lt;/li&gt;
&lt;li&gt;outputs.tf - output declarations&lt;/li&gt;
&lt;li&gt;LICENSE.md and README.md - license and readme documentation&lt;/li&gt;
&lt;li&gt;example/ - folder containing example of usages and variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, there are still a few important points to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;files must have the .tf extension (or .tf.json for JSON format)&lt;/li&gt;
&lt;li&gt;override files must end in _override.tf or _override.tf.json&lt;/li&gt;
&lt;li&gt;files beginning with . or _ are ignored&lt;/li&gt;
&lt;li&gt;variables file have the .tfvars extension (or .tfvars.json for JSON format)&lt;/li&gt;
&lt;li&gt;files ending in .auto.tfvars or .auto.tfvars.json are automatically loaded by OpenTofu to populate variable values without explicitly specifying variable files on the command line&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While it's not mandatory, using consistent file names across modules helps fellow colleagues understand the code faster.&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="err"&gt;...&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"netbox_custom_field_choice_set"&lt;/span&gt; &lt;span class="s2"&gt;"custom_field_choices"&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;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_field_choices&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="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;name&lt;/span&gt;
  &lt;span class="nx"&gt;extra_choices&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="nx"&gt;custom_field_choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;each&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;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;order_alphabetically&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;each&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;order_alphabetically&lt;/span&gt;&lt;span class="p"&gt;,&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;span class="c1"&gt;# variables.tf&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"custom_field_choices"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Map of custom field choice sets"&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;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&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;string&lt;/span&gt;
    &lt;span class="nx"&gt;choices&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&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;description&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;optional&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;order_alphabetically&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="nx"&gt;default&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;Using the module&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;as a local module:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# environments/dev/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&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/netbox-customization"&lt;/span&gt;

  &lt;span class="nx"&gt;custom_field_choices&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;customizations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_field_choices&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;ul&gt;
&lt;li&gt;as a Git stored module:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# environments/dev/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&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;"github.com/rendler-denis/tf-mod-netbox//netbox-customization"&lt;/span&gt;

  &lt;span class="nx"&gt;custom_field_choices&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;customizations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_field_choices&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;Notice the double slashes in the path - that is not a typo. It is needed when the module is residing in a subfolder inside a Git repository.&lt;/p&gt;

&lt;p&gt;Now, to download and prepare the module inside the workspace run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tofu init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dynamic Configuration with Data Sources
&lt;/h2&gt;

&lt;p&gt;Data sources in OpenTofu allows querying and fetching information from existing infrastructure or external services, making this data available for use in the OpenTofu code during runtime.&lt;/p&gt;

&lt;p&gt;Unlike resources that create and manage infrastructure, data sources are read-only and provide a way to reference existing resources, whether they're managed by OpenTofu or not. For example, OpenTofu can use data sources to look up an existing VPC, query an IPAM system for the first available IP etc.&lt;/p&gt;

&lt;p&gt;The key advantage of data sources is that they enable dynamic and flexible infrastructure configurations. Instead of hardcoding values that might change over time (like subnet IDs), data sources can be used to automatically fetch the most current information. This approach reduces maintenance overhead, prevents errors from outdated values, and allows OpenTofu code to adapt to changes in the infrastructure automatically.&lt;/p&gt;

&lt;p&gt;Let's look at an example:&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;# Query existing VPC&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"existing"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;OpenTofu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Query available AZs&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_availability_zones"&lt;/span&gt; &lt;span class="s2"&gt;"available"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"available"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create subnets dynamically&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"private"&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="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;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&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;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&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;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&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;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone&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;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;names&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;index&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="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;"private-subnet-${count.index + 1}"&lt;/span&gt;
    &lt;span class="nx"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;=&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;h2&gt;
  
  
  Efficient Data Handling with Data-Only Modules
&lt;/h2&gt;

&lt;p&gt;Data-only modules in OpenTofu are specialized modules that focus solely on computing and providing data without creating any actual infrastructure resources. They serve as a way to encapsulate complex data transformations, lookups, or business logic that can be reused across different parts of OpenTofu project code.&lt;/p&gt;

&lt;p&gt;Using this concept, modules that manage infrastructure can remain single purpose, decupled and overall easier to maintain and share. It also reduces the execution times because calculations or data retrieval is done only once and the data can be reused by all code being executed during runtime.&lt;/p&gt;

&lt;p&gt;Let's quickly look at an example data-only module from my Netbox modules.&lt;/p&gt;

&lt;p&gt;The definition for the data-only module remains the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the folder structure
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;netbox-data-org
├── LICENSE.md
├── README.md
├── main.tf
├── outputs.tf
└── variables.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;the module code
&lt;/li&gt;
&lt;/ul&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;data&lt;/span&gt; &lt;span class="s2"&gt;"netbox_site"&lt;/span&gt; &lt;span class="s2"&gt;"sites"&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="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;sites&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;each&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;data&lt;/span&gt; &lt;span class="s2"&gt;"netbox_tenant"&lt;/span&gt; &lt;span class="s2"&gt;"tenants"&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="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;tenants&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;each&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="err"&gt;...&lt;/span&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;"sites"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"List of site names to retrieve from Netbox"&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="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;default&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;"tenants"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"List of tenant names to retrieve from Netbox"&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="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;default&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="err"&gt;...&lt;/span&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;"sites_map"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Map of site names to their IDs"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for&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;site&lt;/span&gt; &lt;span class="nx"&gt;in&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;netbox_site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sites&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;site&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="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"tenants_map"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Map of tenant names to their IDs"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for&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;tenant&lt;/span&gt; &lt;span class="nx"&gt;in&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;netbox_tenant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenants&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;tenant&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="err"&gt;....&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;using the data-only module in a &lt;em&gt;root&lt;/em&gt; module
&lt;/li&gt;
&lt;/ul&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 - the main OpenTofu project file (ie. the root module)&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"netbox-org-data"&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;"./lib/netbox/netbox-data-org"&lt;/span&gt;

  &lt;span class="nx"&gt;sites&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;sites&lt;/span&gt;
  &lt;span class="nx"&gt;tenants&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;tenants&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# using the fetched data&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"netbox-racks"&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;"./lib/netbox/netbox-racks"&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;netbox-org&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;netbox-org-data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="p"&gt;...&lt;/span&gt;

  &lt;span class="nx"&gt;site_id_map&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;netbox-org-data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sites_map&lt;/span&gt;
  &lt;span class="nx"&gt;tenant_id_map&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;netbox-org-data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenants_map&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# using the same data in other modules&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"netbox-devices"&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;"./lib/netbox/netbox-devices"&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;netbox-org&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;netbox-org-data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="p"&gt;...&lt;/span&gt;

  &lt;span class="nx"&gt;site_id_map&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;netbox-org-data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sites_map&lt;/span&gt;
  &lt;span class="nx"&gt;tenant_id_map&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;netbox-org-data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenants_map&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To access data from a data-only module, simply reference its outputs using the standard module output syntax: &lt;code&gt;module.[module_name].[output_name]&lt;/code&gt;. This follows the same pattern you'd use to reference outputs from any other type of module.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;depends_on&lt;/code&gt; is not really necessary, but it does make the dependency explicit for other users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The combination of OpenTofu's workspaces, modules, and data-only modules creates a robust foundation for maintainable, scalable, and efficient infrastructure code. Workspaces provide the necessary isolation for testing and development, modules reduce code duplication and improve reusability, and data-only modules offer a clean approach to handling complex data operations.&lt;/p&gt;

&lt;p&gt;While these concepts can introduce additional complexity, mastering them will help create more robust infrastructure-as-code. These tools represent essential components in the modern infrastructure management toolkit, whether applied to small deployments or large-scale infrastructure projects.&lt;/p&gt;

&lt;p&gt;P.S.: The concepts discussed in this note are also applicable to any Terraform code. For brevity, I only mentioned OpenTofu, but feel free to apply these principles to your Terraform projects as well.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>opentofu</category>
      <category>iac</category>
      <category>devops</category>
    </item>
    <item>
      <title>Keeping secrets from your pipelines</title>
      <dc:creator>Denis Rendler</dc:creator>
      <pubDate>Thu, 21 Mar 2024 16:15:54 +0000</pubDate>
      <link>https://dev.to/denisrendler/keeping-secrets-from-your-pipelines-h5f</link>
      <guid>https://dev.to/denisrendler/keeping-secrets-from-your-pipelines-h5f</guid>
      <description>&lt;p&gt;In this article, I invite you to explore a solution I developed that leverages the &lt;code&gt;Keeper Secrets Manager&lt;/code&gt; SDK to provide a REST API for retrieving credentials from the &lt;code&gt;Keeper Enterprise Password Manager&lt;/code&gt; platform.&lt;/p&gt;

&lt;p&gt;In a Continuous Integration/Continuous Deployment (CI/CD) environment, the automation of building, testing, and deployment of applications requires access to various secrets, such as API keys, database passwords, and credentials for third-party services. And although CI/CD platforms offer some sort of secrets management functionality, maintaining these secrets is still a pain point for teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leveraging the Keeper Secrets Manager SDK
&lt;/h3&gt;

&lt;p&gt;One of the problems that I faced using &lt;code&gt;Keeper Secrets Manager&lt;/code&gt;, the CI/CD app for &lt;code&gt;Keeper Enterprise Password Manager&lt;/code&gt;, is that I needed to install it on each run of the pipelines. Further complicating things, when using Docker agents, which resets the build environment even between stages, the installation process can be cumbersome because images were to minimalistic and lacked dependencies which I had to track down before being able to install the app itself. I ended up creating custom base images for the Gitlab runners, but when colleagues needed to use their own images they were forced to revert to the install process and we ended up in the same dependency hell.&lt;/p&gt;

&lt;p&gt;Luckily, the &lt;code&gt;Keeper&lt;/code&gt; team provides several SDKs, one of which is for Go. As such, I came up with the idea of building a Go app that would expose a REST endpoint, and using the &lt;code&gt;Keeper Secrets Manager&lt;/code&gt; SDK I could connect to the &lt;code&gt;Keeper Enterprise Password Manager&lt;/code&gt; to retrieve the secrets. &lt;br&gt;
The idea opened up several usage scenarios, one of which is adding the app as a service container to the Gitlab runners that will be available to be queried without the need to install anything else. Just provide it the &lt;code&gt;KSM_CONFIG&lt;/code&gt; data, either as an environment variable in the container or pass it through a request header, and it will connect to the correct &lt;code&gt;Keeper Enterprise Password Manager&lt;/code&gt; vault.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Unfortunately, at the time of writing, &lt;code&gt;Keeper Secrets Manager&lt;/code&gt; is available only for Business accounts. &lt;br&gt;
To configure a &lt;code&gt;Keeper Secrets Manager&lt;/code&gt; application to access your &lt;code&gt;Keeper Enterprise Password Manager&lt;/code&gt; secrets, you can follow the excellent tutorial on the Keeper's website: &lt;a href="https://docs.keeper.io/secrets-manager/secrets-manager/quick-start-guide" rel="noopener noreferrer"&gt;https://docs.keeper.io/secrets-manager/secrets-manager/quick-start-guide&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  KSM-Api wrapper
&lt;/h3&gt;

&lt;p&gt;To begin using my project, the first step is cloning the Github repository onto your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Hexagonal-Software/ksm-api.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the project is developed in Go, you'll need to download its dependencies after cloning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;ksm-api
go mod download
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have the option to run the application by building it as an executable or packaging it in a Docker image. I am using it as a standalone daemon on servers where I ran build pipelines directly on the machine, and use a Docker image where the build environment uses Docker agents. In a later section I will provide a configuration for running it as a service container for Gitlab runners.&lt;/p&gt;

&lt;p&gt;The project includes multiple Make commands to streamline the build process.&lt;/p&gt;

&lt;p&gt;For creating a Docker image with the &lt;code&gt;KSM-Api&lt;/code&gt; executable use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make release-docker &lt;span class="nv"&gt;OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--platform=linux/amd64"&lt;/span&gt; &lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"denisrendler/kms-api"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the &lt;code&gt;release-docker&lt;/code&gt; command completes you can find the image in your local Docker instance. Note that the image will be tagged with the latest code tag retrieve from the Git repository, which is 0.1.0 at the time of this writing. This is added automatically by the &lt;code&gt;make&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;To run the app in a Docker container, you must configure the &lt;code&gt;KSM_CONFIG&lt;/code&gt; data as an environment variable when creating the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;KSM_CONF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"W2FkZCB5b3UgYmFzZTY0IGVuY29kZWQgS2VlcGVyIGFwcGxpY2F0aW9uIGNvbmZpZyBkYXRhIGhlcmVdCg=="&lt;/span&gt;

docker run &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ksm-api &lt;span class="nt"&gt;-ti&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8086:8086 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"KSM_CONFIG=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KSM_CONF&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; denisrendler/ksm-api:0.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the provided &lt;code&gt;KSM_CONFIG&lt;/code&gt; data, you can access any secrets managed by the &lt;code&gt;Keeper Secrets Manager&lt;/code&gt; using a simple &lt;code&gt;curl&lt;/code&gt; request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--globoff&lt;/span&gt; &lt;span class="s1"&gt;'ksm-api:8086/api/v1/secret/&amp;lt;record UID or name&amp;gt;/&amp;lt;field OR custom_filed&amp;gt;/&amp;lt;field name&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, you can pass the &lt;code&gt;KSM_CONFIG&lt;/code&gt; data as a request header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;KSM_DATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"W2FkZCB5b3UgYmFzZTY0IGVuY29kZWQgS2VlcGVyIGFwcGxpY2F0aW9uIGNvbmZpZyBkYXRhIGhlcmVdCg=="&lt;/span&gt;

curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"KSM_CONFIG: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KSM_DATA&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--globoff&lt;/span&gt; &lt;span class="s1"&gt;'ksm-api:8086/api/v1/secret/&amp;lt;record UID or name&amp;gt;/&amp;lt;field OR custom_filed&amp;gt;/&amp;lt;field name&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The path parameters mirror those used by the &lt;code&gt;Keeper Secrets Manager&lt;/code&gt; app's &lt;code&gt;secret notation&lt;/code&gt; subcommand, as described in the following table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;record UID or name&lt;/td&gt;
&lt;td&gt;The UID or the name of the secret. I recommend using the name because it makes it easier when switching between environments.&lt;/td&gt;
&lt;td&gt;oxhtLx9qrQIzeSXBtvQj2Q  &lt;br&gt;my_keeper_secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;field or custom_field&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;field&lt;/code&gt; if the data you want to retrieve is part of the secret's type, ie. password when it is a login or host[hostName] when it is a server type.&lt;br&gt;Use &lt;code&gt;custom_field&lt;/code&gt; when the data is stored inside a field type that is not part of the secret's template, ie. the field was added using the &lt;code&gt;Add Custom Field&lt;/code&gt; button.&lt;/td&gt;
&lt;td&gt;field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;field name&lt;/td&gt;
&lt;td&gt;The field name you want to retrieve.&lt;/td&gt;
&lt;td&gt;password&lt;br&gt;&lt;br&gt;host[hostName]&lt;br&gt;&lt;br&gt;host[port]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Configure KSM-Api as a Gitlab service container
&lt;/h3&gt;

&lt;p&gt;The easiest way to have the service container always available is to edit your runner configuration and add the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[runners.docker.services]]&lt;/span&gt;
   &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"denisrendler/kms-api:0.1.0"&lt;/span&gt;
   &lt;span class="py"&gt;alias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ksm-api"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, define a &lt;code&gt;KSM_CONFIG&lt;/code&gt; environment variable and add your base64 config data. Make sure to uncheck the &lt;code&gt;Protect variable&lt;/code&gt; checkbox to make the variable available to all pipelines.&lt;br&gt;
By default, Gitlab will not automatically pass user defined environment variables to service containers. To pass your &lt;code&gt;KSM_CONFIG&lt;/code&gt; data from the CI/CD variable, you must explicitly define it in your &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;:&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;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;KSM_CONFIG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${KSM_CONFIG}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it! Whenever you need to retrieve a secret you can use a &lt;code&gt;curl&lt;/code&gt; request to get it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design decisions
&lt;/h3&gt;

&lt;p&gt;There are a couple of design decisions that you need to be aware of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the application does not handle TLS connections since it is build to be run in a limited environment on a local system or containerised environment. This way the app avoids issues with requests not handling self-signed certificates.&lt;/li&gt;
&lt;li&gt;error messages are only available in the logs as a precaution to avoid dumping sensitive information in CI/CD logs.&lt;/li&gt;
&lt;li&gt;since this is an external application, CI/CD platforms might not obfuscate or otherwise hide sensitive information if dumped into the logs. Also, you will need to take care of workspace cleanup after running the pipeline if you dump to disk information like SSH keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you find a use case where these decisions might have a bigger impact, open an issue on the Github repository and we can take it into account to add new functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contributing
&lt;/h3&gt;

&lt;p&gt;The project started from an idea I had to resolve a pain point I encountered. But, if you feel that further functionality can bring more value to the app I am open to suggestions and even code contributions.&lt;br&gt;
Just open a new issue or pull request on the Github repository and let's discuss it.&lt;br&gt;
Bug reports are also welcomed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping up...
&lt;/h3&gt;

&lt;p&gt;While &lt;code&gt;Keeper Enterprise Password Manager&lt;/code&gt; is a great password manager with an excellent zero-knowledge architecture, for me one of the more tedious aspects of managing CI/CD pipelines has been to maintain the &lt;code&gt;Keeper Secrets Manager&lt;/code&gt; (KSM) app installation, a process that not only introduced potential points of failure but it has also deterred adoption for many of my colleagues. &lt;br&gt;
My project addresses this pain point by integrating seamlessly with &lt;code&gt;Keeper Enterprise Password Manager&lt;/code&gt; through the &lt;code&gt;Keeper Secrets Manager SDK&lt;/code&gt;, and using it as a service container it eliminates the need for repetitive installations. It offers a streamlined, secure way to manage and access secrets across the pipelines, significantly reducing build times and enhancing the security and reliability of CI/CD processes. This solution not only simplifies the developer's workflow but also ensures that your secrets are always managed efficiently and securely.&lt;/p&gt;

</description>
      <category>keeper</category>
      <category>secrets</category>
      <category>passwordmanager</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Designing Security Workflows using Gitlab CI Templates</title>
      <dc:creator>Denis Rendler</dc:creator>
      <pubDate>Wed, 21 Feb 2024 09:32:47 +0000</pubDate>
      <link>https://dev.to/denisrendler/designing-security-workflows-using-gitlab-ci-templates-ph0</link>
      <guid>https://dev.to/denisrendler/designing-security-workflows-using-gitlab-ci-templates-ph0</guid>
      <description>&lt;p&gt;The modern software development lifecycle is comprised of multiple stages, among them are installing of dependencies, building applications from source code, and running testing tools on the application - steps that can be found even in the smallest web applications. But repeating these steps after every commit can quickly become tedious and cumbersome for teams. &lt;/p&gt;

&lt;p&gt;That is why I believe that concepts like Continuous Integration (CI) and Continuous Delivery (CD) are fundamental practices in modern software development and aim to streamline and automate the process of delivering code changes. &lt;br&gt;
The CI process involves the automatic integration of code from multiple contributors and typically includes the steps for automating builds and running tests to ensure that new code changes do not break or disrupt the existing codebase. Continuous Delivery extends this automation to ensure that the code changes are automatically prepared and ready to be deployed to a production environment. This means that at any given time, the software is in a deployable state, allowing teams to release new changes to customers quickly and safely. Together, CI/CD enables more efficient and reliable software development cycles, promoting rapid releases while maintaining high quality and stability. &lt;/p&gt;

&lt;p&gt;In recent years, as the focus on cybersecurity has grown, a new approach known as DevSecOps has gained momentum. This methodology builds on the CI/CD pipelines workflows by incorporating security tools that conduct Static Application Security Testing (SAST) and Dynamic Application Security Testing (DAST). This integration offers rapid feedback, enabling teams to identify and address known vulnerabilities before they are merged into the codebase.&lt;/p&gt;
&lt;h3&gt;
  
  
  Overview of GitLab CI/CD
&lt;/h3&gt;

&lt;p&gt;GitLab, an open-source DevOps platform, has introduced the DevOps practices through a human-friendly language known as YAML. By utilizing predefined and intuitive tags, we can design complex workflows for our projects. However, I've often discover that as a project grows, so does the complexity of its GitLab pipeline YAML file. Managing a single file becomes challenging, and modifications frequently resulted in conflicts. &lt;/p&gt;

&lt;p&gt;To tackle this issue, I leveraged the capabilities of the YAML language, such as aliases and anchors, to reduce code redundancy. Additionally, I've sometimes moved instructions from GitLab's CI/CD configuration file to scripted commands or created custom Docker images which contained the necessary tools allowing me to reduce often repeatable tasks. One other effective method was to create Gitlab reusable pipeline templates and afterwards just include them in my projects. &lt;/p&gt;
&lt;h3&gt;
  
  
  Creating Pipeline Templates for Security Tools
&lt;/h3&gt;

&lt;p&gt;All the code I am using in this guide implementation can be found in my public repository: &lt;a href="https://github.com/httpsec-eu/gitlab-security-templates" rel="noopener noreferrer"&gt;https://github.com/httpsec-eu/gitlab-security-templates&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Pre-requisites:
&lt;/h4&gt;

&lt;p&gt;To perform the steps describe in the guide you will need access to a Gitlab instance to create two repositories: one repository will be used for authoring the templates and the second repository will be used to showcase the usage of the templates. If you don't have an on-premise Gitlab instance you can easily create a free account on Gitlab.com at: &lt;a href="https://gitlab.com/users/sign_in" rel="noopener noreferrer"&gt;https://gitlab.com/users/sign_in&lt;/a&gt;&lt;br&gt;
Additionally, you will need a Docker runner registered to the repository with the code in order to run the demo pipeline.&lt;/p&gt;
&lt;h4&gt;
  
  
  Configuring the templates repository
&lt;/h4&gt;

&lt;p&gt;A GitLab template is a YAML document that may define an entire pipeline or specific tasks to be integrated into additional pipelines, with the distinction largely lying in the content of the template itself. In this guide, I'll demonstrate how to create a pipeline designed to be added to existing pipelines, introducing a security tool named Syft which generates a project's components list, also known as an SBOM.&lt;/p&gt;

&lt;p&gt;When setting up a template repository, I often use the following organizational structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── docs
│   └── sbom.md
├── templates
│   └── sbom.yml
├── LICENSE.md
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This format helps me keep the documentation and any relevant information quickly available which improves the usability and user experience.&lt;/p&gt;

&lt;p&gt;After creating the folder structure, let's add the job code. Below is the code I am using for this guide - which can also be found in &lt;code&gt;templates-repo/template/sbom.yml&lt;/code&gt; folder in the repo mentioned previously.&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="c1"&gt;# templates/sbom.yml&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-dependency-sbom&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The stage where the job will run&lt;/span&gt;
    &lt;span class="na"&gt;docker-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;denis.rendler/dependency-scan:1.0&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The Docker image used to run the job&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The path, file or docker image to scan&lt;/span&gt;
    &lt;span class="na"&gt;report-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cyclonedx-json&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The type of report to build&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cyclonedx-json&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;spdx-json&lt;/span&gt;
    &lt;span class="na"&gt;report-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dependency-sbom.json&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The file name for the report&lt;/span&gt;
    &lt;span class="na"&gt;report-expire&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&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&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;year"&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The amount of time Gitlab should keep the report&lt;/span&gt;
      &lt;span class="na"&gt;options&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;30&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;days"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;90&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;days"&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&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;year"&lt;/span&gt;

&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$[[&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;inputs.report-type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;]]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;SBOM:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$[[&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;inputs.path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;]]"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$[[ inputs.stage ]]&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$[[ inputs.docker-image ]]&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;syft scan $[[ inputs.path ]] -o $[[ inputs.report-type]]=$[[ inputs.report-file ]]&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;infosec&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == "merge_request_event"&lt;/span&gt;  &lt;span class="c1"&gt;# Add the job to merge request pipelines if there's an open merge request.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_OPEN_MERGE_REQUESTS&lt;/span&gt;                       &lt;span class="c1"&gt;# Don't add it to a *branch* pipeline if it's already in a merge request pipeline.&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;never&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;GIT_DEPTH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;50"&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sbom-$[[&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;inputs.report-type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;]]-$CI_COMMIT_SHORT_SHA"&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$[[ inputs.report-file ]]&lt;/span&gt;
    &lt;span class="na"&gt;expire_in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$[[ inputs.report-expire ]]&lt;/span&gt;
    &lt;span class="na"&gt;reports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;dependency_scanning&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$[[ inputs.report-file ]]&lt;/span&gt;
  &lt;span class="na"&gt;allow_failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given that the template is designed for use across various projects, it's essential to allow for the modification of certain job values to facilitate an easy integration into existing pipelines. Thus, I am making use of Gitlab's &lt;code&gt;spec:inputs&lt;/code&gt; feature to configure the behavior of the job when it is added to the pipeline. We can also configure default values that help incorporate the job immediately without additional configuration.&lt;br&gt;
While I could also use environment variables, I found that the side effects can lead to a lot of confusion and increase in debug time. For example, if I were to set the &lt;code&gt;path&lt;/code&gt; input as an environment variable, I would have to rename it to avoid overriding the value of the Linux environment variable called &lt;code&gt;PATH&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;After adding the template, and documenting its use cases and usage using a Markdown file in the &lt;code&gt;docs/&lt;/code&gt; folder, I like to create a new Git tag. Using Git tags I can make changes to the template without fear that I will break my colleague's pipelines, while at the same time providing them with the option to update at their own pace.&lt;br&gt;
Based on my experience, this workflow significantly increases the adoption of security tools, since teams do not fear that their release processes will be affected. This is particularly due to the fast feedback loop on known vulnerabilities, allowing teams to address these issues prior to the code being merged.&lt;/p&gt;
&lt;h4&gt;
  
  
  Using the templates
&lt;/h4&gt;

&lt;p&gt;To use the template, GitLab provides a YAML keyword: &lt;code&gt;include&lt;/code&gt; - which merges external files with the current pipeline YAML. &lt;br&gt;
In the second repository, create a file named &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; and position it in the root directory.&lt;br&gt;
Add the code provided below at the top of the file and update the &lt;code&gt;stage&lt;/code&gt; input with the name of the stage where you want the job to run:&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;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;denis.rendler/ci-templates'&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/templates/sbom.yml'&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;
      &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;testing&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The required keywords are: &lt;code&gt;project&lt;/code&gt;, &lt;code&gt;file&lt;/code&gt; and &lt;code&gt;ref&lt;/code&gt;.&lt;br&gt;
The &lt;code&gt;project&lt;/code&gt; keyword specifies the project from which the template is imported. The &lt;code&gt;file&lt;/code&gt; keyword determines the import's path and file, which is relative to the repository. And the &lt;code&gt;ref&lt;/code&gt; keyword directs GitLab to utilize the tag &lt;code&gt;0.1&lt;/code&gt;. &lt;br&gt;
Alternatively, a branch name or commit SHA can be used, though this approach is generally recommended for development phases or when testing the template.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final thoughts
&lt;/h3&gt;

&lt;p&gt;Building custom Gitlab's CI/CD templates along with custom Docker images we can easily design and implement cybersecurity workflows that can be reused to secure many company projects. &lt;br&gt;
Leveraging a SemVer versioning system the templates we design can be integrated into the projects at their own pace, or updated to new versions without fear of breaking project pipelines. &lt;br&gt;
Taking advantage of the smaller Docker images and securing access only to private repositories, topics I discussed in my previous articles, I believe we can achieve a more robust infrastructure against supply-chain attacks.&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>devsecops</category>
      <category>cybersecurity</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Hardening Docker with OPA and Harbor</title>
      <dc:creator>Denis Rendler</dc:creator>
      <pubDate>Sat, 03 Feb 2024 15:53:19 +0000</pubDate>
      <link>https://dev.to/denisrendler/hardening-docker-with-opa-and-harbor-12kl</link>
      <guid>https://dev.to/denisrendler/hardening-docker-with-opa-and-harbor-12kl</guid>
      <description>&lt;p&gt;In my article &lt;a href="https://www.linkedin.com/pulse/docker-chronicles-securing-instances-opa-harbor-rendler-661if" rel="noopener noreferrer"&gt;Docker Chronicles - Securing Docker instances with OPA and Harbor&lt;/a&gt; I am discussing the benefits that Open-Policy-Agent (OPA) and Harbor bring to securing Docker instances.&lt;/p&gt;

&lt;p&gt;This post aims to guide you through the technical steps that I implemented for leveraging OPA and Harbor to not only secure your Docker containers but also to streamline the management of the policies. With OPA's powerful policy-as-code capabilities and Harbor's efficient image management system, you'll discover how to build a Docker ecosystem that's robust, secure, and compliant with industry best practices.&lt;/p&gt;

&lt;p&gt;Let's begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;The code repository we will be using can be found here: &lt;a href="https://github.com/httpsec-eu/opa-article" rel="noopener noreferrer"&gt;https://github.com/httpsec-eu/opa-article&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First we need to create the Gitlab repository for our policies. I named my repository &lt;code&gt;OPA policies&lt;/code&gt;.  If you want to use the bash commands you should change the environment vars to your own values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gitlab.example.com      
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gl-123456789ABCDEFG

curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"PRIVATE-TOKEN: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AUTH_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
        "name": "opa-policies", "description": "OPA Policies", "path": "opa-policies",
        "namespace_id": "1", "initialize_with_readme": "true"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/v4/projects/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we created the Gitlab project to host our policies, you can go ahead an copy the &lt;code&gt;/docker&lt;/code&gt; folder from my repository. Along with the folder you can copy the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; which provides you with the pipeline to create the OPA bundle and push it to Harbor.&lt;/p&gt;

&lt;p&gt;The pipeline uses several variables described below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable name&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Default value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BUNDLE&lt;/td&gt;
&lt;td&gt;the name of the bundle which will be used as a Harbor artefact name&lt;/td&gt;
&lt;td&gt;bundle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REPO&lt;/td&gt;
&lt;td&gt;the folder name to build&lt;/td&gt;
&lt;td&gt;./docker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TAG&lt;/td&gt;
&lt;td&gt;the tag to set for the bundle when pushed to the repository&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IMG_REPO&lt;/td&gt;
&lt;td&gt;the repository URL domain&lt;/td&gt;
&lt;td&gt;harbor.httpsec.eu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REPO_USER&lt;/td&gt;
&lt;td&gt;the robot account to be used to login to Harbor&lt;/td&gt;
&lt;td&gt;no value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REPO_PASS&lt;/td&gt;
&lt;td&gt;the robot account password&lt;/td&gt;
&lt;td&gt;no value&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Next we need to create the Harbor repository. I named mine &lt;code&gt;docker-opa&lt;/code&gt;. If you want, I provided a quick &lt;code&gt;curl&lt;/code&gt; call to create and setup the project - please change the env vars to your own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;harbor.example.com
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUTH&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;BASIC_AUTH_TOKEN_FOR_REOBOT_ACCOUNT_HERE]

curl &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="s1"&gt;'POST'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/v2.0/projects"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"authorization: Basic &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AUTH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "project_name": "docker-opa",
  "public": false,
  "metadata": {
    "enable_content_trust": "true",
    "enable_content_trust_cosign": "true"
  }
}'&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Once the repository is created we need to create a Harbor robot account which we will use to login to Harbor from our Gitlab instance. You can set the variables &lt;code&gt;REPO_USER&lt;/code&gt; and &lt;code&gt;REPO_PASS&lt;/code&gt; as environment variables in the Gitlab project so that you don't have to enter them manually each time you run the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the first bundle
&lt;/h2&gt;

&lt;p&gt;Now that we created the two repositories we need to build our first OPA bundle. Go to your Gitlab project and select &lt;code&gt;Build &amp;gt; Pipelines&lt;/code&gt; and click on &lt;code&gt;Run pipeline&lt;/code&gt;. Fill in the variables or use the default values and select &lt;code&gt;Run&lt;/code&gt;. Once the pipeline finishes you should see a new entry in the Harbor project similar to figure below.&lt;/p&gt;

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

&lt;p&gt;NOTE:  in the screenshot the name of the bundle is &lt;code&gt;docker_auth&lt;/code&gt; because I set the Gitlab pipeline variable &lt;code&gt;BUNDLE&lt;/code&gt; to the same name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Docker
&lt;/h3&gt;

&lt;p&gt;The next steps are to install the Docker plugin and configure it to pull the policies from our Harbor instance.&lt;/p&gt;

&lt;p&gt;Before we install the plugin we need to set the configuration file. The &lt;code&gt;/etc/docker&lt;/code&gt; directory will be mounted as &lt;code&gt;/opa&lt;/code&gt; in the container running the plugin, so let’s create a sub-directory for our configuration file there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/docker/opa-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now copy or move the &lt;code&gt;config.yaml&lt;/code&gt; file from my repository to the &lt;code&gt;/etc/docker/opa-confg/&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;To install the plugin run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker plugin &lt;span class="nb"&gt;install &lt;/span&gt;openpolicyagent/opa-docker-authz-v2:0.9 opa-args&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-config-file /opa/opa-config/config.yaml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To validate that our plugin has been installed correctly, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker plugin &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the everything works you should see an output similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ID             NAME                                      DESCRIPTION                                     ENABLED
d6cee85ae9aa   openpolicyagent/opa-docker-authz-v2:0.9   A policy-enabled authorization plugin for Do…   true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last step is to configure Docker to use plugin. For this we need to edit the file, or create it if it doesn't exist,  &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; and add the following line:&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="nl"&gt;"authorization-plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"openpolicyagent/opa-docker-authz-v2:0.9"&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;Run the following command to restart the Docker daemon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To validate that everything is working run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker image pull nginx:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you should now receive an error similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker image pull nginx:latest
Error response from daemon: authorization denied by plugin openpolicyagent/opa-docker-authz-v2:0.9: request rejected by administrative policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your Docker instance is hardened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;As we wrap up, hardening Docker environments by integrating Open Policy Agent (OPA) and Harbor represents a good starting step in securing containerised applications. Through this tutorial, we've seen how OPA's policy-as-code approach, combined with Harbor's robust image management capabilities, can create a more secure and manageable Docker ecosystem. &lt;/p&gt;

&lt;p&gt;By implementing these tools, OPS and security teams can enforce consistent security policies and practices, reducing the risk of vulnerabilities and enhancing the overall security posture of their applications.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>opa</category>
      <category>openpolicyagent</category>
      <category>containersecurity</category>
    </item>
  </channel>
</rss>
