<?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: Jesse P. Johnson</title>
    <description>The latest articles on DEV Community by Jesse P. Johnson (@kuwv).</description>
    <link>https://dev.to/kuwv</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%2F282956%2F95e76d76-65b5-4fa9-bfe4-c1eaf92268c2.jpg</url>
      <title>DEV Community: Jesse P. Johnson</title>
      <link>https://dev.to/kuwv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kuwv"/>
    <language>en</language>
    <item>
      <title>Commit Signing - GnuPG Agent Forwarding</title>
      <dc:creator>Jesse P. Johnson</dc:creator>
      <pubDate>Wed, 24 Dec 2025 19:45:39 +0000</pubDate>
      <link>https://dev.to/kuwv/commit-signing-gnupg-agent-forwarding-27co</link>
      <guid>https://dev.to/kuwv/commit-signing-gnupg-agent-forwarding-27co</guid>
      <description>&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;Most corporate networks prevent development on company issued laptops. Often developers must rely on using either virtual or cloud instances within an isolated network. This separation helps secure the rest of the network but could potentially expose the supply chain if proper precautions are not taken. Often when developers are required to sign commits, or code it is just easier to setup signing on the instance you develop from. But, it is much safer to use GnuGP agent forwarding instead leaving your keys somewhere that they can be stolen. This article will show how to setup agent forwarding for this.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configure GPG Agent Forwarding with SSH support
&lt;/h1&gt;

&lt;p&gt;First we will copy the public key we exported to the target system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scp "${HOME}/${gpg_asc}" "${remote_user}@${remote_host}:~"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now import it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh "${remote_user}@${remote_host}" \
gpg --import "~/${gpg_asc}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure GPG to use SSH.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; "${GNUPGHOME}/gpg-agent.conf" &amp;lt;EOF
enable-ssh-support
default-cache-ttl 600
max-cache-ttl 7200
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure the &lt;code&gt;gpg-agent&lt;/code&gt; is started when a terminal is first opened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; "${HOME}/.zshrc.d/gpg-agent" &amp;lt;EOF
export GPG_TTY="$(tty)"
gpg-connect-agent reloadagent /bye &amp;gt;/dev/null
alias pinentry=/opt/homebrew/bin/pinentry
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;✏️ NOTE&lt;br&gt;
It's important to ensure the pinentry is setup correctly according to your distribution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Determine the path to your local GPG socket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extra_socket_on_local_box=$(
  gpgconf --list-dir agent-extra-socket
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Determine the path to the GPG socket on your remote box.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;socket_on_remote_box=$(
  ssh "${remote_user}@${remote_host}" \
  gpgconf --list-dir agent-socket
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setup SSH to map the GPG extra socket to that of its remote.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; "${HOME}/.ssh/config" &amp;lt;&amp;lt;EOF
Host gpgtunnel
  HostName ${remote_host}
  RemoteForward ${socket_on_remote_box} ${extra_socket_on_local_box}
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reconfigure SSH so that GPG agent will use the remote socket instead starting the local one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh "${remote_user}@${remote_host}" \
sudo bash -c "cat &amp;gt; /etc/ssd/sshd_config.d/gpg-agent.conf &amp;lt;&amp;lt;EOF
StreamLocalBindUnlink yes
EOF"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart SSH.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh "${remote_user}@${remote_host}" \
sudo systemctl restart sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure remote gpg-agent is not running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh "${remote_user}@${remote_host}" \
gpgconf --kill gpg-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test that everything is loaded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh "${remote_user}@${remote_host}" \
gpg --list-keys; \
gpg --list-secret-keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you should be able to do commit signing from you remote development box.&lt;/p&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wiki.gnupg.org/AgentForwarding" rel="noopener noreferrer"&gt;https://wiki.gnupg.org/AgentForwarding&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>security</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Commit Signing - GnuPG</title>
      <dc:creator>Jesse P. Johnson</dc:creator>
      <pubDate>Mon, 22 Dec 2025 15:55:44 +0000</pubDate>
      <link>https://dev.to/kuwv/commit-signing-gnupg-1pl4</link>
      <guid>https://dev.to/kuwv/commit-signing-gnupg-1pl4</guid>
      <description>&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;DevSecOps as a practice is intended to introduce security early within the development process. One of simplest and with the highest impact on security capabilities is commit signing.&lt;/p&gt;

&lt;p&gt;Because I work multiple projects that have varying capabilities and setup I decided to put together a bunch of useful primers on this subject. This document covers how to securely setup commit / tag signing.&lt;/p&gt;

&lt;p&gt;Others can be found here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="//..."&gt;Commit Signing - GnuPG with yubikey&lt;/a&gt; planned&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/kuwv/commit-signing-gnupg-agent-forward"&gt;Commit Signing - GnuPG Agent Forwarding&lt;/a&gt; WIP&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why commit signing is important?
&lt;/h2&gt;

&lt;p&gt;Commit signing is a safeguard against:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tampering&lt;/li&gt;
&lt;li&gt;impersonation&lt;/li&gt;
&lt;li&gt;protects against account takeover&lt;/li&gt;
&lt;li&gt;strengthens the software supply chain &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup GPG and Create keys
&lt;/h2&gt;

&lt;p&gt;Install &lt;code&gt;gnupg&lt;/code&gt; along with &lt;code&gt;pinentry-mac&lt;/code&gt; to provide input to GnuPG.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install gnupg pinentry-mac
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure GPG agent
&lt;/h3&gt;

&lt;p&gt;The first step is to ensure GPG is configured with the most secure options available.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✏️ NOTE&lt;br&gt;
The examples provided here use &lt;code&gt;GNUPGHOME&lt;/code&gt; so that existing GPG keys won't be effected. Just set it accordingly to fit your needs:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export GNUPGHOME="${HOME}/.gnupg"
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;or&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export GNUPGHOME="$(mktemp -d)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; "${GNUPGHOME}/gpg.conf" &amp;lt;&amp;lt;EOF
# Strong algorithms
default-new-key-algo ed25519/cv25519
personal-digest-preferences SHA512 SHA384 SHA256
cipher-algo AES256

# Verification
require-valid-signatures
verify-options show-uid-validity
with-fingerprint

# Key expiration prompt
ask-cert-expire

# Quantum readiness (future versions)
# require-pqc-encryption
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding GnuPG key management
&lt;/h2&gt;

&lt;p&gt;Next we should consider key management. There are two distinct approaches that can be taken using GPG keys.&lt;/p&gt;

&lt;p&gt;One approach would be to set an expiration and rotate keys as they expire. This is solid practice as the shorter the validation period the less impact of a key compromise. Unfortunately, this approach requires the use of a Time Stamp Authority (TSA) so that commits continue to be valid after rotation. Currently, neither GitHub or GitLab support this though.&lt;/p&gt;

&lt;p&gt;The second would be to create a GPG hierarchy where the universal key never expires but subkeys do. The benefit of this approach is that it separates the identity from key use.&lt;/p&gt;

&lt;p&gt;This is a solid practice but unfortunately comes with an additional requirement that neither GitHub nor GitLab support. &lt;/p&gt;

&lt;p&gt;Now that we have a starting config we'll move onto creating a GPG key  hierarchy.&lt;/p&gt;

&lt;p&gt;We need to define our identity so that we can reuse it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name="${NAME:-First N. Last}"
email="${EMAIL:-first.n.last@example.com}"
echo -n 'Enter password:' &amp;amp;&amp;amp; read -r -s passphrase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create GnuPG universal key
&lt;/h3&gt;

&lt;p&gt;Here we create a universal key that will act as the root of our GPG key hierarchy. This will be used to generate subkeys from.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpg --batch --full-generate-key &amp;lt;&amp;lt;EOF
Key-Type: eddsa
Key-Curve: Ed25519
Key-Usage: sign
Name-Real: ${name}
Name-Email: ${email}
Passphrase: ${passphrase}
Expire-Date: 0
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup GnuPG subkey
&lt;/h3&gt;

&lt;p&gt;To create our subkey we need the fingerprint of our universal key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpg_fp=$(
  gpg --list-options show-only-fpr-mbox --list-secret-keys \
  | awk '{print $1}')
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then create a signing subkey that will be used for commit / tag signing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpg --batch --quick-add-key "${gpg_fp}" ed25519 sign 1y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup git for commit signing
&lt;/h2&gt;

&lt;p&gt;To setup &lt;code&gt;git&lt;/code&gt; so that it can perform commit signing we will need to get the key id for our subkey. Here we'll retrieve the key id of last subkey created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signingkey="$(
  gpg --list-secret-keys "${email}" --keyid-format LONG \
  | awk '/^ssb/{print $2}' \
  | awk -F'/' 'END{print $2}'
)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit signing can then be configured directly through the git CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Set your user identity (must match info of the GPG key)
git config --global user.name "${name}"
git config --global user.email "${email}"

# Set your signing key
git config --global user.signingkey "${signingkey}"

# Ensure commits are signed by default
git config --global commit.gpgsign true

# Ensure tags are signed by default
git config --global tag.gpgsign true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure git account with GPG signature
&lt;/h3&gt;

&lt;p&gt;To begin we first need to export the public key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpg_asc="${email//[@.]/_/}.asc"
gpg --output "${HOME}/${gpg_asc}" \
  --armor \
  --export "${email}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can follow those steps here for &lt;a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and here for &lt;a href="https://docs.gitlab.com/user/project/repository/signed_commits/gpg/#add-a-gpg-key-to-your-account" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gnupg.org/gph/en/manual/c235.html" rel="noopener noreferrer"&gt;https://www.gnupg.org/gph/en/manual/c235.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc3161" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/rfc3161&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gnupg</category>
      <category>devsecops</category>
      <category>development</category>
    </item>
    <item>
      <title>Applying DevSecOps within Databricks</title>
      <dc:creator>Jesse P. Johnson</dc:creator>
      <pubDate>Tue, 31 Dec 2024 17:25:13 +0000</pubDate>
      <link>https://dev.to/kuwv/applying-devsecops-within-databricks-3496</link>
      <guid>https://dev.to/kuwv/applying-devsecops-within-databricks-3496</guid>
      <description>&lt;p&gt;Databricks is a data processing platform that combines both the processing and storage of data to support many business use cases. Traditionally this been the role of data warehousing but can also include Business Intelligence (BI) and Artificial Intelligence (AI). To implement these use cases though requires access many data sources and software libraries. This combination of data and processing capabilities makes it a target for exploits if proper precautions are not taken. This article will attempt to delve into the evolving landscape of DevSecOps within Databricks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This document will discuss PySpark as it is the most commonly used library within Databricks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Attack Vectors
&lt;/h2&gt;

&lt;p&gt;Databricks primarily uses the powerful Extract, Transform, and Load (ETL/ELT) along with the medallion architecture to simultaneously process and store data. This makes it much more more robust than an average data layer but can also potentially expose the system to a wider variety of exploits than other designs. There are three attack vectors related to this design that relate to development I will discuss. &lt;/p&gt;

&lt;p&gt;The first potential issue with this design is that it is intended to allow consumption of data from nearly any source. This is different than many data stores in that the source data is usually local. ETL/ELT designs primarily center around big data and handling large amounts of it. This includes data from third parties or even untrusted sources. The more sources the more likely the case for an exploit.&lt;/p&gt;

&lt;p&gt;The second potential issue with is all the powerful capabilities it can provide through its orchestration of clusters. Each of these supported capabilities utilize additional libraries thereby increasing access to both a larger amount of data and processing capability. In contrast a typical web application design is split into multiple separate layers to provide separation of concerns. By providing silos between data access, business logic processing, and the presentation each has a much smaller number of libraries and what can be accessed by each library within a layer. This layered approach simplifies development and improves security when done correctly.&lt;/p&gt;

&lt;p&gt;Finally, because of the inherent power and capabilities that Databricks provides, developers are afforded a very high level of trust and power. Insider threats are just as dangerous as those outside of an organization - if not more. If developers are given too much access it could be misused and/or abused. Depending on the size and scope of access potential damage could be quite large and extensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous Integration / Continuous Deployment (CI/CD)
&lt;/h2&gt;

&lt;p&gt;The first import development milestone I believe is establishing a CI/CD process. So, we'll start there.&lt;/p&gt;

&lt;p&gt;Development in Databricks mostly revolves around the use of developer notebooks that can utilize many libraries. Notebooks provide a powerful Graphical User Interface (GUI) that are modular, executable, and can be combined into workflows. The development of these Notebooks however doesn't really fit neatly into DevOps ecosystem though. This section will cover the phases of implementing a CI/CD laid out by Databricks &lt;a href="https://docs.databricks.com/en/dev-tools/ci-cd.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; and explain how to integrate DevSecOps practices to secure your data pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Store
&lt;/h3&gt;

&lt;p&gt;Modern software development centers around the use of a Version Control System (VCS). This practice allows multiple collaborators to modify and develop features and propose changes to the main code branch. This allows efficient code management of changes over time and while maintaining the overall health of the codebase. This most often is Git but can include a few outliers such as Mercurial, or even Subversion.&lt;/p&gt;

&lt;p&gt;Databricks provides two implementations for VCS: Git Repos and Git Folders. The Git Repos has limited integration with only a few repositories and been replaced by Git Folders. With Git Folders either GitHub or GitLab can then be setup to track changes to the workflow being developed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: I won't cover this step due to there being too many considerations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Code
&lt;/h3&gt;

&lt;p&gt;Once the VCS is setup development can then be moved from Databricks GUI to an local IDE environment. This will provide for a much improved development experience along with additional integrations for SAST and SCA from other systems such as SonarQube, CheckMarx or Fortify.&lt;/p&gt;

&lt;p&gt;Install spark locally (MacOS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; visual-studio-code
brew &lt;span class="nb"&gt;install &lt;/span&gt;openjdk@11 python@3.11 apache-spark
pip3 &lt;span class="nb"&gt;install &lt;/span&gt;jupyterlab pyspark

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"export SPARK_HOME=/opt/local/apache-spark"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"export PYSPARK_PYTHON=/opt/local/bin/python"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc

code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; ms-toolsai.jupyter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: The above example uses VSCode but any IDE that supports plugins for jupyter notebooks will most likely work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Build
&lt;/h3&gt;

&lt;p&gt;The main capability that makes the CI/CD process worth while with Databricks is the new inclusion of the Databricks Asset Bundles (DAB). A is a type of Infrastructure as Code (IaC) that can provision notbooks, libraries, workbooks, data pipelines and infrastructure. &lt;/p&gt;

&lt;p&gt;It is recommended that a DAB first be built locally and then ported to a CI/CD. This will also help when troubleshooting needs to be performed on deployments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap databricks/tap
brew &lt;span class="nb"&gt;install &lt;/span&gt;databricks

databricks auth login &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--host&lt;/span&gt; &amp;lt;account-console-url&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--account-id&lt;/span&gt; &amp;lt;account-id&amp;gt;

databricks bundle init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the process has been validated to function with your cluster setup a CI/CD process can be setup.&lt;/p&gt;

&lt;p&gt;Example DAB catalog to package up an example users pipeline:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
  &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
    &lt;span class="na"&gt;git&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="s"&gt;pipelines&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;users_pipeline&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;test-pipeline-{{ .unique_id }}&lt;/span&gt;
      &lt;span class="na"&gt;libraries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;notebook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./users.ipynb&lt;/span&gt;
      &lt;span class="na"&gt;development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;catalog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${resources.schemas.users_schema.id}&lt;/span&gt;
  &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;users_schema&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;test-schema-{{ .unique_id }}&lt;/span&gt;
      &lt;span class="na"&gt;catalog_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
      &lt;span class="na"&gt;comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;This schema was created by DABs.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example GitLab CI build 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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;build-dab&lt;/span&gt;&lt;span class="pi"&gt;:&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;ghcr.io/databricks/cli:v0.218.0&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;build&lt;/span&gt;
  &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="s"&gt;DATABRICKS_HOST="$DATABRICKS_HOST_ENVAR"&lt;/span&gt;
    &lt;span class="s"&gt;DATABRICKS_TOKEN="$DATABRICKS_TOKEN_ENVAR"&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;/app/databricks --workdir ./ bundle deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: Make sure to sign all packages including these. See your respective CI/CD environment for details.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Deploy
&lt;/h3&gt;

&lt;p&gt;Here the Databricks teams would deploy DAB build in the previous section (or CI job) can then be deployed to a development environment for testing.&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;deploy-dab-dev&lt;/span&gt;&lt;span class="pi"&gt;:&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;ghcr.io/databricks/cli:v0.218.0&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;build&lt;/span&gt;
  &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="s"&gt;DATABRICKS_HOST="$DATABRICKS_HOST_ENVAR"&lt;/span&gt;
    &lt;span class="s"&gt;DATABRICKS_TOKEN="$DATABRICKS_TOKEN_ENVAR"&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;/app/databricks --workdir ./ bundle deploy -t dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Unit and Integration testing are pivotal development practices to software engineering. There are two ways to perform unit testing within databricks. The first revolved around using one notebook to test another. The second would be require packaging the source as a library and perform &lt;code&gt;pytest&lt;/code&gt; normally.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This is relatively easy to figure out so I will skip this for now and decide if I need to elaborate more at a later time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Static Application Security Testing (SAST)
&lt;/h4&gt;

&lt;p&gt;Implementing SAST should utilize some sort of source scanning that is able to pick up vulnerabilities. Additionally this should include secrets scanning. Don't skip on scanning Infrastructure as Code (IaC) also.&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;sast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$SF_PYTHON_IMAGE"&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;test&lt;/span&gt;
  &lt;span class="na"&gt;before_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;pip install semgrep==1.101.0&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="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;semgrep ci \&lt;/span&gt;
        &lt;span class="s"&gt;--config=auto \&lt;/span&gt;
        &lt;span class="s"&gt;--gitlab-sast \&lt;/span&gt;
        &lt;span class="s"&gt;--no-suppress-errors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Software Composition Analysis (SCA)
&lt;/h4&gt;

&lt;p&gt;Vulnerabilities within the supply chain have been some of the devastating in recent memory. Implementing SCA helps secure the environment from libraries with known vulnerabilities. This works in tandem with a cyber team that is perform risk analysis on packages that are even allowed in the environment.&lt;/p&gt;

&lt;p&gt;Unfortunately, this stage really should use a manifest to determine if there are any vulnerable dependencies. Currently, there is no such file created for this. To complete this task it would be possible to search out the pre-proccessing commands to install packages from PyPI , Maven or CRAN. This is outside of scope unfortunately for this article. Just be aware that this is a requirement though.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: See Clean and Validate Data for why this should be considered a good thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Run
&lt;/h3&gt;

&lt;p&gt;This step is similar to the deploy stage covered before. The main difference is that there is also a run associate with this. This stage represent the Continuous Deployment (CD) phase.&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;run-dab-prod&lt;/span&gt;&lt;span class="pi"&gt;:&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;ghcr.io/databricks/cli:v0.218.0&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;build&lt;/span&gt;
  &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="s"&gt;DATABRICKS_HOST="$DATABRICKS_HOST_ENVAR"&lt;/span&gt;
    &lt;span class="s"&gt;DATABRICKS_TOKEN="$DATABRICKS_TOKEN_ENVAR"&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;/app/databricks --workdir ./ bundle deploy -t prod&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/app/databricks --workdir ./ bundle run -t prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Monitor
&lt;/h3&gt;

&lt;p&gt;The last DevSecOps capability I will discuss will be the Static Analysis Tool (SAT) provided by Databricks. This tool provides a very useful feature to track the command logs execute by notebooks. When this is implemented tracing what and how something happened becomes much easier.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://www.databricks.com/blog/monitoring-notebook-command-logs-static-analysis-tools" rel="noopener noreferrer"&gt;here&lt;/a&gt; for additional details.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Warning: The example utilizes &lt;code&gt;Pyre&lt;/code&gt; instead of &lt;code&gt;Mypy&lt;/code&gt; which conflicts with one of the tools I would like to suggest for a different purpose.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Iterate
&lt;/h3&gt;

&lt;p&gt;I think almost anyone who is willing to get this far in this article is probably aware of the Software Development Lifecycle (SDLC). An SDLC is a well established software development process to develop high-quality software. Development doesn't need to move from notebooks locally or even a CI/CD to be setup to implement this process. But they do complement each other. If anything I would recommend that time should be spent to determine if your team is hierarchical in nature or if it is flat. If it is the former I would recommend just implementing only Scrum but if it is the latter I would recommend looking into Extreme Programming (XP). &lt;/p&gt;

&lt;h2&gt;
  
  
  Clean and Validate Data
&lt;/h2&gt;

&lt;p&gt;One of the primary uses of Databricks is to clean and validate data utilizing the aforementioned medallion architecture. The system is however designed to support a polyglot of languages. To do so it provides various types of dataframes (typically parquet) that utilizes a common set of primitive and complex types. This type system is then organized through the use of a schema to help structure these dataframes. This schema can be either manually specified or automatically generated. This schema ensures the data matches the schema type input into a dataframe but provide no additional validation capabilities.&lt;/p&gt;

&lt;p&gt;Example schema using PySpark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IntegerType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;StringType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;StructField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;StructType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;user_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StructType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;StructField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;StringType&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;StructField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IntegerType&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="bp"&gt;True&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="n"&gt;dataframe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;spark&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;header&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DROPMALFORMED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users.csv&lt;/span&gt;&lt;span class="sh"&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;Adding additional validation can be provided through multiple third party modules. This is also desirable to have to the possibility to share this schema to any middleware and/or presentation layer if possible. The most popular libraries for this task are typically &lt;code&gt;pydantic&lt;/code&gt; and &lt;code&gt;marshmallow&lt;/code&gt; but neither supports dataframes natively. There are two promising libraries extend &lt;code&gt;pydantic&lt;/code&gt; to support this: &lt;code&gt;great_expectations&lt;/code&gt; and &lt;code&gt;pandera&lt;/code&gt;. I will review &lt;code&gt;pandera&lt;/code&gt; here as I have not been able to get any version of great_expectations to pass a cyber review (possibly due to the &lt;code&gt;mistune&lt;/code&gt; dependency utilizing &lt;code&gt;regex&lt;/code&gt; to &lt;a href="https://blog.codinghorror.com/parsing-html-the-cthulhu-way/" rel="noopener noreferrer"&gt;parse markdown the Cthulhu Way&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;pandera&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;22.1&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pandera&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DataFrameModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataFrameModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;coerce&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;coerce&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;UserModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataframe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Parameterized Queries
&lt;/h3&gt;

&lt;p&gt;Data persistence is provided through Databricks via the use of SQL Warehousing (formerly SQL endpoints) capability. In earlier versions   concatenation and interpolation were the only approaches available to passing parameters to SQL queries. This unfortunately is the primary cause of SQL injection attacks and is considered bad security practice regardless of the language it is implemented. This attack is possible due to the recursive nature of SQL statements and the mishandling of untrusted inputs.&lt;/p&gt;

&lt;p&gt;There are two ways to mitigate this attack. The first would be to sanitize all inputs. This is still considered insufficient though and more of a naive approach. The issue still is there is no guarantee that some unsantized inputs could still potentially be processed incorrectly by a statement susceptible to SQL inject attack. The preferred approach is to use prepared statements which are now supported by databricks (or Named Parameter Markers if using pure SQL)&lt;/p&gt;

&lt;p&gt;Example parameterized query using PySpark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM example_table WHERE id = {id};&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.databricks.com/en/dev-tools/bundles/index.html" rel="noopener noreferrer"&gt;https://docs.databricks.com/en/dev-tools/bundles/index.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.databricks.com/blog/parameterized-queries-pyspark" rel="noopener noreferrer"&gt;https://www.databricks.com/blog/parameterized-queries-pyspark&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devsecops</category>
      <category>datascience</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Deconstructing DevSecOps</title>
      <dc:creator>Jesse P. Johnson</dc:creator>
      <pubDate>Thu, 26 Dec 2024 14:58:00 +0000</pubDate>
      <link>https://dev.to/kuwv/deconstructing-devsecops-2nej</link>
      <guid>https://dev.to/kuwv/deconstructing-devsecops-2nej</guid>
      <description>&lt;p&gt;As a an engineer that has worked in multiple fields I have seen many approaches to handle the complexity of product development. Among these, DevOps has demonstrated itself as the biggest success story to delivering software today. This success has inspired multiple offshoots attempting to capitalize on the zeitgeist of their specialized field. The advent of virtualization and cloud first gave us CloudOps and then eventually CattleOps. The ability to orchestrate containers gave us GitOps. Now AI and ML are poised to give use AIOps and MLOps respectfully. It's DevSecOps though that has succeeded where others have stumbled that we will discuss here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why DevOps?
&lt;/h2&gt;

&lt;p&gt;It's not hard to understand why DevOps has been successful for many. There has been a collective shift of many projects to adopt agile development practices. DevOps itself is a natural progression of similar principles that extend that collaboration to the rest of the product team. Ideally DevOps would help foster a more adaptive team and product between that of the developers and that also of the operations and quality assurance (QA) team.&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%2Fayv0xqvvf9sk5ypuqsdt.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%2Fayv0xqvvf9sk5ypuqsdt.png" alt="DevOps" width="478" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Breaking down the silos between these teams is a foundational concept. When the teams understand the product coherently it allows each team to solve problems more quickly. This works best when cross-team training and shared responsibilities &lt;/p&gt;

&lt;p&gt;Another tenant of DevOps is the reliance on automation that it utilizes. This exists as either Continuous Integration (CI) and Continuous Delivery (CD) or CI/CD and Infrastructure as Code (IaC). This is only possible due to the wide selection of open source tooling that has been released in the previous decade. This has allowed CI/CD automate the build, test and deployment of products with evermore efficiency as new products and improvements are realized. &lt;/p&gt;

&lt;p&gt;Lastly, establishing a feedback loop through testing, monitoring, and logging are vital throughout the application lifecycle. These help determine the health of the project and what action need to be performed next.&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%2Fdinf1bv20stbd14uuryp.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%2Fdinf1bv20stbd14uuryp.png" alt="DevOps Toolchain" width="512" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Together these establish the foundation of the DevOps toolchain. This focus on automation allows small teams do more with less and large teams to better scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  From DevOps to DevSecOps
&lt;/h2&gt;

&lt;p&gt;As a Certified Information Systems Security Professional (CISSP) for many years I have always considered security to be paramount for any project I worked. However, there was a paradigm shift occurring that I knew nothing about until I saw a meme critiquing DevOps security practices as...&lt;/p&gt;

&lt;p&gt;Well, a mess...&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%2Fp4ff82chb7qv7jyzxfro.jpg" 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%2Fp4ff82chb7qv7jyzxfro.jpg" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a fact that not all projects are built the same and some are more risk adverse than others. In recent years an increasing number of supply chain attacks has only accelerated adoption of DevSecOps and put it in the forefront.&lt;/p&gt;

&lt;p&gt;This has resulted in an increased focus to move security earlier in development. This happens to also be a core tenant of DevSecOps and is referred as a "shift-left" of security to the development phase.&lt;/p&gt;

&lt;p&gt;Unsurprisingly, the roles of quality assurance has been replaced with an emphasis on security instead. This could be seen as a natural evolution for many projects utilizing some form of agile development due to a lesser role of requirements management in a post-waterfall world. The outcome being that lesser requirements being managed result in less requirements testing a QA engineer would perform on an application. However, this responsibility still exists but is now solely on developers in most situations.&lt;/p&gt;

&lt;p&gt;Additionally, developers would also be responsible for ensuring that CI/CD pipelines pass various security tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secrets scanning&lt;/li&gt;
&lt;li&gt;Commit / Package signing&lt;/li&gt;
&lt;li&gt;Static Application Security Testing (SAST)&lt;/li&gt;
&lt;li&gt;Software Composition Analysis (SCA)&lt;/li&gt;
&lt;li&gt;Dynamic Application Security Testing (DAST)&lt;/li&gt;
&lt;li&gt;Container Scanning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An operations team would do their part by ensuring their IaC implement some type of infrastructure compliance such as a CIS benchmark and ideally some level of FIPS compliance. Since this work is usually Configuration-as-Code (CaC) and not considered code it usually doesn't have the same rigor as application code though.&lt;/p&gt;

&lt;p&gt;All-in-all these are amazing improvements that any team should implement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What issues does DevSecOps have?
&lt;/h2&gt;

&lt;p&gt;Time and again I am reminded that there is a limit to how far collaboration can take a team. This can be because either another team has a limit to how much resources it is willing to allocate, or it is incapable of contributing regardless of its resources offered. This is often the case with cyber teams that haven't restructured or adapted the training of their personnel to support DevSecOps. To often these types are policy wonks that will happily redirect you to help desk instead of assisting anyone.&lt;/p&gt;

&lt;p&gt;Another huge problem is with tooling ecosystem itself. While DevOps has an embarrassment of riches in open source tooling, DevSecOps instead has an endless number of licensing fees awaiting. Worse yet, many of these tools are only designed to common security issues in code. This is still better than nothing but it is pretty underwhelming when you are responsible for remediating the shear number of redundant (or duplicate) findings that have no bearing.&lt;/p&gt;

&lt;p&gt;Once an organization begins to implement DevSecOps it can quickly spiral. This happens when the organization is unable to determine what is acceptable risk any longer. Once this happens any rapid prototyping capability will just not be allowed at this point. Suddenly, any pioneering spirit or creative capability can be strangled out of the organization.&lt;/p&gt;

&lt;h2&gt;
  
  
  How can DevSecOps be improved?
&lt;/h2&gt;

&lt;p&gt;Cyber professionals need the correct skill sets for this type of position. Understanding software architecture is a good first step but that alone wouldn't suffice. Threat modeling is a great recommended practice for DevSecOps but useless if the team reviewing that threat model is unable to grasp it. An understanding of where vulnerabilities are and how they are exploited is much more valuable to a development and operations team then a checklist of findings. &lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;There once was criticism about whether DevOps was strictly a methodology or a role. Today we largely associate this type of position with that of an SRE. I personally never thought this an issue, and have always considered someone who implemented the tooling and facilitated the collaboration between the development and operation teams to be just that. It is specifically the social interaction piece to break down the barriers between teams that I believe sets a DevOps engineer apart from an SRE engineer. Whereas an SRE engineer is specifically an software developer tasked with tackling infrastructure.&lt;/p&gt;

&lt;p&gt;I think that teams would benefit more if they cross-trained. The main reason a Cyber team fails to adapt and change is that they these members or assigned these teams permanently as a scrum master or product owner are. Instead of a fully engaged participating members of a team specializing in various disciplines you are more likely to find an issue management system in place instead. Instead of engineers embracing DevSecOps as a way to interact with other an quickly resolve issues you might have to resolve yourself to get in line instead and have to prepare a documents justifying your deployment scenario.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>Why I use Ansible over docker-compose</title>
      <dc:creator>Jesse P. Johnson</dc:creator>
      <pubDate>Sun, 15 Dec 2019 23:45:44 +0000</pubDate>
      <link>https://dev.to/kuwv/why-i-use-ansible-over-docker-compose-edg</link>
      <guid>https://dev.to/kuwv/why-i-use-ansible-over-docker-compose-edg</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is intended to provide background for other articles I plan on writing&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have used Red Hat Single Sign-On (KeyCloak) off and on for a few years now and have good experiences with it. It can be a little overwhelming though for developers that don't have experience with Identity Access Management (IAM). Having a good reference architecture readily available is invaluable to demonstrate how it works. So, I decided to create a Python microservices prototype using FastAPI, SSO, and an API Gateway. Prior to starting, I hadn't yet tried Kong and decided to use it as the API gateway for the prototype.&lt;/p&gt;

&lt;p&gt;To start off, I began looking for example implementations with KeyCloak and Kong and found this &lt;a href="https://www.jerney.io/secure-apis-kong-keycloak-1/" rel="noopener noreferrer"&gt;gem of an article&lt;/a&gt;. It's great for getting KeyCloak and Kong to work together. The instructions were clear and I didn't have to figure out the versions issues right away. Those conflicts came later when I wanted additional features. &lt;/p&gt;

&lt;p&gt;But, it was clear that the author's choice of docker-compose and Curl wouldn't work for my needs. Using docker-compose could not setup integration between KeyCloak and Kong by itself. I wanted something that would allow users to be able to access the stack through just one command. This is why Ansible is the better choice.&lt;/p&gt;

&lt;h1&gt;
  
  
  Objectives
&lt;/h1&gt;

&lt;p&gt;Security is difficult without automation. It can also slow work down too if this step isn't done. It's also much harder to collaborate if development environments are not consistent. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple to create/tear-down environments&lt;/li&gt;
&lt;li&gt;Must encapsulate deployment commands&lt;/li&gt;
&lt;li&gt;Allow deployment to co-exist with application&lt;/li&gt;
&lt;li&gt;Allow deployment to scale with application&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What is Ansible?
&lt;/h1&gt;

&lt;p&gt;For newcomers, Ansible can best be described as a tool that specializes in orchestration, configuration management, and automation. It is agentless and allows management of resources without requiring client software be installed. This allows many built-in integrations (called modules) to be used - including Docker.&lt;/p&gt;

&lt;h1&gt;
  
  
  Comparing Ansible and docker-compose
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Caveat: The Ansible here is written entirely as playbooks. For simplicity it has minimal amount of variables and no external roles. I later plan to write an additional article for reducing redundancy with Ansible roles.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building Docker Images is still painless.&lt;/strong&gt; When &lt;code&gt;docker-compose up&lt;/code&gt; is run it will automatically build any Dockerfile if the image is not already available. Ansible requires that the &lt;a href="https://docs.ansible.com/ansible/latest/modules/docker_image_module.html" rel="noopener noreferrer"&gt;docker_image&lt;/a&gt; module be provided the instructions for images to be built.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example Docker build using Ansible:&lt;/em&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="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;Build Kong OIDC image&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kong:0.14.1-centos-oidc&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pull&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../kong&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Setting up docker resources is relatively the same.&lt;/strong&gt; Both docker-compose and Ansible can setup resources such as networks and volumes within Docker. The YAML used by both systems are relatively similar.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example docker-compose for building resources from jerney.io:&lt;/em&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;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;keycloak-net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;keycloak-datastore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;Example using &lt;a href="https://docs.ansible.com/ansible/latest/modules/docker_network_module.html" rel="noopener noreferrer"&gt;docker_network&lt;/a&gt; and &lt;a href="https://docs.ansible.com/ansible/latest/modules/docker_volume_module.html" rel="noopener noreferrer"&gt;docker_volume&lt;/a&gt;:&lt;/em&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="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;Setup network for KeyCloak&lt;/span&gt;
  &lt;span class="na"&gt;docker_network&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;keycloak-net&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;sso_network_state&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;default('present')&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&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;Setup volumes for KeyCloak&lt;/span&gt;
  &lt;span class="na"&gt;docker_volume&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;keycloak-volume&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;sso_volume_state&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;default('present')&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the above examples you can see that Ansible is a bit more verbose. But, much of this can be simplified further.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Container provisioning is a bit better in docker-compose.&lt;/strong&gt; KeyCloak requires a database for it to operate. Ensuring containers are deployed in order is easily done through docker-compose with the 'depends_on' keyword. Deployments with docker-compose have some benefits in that it is efficient at ensuring the database is up before starting the KeyCloak container.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example setting up KeyCloak database from jerney.io&lt;/em&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="s"&gt;keycloak-db&lt;/span&gt;&lt;span class="err"&gt;:&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;postgres:9.6&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;keycloak-datastore:/var/lib/postresql/data&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;keycloak-net&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;25432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;keycloak&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;keycloak&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;example setting up KeyCloak from jerney.io&lt;/em&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="s"&gt;keycloak&lt;/span&gt;&lt;span class="err"&gt;:&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;jboss/keycloak:4.5.0.Final&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;keycloak-db&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;keycloak-net&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;8180:8080"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DB_VENDOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;POSTGRES&lt;/span&gt;
      &lt;span class="na"&gt;DB_ADDR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;keycloak-db&lt;/span&gt;
      &lt;span class="na"&gt;DB_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="m"&gt;5432&lt;/span&gt;
      &lt;span class="na"&gt;DB_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak&lt;/span&gt;
      &lt;span class="na"&gt;DB_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;keycloak&lt;/span&gt;
      &lt;span class="na"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
      &lt;span class="na"&gt;KEYCLOAK_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;admin&lt;/span&gt;
      &lt;span class="na"&gt;KEYCLOAK_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Ansible code required to setup the KeyCloak containers is again similar to that of docker-compose with two differences. Firstly, the &lt;a href="https://docs.ansible.com/ansible/latest/modules/docker_container_info_module.html" rel="noopener noreferrer"&gt;docker_container_info&lt;/a&gt; module is used to determine if the database container is already deployed. If it isn't it will then use the &lt;a href="https://docs.ansible.com/ansible/latest/modules/docker_container_module.html" rel="noopener noreferrer"&gt;docker_container&lt;/a&gt; module to then pull, setup and start the image.&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="nn"&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;Check if KeyCloak DB is running&lt;/span&gt;
  &lt;span class="na"&gt;docker_container_info&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;keycloak-db&lt;/span&gt;
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak_db_state&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;block&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;Start KeyCloak DB&lt;/span&gt;
      &lt;span class="na"&gt;docker_container&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;keycloak-db&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;postgres:9.6&lt;/span&gt;
        &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;keycloak-datastore:/var/lib/postresql/data&lt;/span&gt;
        &lt;span class="na"&gt;networks_cli_compatible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;networks&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;keycloak-net&lt;/span&gt;
        &lt;span class="na"&gt;exposed_ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;25432:5432'&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;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak&lt;/span&gt;
          &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak&lt;/span&gt;
          &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak_db_register&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Secondly, the &lt;a href="https://docs.ansible.com/ansible/latest/modules/wait_for_module.html" rel="noopener noreferrer"&gt;wait_for&lt;/a&gt; is then used to ensure that the database is operational before continuing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example wait for database port for Ansible:&lt;/em&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="s"&gt;...&lt;/span&gt;
    &lt;span class="s"&gt;- name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Wait for KeyCloak DB to accept connections&lt;/span&gt;
      &lt;span class="s"&gt;wait_for&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;
          &lt;span class="s"&gt;keycloak_db_register['ansible_facts']&lt;/span&gt;
          &lt;span class="s"&gt;['docker_container']&lt;/span&gt;
          &lt;span class="s"&gt;['NetworkSettings']&lt;/span&gt;
          &lt;span class="s"&gt;['Networks']&lt;/span&gt;
          &lt;span class="s"&gt;['keycloak-net']&lt;/span&gt;
          &lt;span class="s"&gt;['IPAddress']&lt;/span&gt;
        &lt;span class="s"&gt;}}"&lt;/span&gt;
        &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;started&lt;/span&gt;
        &lt;span class="na"&gt;connect_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
&lt;span class="err"&gt;      &lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak_db_running&lt;/span&gt;
      &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak_db_running is success&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="err"&gt;  &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;not keycloak_db_state.exists&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The KeyCloak container can then be provisioned once the database is operational. The process is identical to initializing the database with both &lt;a href="https://docs.ansible.com/ansible/latest/modules/docker_container_info_module.html" rel="noopener noreferrer"&gt;docker_container_info&lt;/a&gt; and &lt;a href="https://docs.ansible.com/ansible/latest/modules/docker_container_module.html" rel="noopener noreferrer"&gt;docker_container&lt;/a&gt; modules being utilized again.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example starting KeyCloak with Ansible&lt;/em&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="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;Check if KeyCloak DB is running&lt;/span&gt;
  &lt;span class="na"&gt;docker_container_info&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;keycloak&lt;/span&gt;
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak_state&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;block&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;Start KeyCloak&lt;/span&gt;
      &lt;span class="na"&gt;docker_container&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;keycloak&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;jboss/keycloak:7.0.0&lt;/span&gt;
        &lt;span class="na"&gt;networks_cli_compatible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;networks&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;keycloak-net&lt;/span&gt;
            &lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;keycloak-db&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;api-net&lt;/span&gt;
            &lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;webapp&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kong&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8080:8080'&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;DB_VENDOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES&lt;/span&gt;
          &lt;span class="na"&gt;DB_ADDR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak-db&lt;/span&gt;
          &lt;span class="na"&gt;DB_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5432'&lt;/span&gt;
          &lt;span class="na"&gt;DB_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak&lt;/span&gt;
          &lt;span class="na"&gt;DB_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak&lt;/span&gt;
          &lt;span class="na"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
          &lt;span class="na"&gt;KEYCLOAK_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
          &lt;span class="na"&gt;KEYCLOAK_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak_register&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;Wait for KeyCloak to accept connections&lt;/span&gt;
      &lt;span class="na"&gt;wait_for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;
          &lt;span class="s"&gt;keycloak_register['ansible_facts']&lt;/span&gt;
          &lt;span class="s"&gt;['docker_container']&lt;/span&gt;
          &lt;span class="s"&gt;['NetworkSettings']&lt;/span&gt;
          &lt;span class="s"&gt;['Networks']&lt;/span&gt;
          &lt;span class="s"&gt;['keycloak-net']&lt;/span&gt;
          &lt;span class="s"&gt;['IPAddress']&lt;/span&gt;
        &lt;span class="s"&gt;}}"&lt;/span&gt;
        &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;started&lt;/span&gt;
        &lt;span class="na"&gt;connect_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak_running&lt;/span&gt;
      &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak_running is success&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&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;not keycloak_state.exists&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Additional Configuration with Ansible
&lt;/h1&gt;

&lt;p&gt;So, if you have been following up to now you may wonder where the real benefits in using Ansible are. Ansible may not be as efficient at managing Docker as well as docker-compose. It is a bit more verbose. But, it is in the post setup where Ansible shines and docker-compose is essentially a no-show.&lt;/p&gt;

&lt;p&gt;Here docker-compose lacks Curl and JSON integration. Ansible on the other hand provides the &lt;a href="https://docs.ansible.com/ansible/latest/modules/uri_module.html" rel="noopener noreferrer"&gt;uri&lt;/a&gt; module and native JSON support to perform additional tasks.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example retrieving login token from KeyCloak&lt;/em&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="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;Authenticate with KeyCloak&lt;/span&gt;
      &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8080/auth/realms/master&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;/protocol/openid-connect/token"&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
        &lt;span class="na"&gt;body_format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;form-urlencoded&lt;/span&gt;
        &lt;span class="na"&gt;body&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;admin-cli&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
          &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
        &lt;span class="na"&gt;return_content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
      &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sso_auth.status != -1&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sso_auth&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;Set KeyCloak access token&lt;/span&gt;
      &lt;span class="na"&gt;set_fact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;sso_auth.json.access_token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;Example create KeyCloak client for Kong:&lt;/em&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="s"&gt;...&lt;/span&gt;
    &lt;span class="s"&gt;- name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create KeyCloak client&lt;/span&gt;
      &lt;span class="s"&gt;keycloak_client&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;auth_client_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin-cli&lt;/span&gt;
        &lt;span class="na"&gt;auth_keycloak_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8080/auth&lt;/span&gt;
        &lt;span class="na"&gt;auth_realm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
        &lt;span class="na"&gt;auth_username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
        &lt;span class="na"&gt;auth_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&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;api-gw&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;sid&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openid-connect&lt;/span&gt;
        &lt;span class="na"&gt;public_client&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;root_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8000&lt;/span&gt;
        &lt;span class="na"&gt;redirect_uris&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8000/mock/*&lt;/span&gt;
        &lt;span class="na"&gt;direct_access_grants_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;standard_flow_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;client_authenticator_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;client-secret&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;client_register&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With this layout provisioning the full stack with Ansible requires only one command. Comparatively, provisioning with docker-compose requires that a separate curl command be issued to create a client, fetch the client_secret and registered with the Kong. And while task runners such as automake, rake, pyinvoke, or even just plain Bash but it would still entail that docker-compose couldn't do it alone.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example provisioning command with Ansible:&lt;/em&gt;&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="c"&gt;# ansible-playbook -i localhost, sso/deploy.yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Please view the prototype to test it out:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kuwv" rel="noopener noreferrer"&gt;
        kuwv
      &lt;/a&gt; / &lt;a href="https://github.com/kuwv/python-microservices" rel="noopener noreferrer"&gt;
        python-microservices
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Python Microservices with OpenID-Connect/OAuth2
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h1&gt;
  
  
  When to use one or the other
&lt;/h1&gt;

&lt;p&gt;If you develop on POSIX systems, such as Linux or Mac, using Ansible with Docker might just be easier.&lt;/p&gt;

&lt;p&gt;If you develop on Windows systems then using either Vagrant with Ansible or a task runner with docker-compose might work best.&lt;/p&gt;

&lt;p&gt;Also, if you develop on Swarm then docker-compose might just be your comfort zone. But, take a look at &lt;a href="https://docs.ansible.com/ansible/latest/modules/docker_swarm_module.html" rel="noopener noreferrer"&gt;docker_swarm&lt;/a&gt; module if you're curious.&lt;/p&gt;

&lt;p&gt;This is just my opinion of course.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;The problem with using docker-compose alone is that it doesn't provide any other automation capabilities outside of managing Docker. Interfacing APIs or running additional post configuration tasks just requires additional tools. This can include provisioning an image with your configuration management tool of choice or using a task runner such as &lt;a href="http://www.pyinvoke.org/" rel="noopener noreferrer"&gt;pyinvoke&lt;/a&gt; or &lt;a href="https://ruby.github.io/rake/" rel="noopener noreferrer"&gt;rake&lt;/a&gt; locally. &lt;/p&gt;

&lt;p&gt;Comparing docker-compose to Ansible is probably unfair since it competes more with Vagrant for developer mind space - I guess. But, where Vagrant has integrations with configuration management tools, docker-compose requires additional images to be deployed with those tools instead.&lt;/p&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.jerney.io/secure-apis-kong-keycloak-1/" rel="noopener noreferrer"&gt;Securing APIs with Kong and Keycloak - Part 1&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>docker</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
