<?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: Tiexin Guo</title>
    <description>The latest articles on DEV Community by Tiexin Guo (@ironcore864).</description>
    <link>https://dev.to/ironcore864</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%2F740553%2F62f97988-bdce-4472-9836-47551000bd69.jpeg</url>
      <title>DEV Community: Tiexin Guo</title>
      <link>https://dev.to/ironcore864</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ironcore864"/>
    <language>en</language>
    <item>
      <title>Container Security Scanning: Vulnerabilities, Risks and Tooling</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Mon, 07 Oct 2024 15:21:27 +0000</pubDate>
      <link>https://dev.to/gitguardian/container-security-scanning-vulnerabilities-risks-and-tooling-2g8m</link>
      <guid>https://dev.to/gitguardian/container-security-scanning-vulnerabilities-risks-and-tooling-2g8m</guid>
      <description>&lt;p&gt;Over the past decade, the rise of microservice architectures, the DevOps culture, and popular tools like Docker and Kubernetes have made deploying applications as containers standard practice in the industry, making container security a top priority.&lt;/p&gt;

&lt;p&gt;In this article, we will take a deep look at container security: what are the common container vulnerabilities, how to mitigate risks by container security scanning, and what popular container security tools and solutions are available to integrate into the SDLC in a DevSecOps way. Without further ado, let's get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container Vulnerabilities
&lt;/h3&gt;

&lt;p&gt;Containers are created using container images, which act as templates. If the images have vulnerabilities, the containers will introduce those vulnerabilities into the production environment.&lt;/p&gt;

&lt;p&gt;Container images have a collection of files in them: source code, but also configurations, binaries, and other dependencies, all of which could introduce vulnerabilities. So, what are the common &lt;em&gt;container vulnerabilities&lt;/em&gt;? Here are a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Misconfigurations and hard-coded secrets.&lt;/li&gt;
&lt;li&gt;  Vulnerabilities in the application code.&lt;/li&gt;
&lt;li&gt;  Vulnerabilities brought by insecure libraries or other dependencies that are imported into the images (for example, malicious code could be introduced by a software supply chain attack).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Container Security Scanning
&lt;/h3&gt;

&lt;p&gt;Fortunately, most container vulnerabilities can be mitigated relatively easily via &lt;em&gt;container security scanning,&lt;/em&gt; a process that analyzes images and containers for security issues. Continuous container security scanning, or continuous container security, if you will, is a critical part of DevSecOps.&lt;/p&gt;

&lt;p&gt;How does container security scanning work? Typically, there are two different methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;by analyzing files, packages, and dependencies&lt;/strong&gt;. For example, by looking at insecure coding practices or misconfiguration, search for patterns that look like hard-coded secrets, and scan the application's dependencies (including third-party libraries and frameworks) against databases of known vulnerabilities.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;by analyzing containers' activity&lt;/strong&gt;. Unexpected network traffic or a a shell spawned in a container with an attached terminal are both suspicious activities that could cause security concerns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, let's examine these container vulnerabilities—secrets, misconfiguration, vulnerabilities from code, and dependencies—and how different approaches and tools can help mitigate these risks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hard-Coded Secrets and Misconfigurations in Docker Images
&lt;/h2&gt;

&lt;p&gt;Misconfigurations and hard-coded secrets reduce security by making the application and potentially the entire infrastructure vulnerable to easy exploits. Attackers can gain access to critical systems and may even escape from within containers to the host machine.&lt;/p&gt;

&lt;p&gt;So, at a bare minimum, we need to check for misconfigurations and ensure &lt;strong&gt;no hard-coded secrets are present in Docker images&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;💡&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Hard-coded secrets are fundamentally different from other vulnerabilities&lt;/em&gt;&lt;/strong&gt;*: any exposed secret can lead to a security breach, regardless of whether the container is unused or the image is old. A Docker image is constructed from stacked layers that form its current state. This layered structure is prone to leaks because a layer can hide secrets from previous layers, making them invisible in the final state but still present within the image. So using a scanner that can thoroughly examine each layer is crucial for identifying and mitigating these hidden secrets.&lt;/p&gt;

&lt;p&gt;Now, let's have a look at a few popular tools that can detect hard-coded secrets and misconfiguration.&lt;/p&gt;

&lt;h3&gt;
  
  
  ggshield
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/GitGuardian/ggshield?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;&lt;code&gt;ggshield&lt;/code&gt;&lt;/a&gt; (requires a GitGuardian account) is a CLI to find more than 350+ types of hardcoded secrets (&lt;a href="https://blog.gitguardian.com/why-detecting-generic-credentials-is-a-game-changer/" rel="noopener noreferrer"&gt;specific and generic ones&lt;/a&gt;) and 70+ Infrastructure-as-Code security misconfigurations.&lt;/p&gt;

&lt;p&gt;Simply install it with your package manager (for example, for macOS, you can install &lt;code&gt;ggshield&lt;/code&gt; using Homebrew: &lt;code&gt;brew install gitguardian/tap/ggshield&lt;/code&gt;), and then the &lt;code&gt;ggshield secret scan docker&lt;/code&gt; command is ready to be used to scan local docker images for secrets present in the image's creation process (Dockerfile and build arguments), and in the image's layers' filesystem.&lt;/p&gt;

&lt;p&gt;Here's a demo of how it works:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ggshield&lt;/code&gt; provides many more functionalities than simply scanning for secrets. For more details, refer to the cheat sheet below (click to get the PDF) or the &lt;a href="https://docs.gitguardian.com/ggshield-docs/home?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;official documentation here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gitguardian.com/files/securing-your-secrets-with-ggshield-cheat-sheet?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;&lt;img src="https://media.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%2Fqhmk2kd29chfrxex22o3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  SecretScanner
&lt;/h3&gt;

&lt;p&gt;Another option is &lt;a href="https://github.com/deepfence/SecretScanner?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;&lt;code&gt;SecretScanner&lt;/code&gt;&lt;/a&gt; from Deepfence. It is a standalone, open-source tool that retrieves and searches container and host filesystems, matching the contents against approximately 140 secret types (specific only).&lt;/p&gt;

&lt;p&gt;Using SecretScanner is simple: pull the docker image for it by running &lt;code&gt;docker pull quay.io/deepfenceio/deepfence_secret_scanner_ce:2.2.0&lt;/code&gt;, and we can simply do a &lt;code&gt;docker run&lt;/code&gt; to scan container images.&lt;/p&gt;

&lt;p&gt;See it in action below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/content/images/2024/06/secretscanner.svg" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.gitguardian.com%2Fcontent%2Fimages%2F2024%2F06%2Fsecretscanner.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  trivy
&lt;/h3&gt;

&lt;p&gt;Yet another option is &lt;a href="https://github.com/aquasecurity/trivy?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;&lt;code&gt;trivy&lt;/code&gt;&lt;/a&gt; , which is a versatile security scanner that can scan for both secrets (&lt;a href="https://github.com/aquasecurity/trivy/blob/main/pkg/fanal/secret/builtin-rules.go?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;but only specific ones&lt;/a&gt;) and misconfiguration in container images, file systems, remote git repositories, and more (will touch on this a bit more in the next section). For now, let's focus on its capabilities of secrets and misconfiguration detection.&lt;/p&gt;

&lt;p&gt;It can be installed using your package manager (for example, for macOS, you can also use Homebrew to install it: &lt;code&gt;brew install trivy&lt;/code&gt;), and you can use it to scan both Docker images and local file systems.&lt;/p&gt;

&lt;p&gt;For example, to scan secrets in an image, run &lt;code&gt;trivy image --scanners secret IMAGE_NAME&lt;/code&gt;. Below is an example with a clean result:&lt;/p&gt;

&lt;p&gt;Trivy also provides built-in checks to detect misconfigurations in popular Infrastructure as Code files, such as Docker, Kubernetes, Terraform, CloudFormation, and more. You can even define your own custom checks.&lt;/p&gt;

&lt;p&gt;Here's an example to scan a Dockerfile locally: Given the Dockerfile below (building a simple Golang app with Alpine as the base image):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM alpine
WORKDIR $GOPATH/src/github.com/ironcore864/go-hello-http
COPY . .
RUN apk add git
RUN go get ./... &amp;amp;amp;&amp;amp;amp; CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o hello
CMD ["./hello"]
EXPOSE 8080/tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to check for misconfigurations (as we can see, 4 are found):&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Image Scanning for CVEs
&lt;/h2&gt;

&lt;p&gt;The previous section ensures that there are no hard-coded secrets and that the configurations are spot-on. Next, let's move on to the other files in the Docker image: OS packages, language-specific packages, dependencies, etc.&lt;/p&gt;

&lt;p&gt;The aforementioned comprehensive scanner &lt;code&gt;trivy&lt;/code&gt; is one of the most popular open-source security scanners. It can also detect known vulnerabilities according to the versions of installed packages. This means that everything inside the image, whether called by your code or not, is scanned.&lt;/p&gt;

&lt;p&gt;For example, let's write a simple Python Flask application and build a Docker image out of it, then scan it with &lt;code&gt;trivy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A very simple Flask hello world app is created:&lt;/p&gt;

&lt;p&gt;Then, we add some dependencies in &lt;code&gt;requirements.txt&lt;/code&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="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;2.19&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;cryptography&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;3.3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;flask&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you noticed, some packages are added, but not even used by our Flask app code, simply as a test.&lt;/p&gt;

&lt;p&gt;Then we create a Dockerfile to build it:&lt;/p&gt;

&lt;p&gt;If you wish, you can &lt;a href="https://github.com/IronCore864/python-insecure-app?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;clone the repository here&lt;/a&gt; to get the whole code above.&lt;/p&gt;

&lt;p&gt;We build and tag it: &lt;code&gt;docker build . -t ironcore864/python-insecure-app:0.0.1&lt;/code&gt;, then scan it with &lt;code&gt;trivy&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;As you can see, multiple vulnerabilities are found, some of which are from the OS packages and some from the dependencies we added to it. Even though we do not call those packages directly with our code, since image scanning works by scanning the OS packages and dependencies—what's in the image—it finds them all.&lt;/p&gt;

&lt;p&gt;Besides &lt;code&gt;trivy&lt;/code&gt;, there are a few other tools worth mentioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://github.com/quay/clair?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;&lt;code&gt;clair&lt;/code&gt;&lt;/a&gt;: an open-source application for parsing image contents and reporting vulnerabilities affecting the contents, done via static analysis, not at runtime.&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/anchore/grype/?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;&lt;code&gt;grype&lt;/code&gt;&lt;/a&gt;: a vulnerability scanner for container images and filesystems. It works with &lt;code&gt;syft&lt;/code&gt;, the powerful SBOM (software bill of materials) tool for container images and filesystems. For more information about SBOMs:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/why-you-need-an-sbom-software-bill-of-materials/" rel="noopener noreferrer"&gt;Why you need an SBOM (Software Bill Of Materials)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SCA: Software Composition Analysis
&lt;/h2&gt;

&lt;p&gt;Next, let's move on to a slightly different approach to vulnerability scanning: Software Composition Analysis (SCA), which automatically scans and detects vulnerabilities in components and third-party libraries.&lt;/p&gt;

&lt;p&gt;While this might sound similar to Docker image scanning, there are key differences. SCA focuses on identifying vulnerabilities in the software dependencies and libraries used within an application, ensuring that all components are secure. In contrast, as we have seen, Docker image scanning examines the entire container image, including the operating system, application code, and configurations.&lt;/p&gt;

&lt;p&gt;Let's have a look at one of the SCA tools &lt;a href="https://github.com/snyk/cli?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;&lt;code&gt;snyk&lt;/code&gt;&lt;/a&gt; to get a hands-on feeling about SCA. For macOS users, you can install &lt;code&gt;snyk&lt;/code&gt; by running:&lt;/p&gt;

&lt;p&gt;If we scan the same app from the previous section:&lt;/p&gt;

&lt;p&gt;We should get results similar to:&lt;/p&gt;

&lt;p&gt;As we can see, the issues detected by the Docker image scanning in the previous section were also detected by image scanning. Note, however, that image scanning found &lt;em&gt;more&lt;/em&gt; issues.&lt;/p&gt;

&lt;p&gt;Both Docker image scanning and SCA work by referring to known vulnerability databases, which explains the similar results.&lt;/p&gt;

&lt;p&gt;However, whereas Docker image scanning scans everything present inside an image, including OS packages, SCA is only concerned about the dependencies declared in our project (in our case, defined in the &lt;code&gt;requirements.txt&lt;/code&gt;), which is a more specific scope.&lt;/p&gt;

&lt;p&gt;At the end of the day, vulnerable dependencies (in our case, &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;cryptography&lt;/code&gt;) are reported by both Docker image scanning and SCA, but there are key differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;When the scan happens in the DevOps cycle&lt;/strong&gt;: Docker image scanning scans &lt;em&gt;built&lt;/em&gt; images. So the scan can only happen once the image is ready, in other words, towards the end of the development cycle (typically in your CI pipeline, just before deploying it in a test environment). SCA, on the other hand, scans the source code, which means SCA is implemented in the early stages of the cycle. The bottom line is that SCA allows us to discover issues earlier.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;What is the real scope of the scan?&lt;/strong&gt; As we mentioned, SCA is only concerned with how the running code could be compromised, nothing more. But this code will run in a container, which presents its own attack surface. Ultimately, the container image is the blueprint for what will run in the production environment, so it's crucial to detect both the vulnerabilities affecting the container AND the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combining the above pros and cons of both Docker image scanning and SCA, we can see that they provide complementary values at different stages. They both have their place in your automated DevSecOps workflow.&lt;/p&gt;

&lt;p&gt;Besides &lt;code&gt;snyk&lt;/code&gt;, there is another open-source project &lt;a href="https://github.com/google/osv-scanner?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;&lt;code&gt;osv-scanner&lt;/code&gt;&lt;/a&gt; that's worth a look at: It's an SCA vulnerability scanner written in Go, which uses the data provided by &lt;a href="https://osv.dev/?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;osv.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For more information on &lt;code&gt;osv-scanner&lt;/code&gt; (and open-source security in general), read this blog:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/open-source-software-security/" rel="noopener noreferrer"&gt;Open-Source Software Security&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Container Runtime Security
&lt;/h2&gt;

&lt;p&gt;So far, we've covered both Docker image scanning and SCA, which happened at the code/image level, with which we can discover the known vulnerabilities and fix them before our apps get deployed. Next, let's cover another type of container security scanning: &lt;strong&gt;container runtime security.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Container runtime security, as the name suggests, happens at the runtime level: &lt;strong&gt;it takes proactive measures and controls to protect containerized applications during the runtime phase&lt;/strong&gt;, detecting unexpected behaviour, configuration changes, and attacks in near real-time.&lt;/p&gt;

&lt;p&gt;This sounds theoretical, so let's take a look at one concrete example with &lt;a href="https://github.com/falcosecurity/falco?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;Falco&lt;/a&gt;, an open-source tool designed to detect and alert on abnormal behaviour and potential security threats in real-time.&lt;/p&gt;

&lt;p&gt;To quickly try Falco out, prepare a K8s cluster and install it with helm:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Be sure to change the last parameter from changeme to your name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a more detailed installation guide, see &lt;a href="https://falco.org/docs/getting-started/falco-kubernetes-quickstart/?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;the official documentation here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once Falco is up and running, we can simulate suspicious actions to see if the so-called "runtime security" platform can capture them. For As a first example, let's start a container and get a shell. In the real world, an attacker with shell access means the container is already compromised, as the attacker could try all kinds of things.&lt;/p&gt;

&lt;p&gt;Start a container by running: &lt;code&gt;kubectl run alpine --image alpine -- sh -c "sleep infinity"&lt;/code&gt;, then execute the uptime&lt;code&gt;command&lt;/code&gt;inside it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl exec -it alpine – sh -c "uptime"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(well, this is a harmless command, but you get the gist).&lt;/p&gt;

&lt;p&gt;If we check Falco's output, we should get an alert:&lt;/p&gt;

&lt;p&gt;Falco's base rules alert you if someone runs an interactive shell into a running container. For more standard rules, see &lt;a href="https://falco.org/docs/rules/?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;the official documentation&lt;/a&gt;. Also, you can write and customize Falco rules by yourself to secure your environment.&lt;/p&gt;

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

&lt;p&gt;In this blog, we've seen that there are several types of container vulnerabilities: hard-coded secrets, misconfigurations, vulnerabilities in the application code, and vulnerabilities in OS packages and dependencies.&lt;/p&gt;

&lt;p&gt;Luckily, most of them can be detected by tools fairly easily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;ggshield&lt;/code&gt;, &lt;code&gt;SecretScanner&lt;/code&gt; to scan for secrets&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;trivy&lt;/code&gt; to scan for misconfigurations&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;trivy&lt;/code&gt;, &lt;code&gt;clair&lt;/code&gt; and &lt;code&gt;grype&lt;/code&gt; to scan Docker images for known CVEs&lt;/li&gt;
&lt;li&gt;  SCA tools like &lt;code&gt;snyk&lt;/code&gt; and &lt;code&gt;osv-scanner&lt;/code&gt; to analyze software composition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Besides scanning code and images, we also can enhance our production environment by using container runtime security platforms such as Falco, which detects unexpected behaviour and attacks in real-time with predefined and customizable rules.&lt;/p&gt;

&lt;p&gt;As a closing thought, it's worth pointing out that container security scanning has limitations: It's not great for detecting &lt;em&gt;unknown&lt;/em&gt; vulnerabilities. &lt;strong&gt;If some vulnerabilities are not yet publicly disclosed, or if a new type of attacking behavior just emerged and it's not in the runtime security platform's rule list, they can't be detected.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, although container security scanning is necessary to secure production environments, it's important to remember that it is one layer of security and not an end-game solution to mitigate all types of threats.&lt;/p&gt;

&lt;p&gt;As a follow-up reading, you can refer to the Docker security cheat sheet, to help you understand how to run containers with the right security guardrails from the onset:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/how-to-improve-your-docker-containers-security-cheat-sheet/" rel="noopener noreferrer"&gt;Docker Security Best Practices: Cheat Sheet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this article. See you in the next piece!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>vulnerabilities</category>
      <category>security</category>
      <category>containers</category>
    </item>
    <item>
      <title>How to Handle Secrets in Jupyter Notebooks</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Wed, 10 Jul 2024 12:37:00 +0000</pubDate>
      <link>https://dev.to/gitguardian/how-to-handle-secrets-in-jupyter-notebooks-466o</link>
      <guid>https://dev.to/gitguardian/how-to-handle-secrets-in-jupyter-notebooks-466o</guid>
      <description>&lt;p&gt;With the rise of big data and machine learning, &lt;a href="https://jupyter.org/?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;project Jupyter&lt;/a&gt; is becoming increasingly popular among data scientists and machine learning engineers. Jupyter Notebooks, together with IPython, provide an interactive workflow for developing, visualizing data, and writing texts and documentation, all in a single place and stored as a single document.&lt;/p&gt;

&lt;p&gt;However, data science and machine learning projects often need to access &lt;strong&gt;third-party APIs&lt;/strong&gt;, read data from a &lt;strong&gt;data store&lt;/strong&gt;, or interact with &lt;strong&gt;cloud services&lt;/strong&gt;. This means that, just like normal code, the code in Jupyter Notebooks also needs to use &lt;strong&gt;secrets and credentials&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These notebooks are nothing more than code, and, as you know, storing plaintext credentials in code is not acceptable. If you don't know why, I encourage you to have a look here to understand &lt;a href="https://blog.gitguardian.com/why-its-urgent-to-deal-with-your-hard-coded-credentials/" rel="noopener noreferrer"&gt;why it's urgent to deal with your hard-coded secrets&lt;/a&gt;, as well as &lt;a href="https://blog.gitguardian.com/secrets-api-management/" rel="noopener noreferrer"&gt;this cheat sheet for managing and storing secrets in Git repositories.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today, we will look at different ways of securely handling secrets in Jupyter notebooks. Whether you are using Jupyter Notebook, JupyterLab, or JupyterHub, these methods work across them all. Without further ado, let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Entering Passwords on the Fly in Jupyter Notebooks: &lt;code&gt;getpass&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is probably the most intuitive method for handling secrets in Jupyter notebooks: entering secret values interactively on the fly.&lt;/p&gt;

&lt;p&gt;Luckily, we have a native solution for that: &lt;a href="https://docs.python.org/3/library/getpass.html?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;&lt;code&gt;getpass&lt;/code&gt;&lt;/a&gt;. It's been part of the Python standard library for a long time, meaning that you don't have to install anything, and it's already there. The use case is simple: it prompts you for a password when you run the cell and doesn't log this password even when printed out, so it's safe even if someone else takes a glance at your screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/content/images/2024/06/getpass.png" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KpDqP41b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.gitguardian.com/content/images/2024/06/getpass.png" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The advantage is you don't have any secret stored inside the notebook, so it's safe to push your code to any git repository. Even if the code repo is compromised, no critical information will be leaked. There are, however, a couple of downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  you will always have to enter it manually every time you run your Jupyter notebook. As you probably know, it's not unusual to run your cells dozens of times when you are developing and debugging your code. So, this can be a major burden.&lt;/li&gt;
&lt;li&gt;  you still have to store the actual value of the password somewhere. In a local file? But how do you keep the file safe and secure? What if it's lost? Where to back it up securely? And how do you share it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, although &lt;code&gt;getpass&lt;/code&gt; is a great choice for a quick start when you're just starting a simple project, but it's hardly the most convenient. Let's move on to the next choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Secrets from a File Uploaded to JupyterHub
&lt;/h2&gt;

&lt;p&gt;One of the downsides of the previous approach is that you'd have to enter the secret values manually every time you run your notebook. To avoid this, it's natural to think of a solution where secrets are stored in a file (typically, a &lt;code&gt;.env&lt;/code&gt; environment file) and loaded from the code.&lt;/p&gt;

&lt;p&gt;The Python module &lt;a href="https://pypi.org/project/python-dotenv/?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;&lt;code&gt;python-dotenv&lt;/code&gt;&lt;/a&gt; does just that: it reads key-value pairs from a &lt;code&gt;.env&lt;/code&gt; file and sets them as environment variables. This helps in the development of applications following the &lt;a href="https://12factor.net/?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;12-factor principles&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are using Jupyter Notebook or JupyterLab locally, you can create a &lt;code&gt;.env&lt;/code&gt; file locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env example
DB_PASS=hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it's simply a matter of calling &lt;code&gt;load_dotenv()&lt;/code&gt; to read the &lt;code&gt;.env&lt;/code&gt; values and making them accessible to your notebook code, just like accessing any environment variables with &lt;code&gt;os.getenv()&lt;/code&gt;. Example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/content/images/2024/06/dotenv.png" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hOi_JPkq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.gitguardian.com/content/images/2024/06/dotenv.png" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Better still, if you are using JupyterHub (a SaaS version of JupyterLab for groups of users)—or any cloud-based Jupyter service— you can upload the &lt;code&gt;.env&lt;/code&gt; file from your local computer and host them online, making it shared amongst your teammates.&lt;/p&gt;

&lt;p&gt;To do so, first, navigate to the Jupyter Notebook interface home page. Then you can add files by clicking the "Upload" button to open the file chooser modal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/content/images/2024/06/jupyterhub-upload.png" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZxJ9CB-F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.gitguardian.com/content/images/2024/06/jupyterhub-upload.png" width="749" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Compared to &lt;code&gt;getpass&lt;/code&gt;, this approach eliminates the toil of entering a password manually.&lt;/p&gt;

&lt;p&gt;However, it's still far from perfect. Using a file as a single source of trust comes with its own caveats: What if you need to update a secret in the file? What if a value is updated on a local machine but not on the online one? And how can you share the file securely? Manually creating new backups sounds like a lot of trouble.&lt;/p&gt;

&lt;p&gt;Theoretically, we have known the solution to these problems for a long time: use a &lt;strong&gt;version control system&lt;/strong&gt; like Git. But we can't store hard-coded plaintext secrets in a Git repo. Or can we? What if we encrypt the &lt;code&gt;.env&lt;/code&gt; file? Let's move on to the next method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encrypt Jupyter Notebook Secrets Files in Git
&lt;/h2&gt;

&lt;p&gt;We can't store a file full of secrets in a Git repo, except if the file is encrypted. Yet encrypting a file brings its own set of challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Where to store the key to decrypt the file?&lt;/li&gt;
&lt;li&gt;  If there is a new team member, how to allow his key to decrypt an existing file?&lt;/li&gt;
&lt;li&gt;  If I need to update some content in the encrypted file, I need to decrypt, change, and then encrypt it. That sounds like a lot of trouble.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Luckily, there is a tool that handles this overhead for us: &lt;a href="https://github.com/getsops/sops?ref=blog.gitguardian.com" rel="noopener noreferrer"&gt;&lt;code&gt;SOPS&lt;/code&gt;&lt;/a&gt;. Simply put, SOPS (short for Secrets OPerationS) is an open-source text file editor that encrypts/decrypts files automatically and supports various encryption methods, like GPG keys, AWS KMS, etc.&lt;/p&gt;

&lt;p&gt;After setting up proper encryption methods, you can open an encrypted file, edit it, and then save it encrypted again with no overhead at all. It supplements the previous &lt;code&gt;dotenv&lt;/code&gt; method because now you can store the &lt;code&gt;.env&lt;/code&gt; file safely and securely in a Git repository with minimum operational overhead when managing the file itself.&lt;/p&gt;

&lt;p&gt;For a detailed tutorial on how to use &lt;code&gt;sops&lt;/code&gt;, read &lt;a href="https://blog.gitguardian.com/a-comprehensive-guide-to-sops/" rel="noopener noreferrer"&gt;this tutorial here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;SOPS is a great tool, but it's not the ultimate solution. You still have to decrypt the file before loading it. Also, as your project grows larger, managing encryption keys for all team members can become an added overhead.&lt;/p&gt;

&lt;p&gt;That said, let's move on to secret managers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ultimate Solution: Access Secret Managers from Jupyter Notebooks
&lt;/h2&gt;

&lt;p&gt;As we can see from the previous section, storing secrets in an encrypted file is not ideal. Managing secrets is not a trivial task: We need to store them securely and use them securely; there is encryption, there is encryption at rest, encryption in transit, and encryption end-to-end; we need to do backup-restore properly... I could go on, but you get the gist.&lt;/p&gt;

&lt;p&gt;Secret managers come to the rescue: they act as a single source of truth to store secrets securely, and we can store, manage, and access secrets from our code. Secret managers also make sharing secrets among team members easy, because we can configure different permissions for different secrets and members.&lt;/p&gt;

&lt;p&gt;The bottom line is that &lt;strong&gt;thanks to secret managers, we completely remove ourselves from the overhead of managing secrets themselves, we simply use them.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Secret Managers in Jupyter Notebooks
&lt;/h3&gt;

&lt;p&gt;Most secret managers support different ways of creating secrets: you can do it in the web UI, in the CLI console, etc, and accessing the secrets takes only a couple of lines of code.&lt;/p&gt;

&lt;p&gt;Take AWS Secrets Manager as an example, with a couple of lines of code, you can easily read from it:&lt;/p&gt;

&lt;p&gt;In this way, your Jupyter notebooks contain no secret, no encryption is needed, and they can be easily shared without worrying about leaking sensitive information.&lt;/p&gt;

&lt;p&gt;If you are running Jupyter Notebook or JupyterLab locally, you simply set up your access to AWS and that's it. Accessing other secret managers from your local machine is more or less the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Cloud-Based Jupyter Service to Access Secret Managers
&lt;/h3&gt;

&lt;p&gt;If you are using JupyterHub or some cloud-based Jupyter service like &lt;strong&gt;AWS SageMaker&lt;/strong&gt;, there might be some extra configuration, but it's a one-time-only job.&lt;/p&gt;

&lt;p&gt;For example, with AWS SageMaker, you need to know the IAM role used by your SageMaker instance to set up the API key for it (which can be found in the overview of the SageMaker notebook instance of the AWS Management Console), then grant access to the AWS Secrets Manager to the SageMaker notebook role. Granted, AWS is not the easiest platform. So here are a few resources to help you navigate their services if you are new to them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  To know more about AWS IAM and security best practices, &lt;a href="https://blog.gitguardian.com/aws-iam-security-best-practices/" rel="noopener noreferrer"&gt;read this blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  To know more about how to manage IAM roles with Infrastructure as Code (IaC), read &lt;a href="https://blog.gitguardian.com/managing-aws-iam-with-terraform-part-1/" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt; and &lt;a href="https://blog.gitguardian.com/managing-aws-iam-with-terraform-part-2/" rel="noopener noreferrer"&gt;the second part of it&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;  To know more about handling secrets in AWS Secrets Manager, see &lt;a href="https://blog.gitguardian.com/handling-secrets-with-aws-secrets-manager/" rel="noopener noreferrer"&gt;this blog post here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are using Secret Manager from Google Cloud (GCP), you need to install the Secret Manager client library for Python by running &lt;code&gt;pip3 install google-cloud-secret-manager&lt;/code&gt;, then you can access the secrets with a simple piece of code. See an example &lt;a href="https://codelabs.developers.google.com/codelabs/secret-manager-python?ref=blog.gitguardian.com#6" rel="noopener noreferrer"&gt;here&lt;/a&gt;. To learn more about how to handle secrets with Google Cloud Secret Manager, read &lt;a href="https://blog.gitguardian.com/how-to-handle-secrets-with-google-cloud-secret-manager/" rel="noopener noreferrer"&gt;this blog here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For Microsoft Azure, it's more or less the same: set up permissions, install a client library, and then some simple code. For a detailed tutorial on handling secrets with Azure Key Vault, see &lt;a href="https://blog.gitguardian.com/how-to-handle-secrets-with-azure-key-vault/" rel="noopener noreferrer"&gt;this blog here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Secrets in Kaggle Notebooks
&lt;/h3&gt;

&lt;p&gt;Since many machine learners and data scientists compete on Kaggle, it's worth talking about how to handle secrets in Kaggle.&lt;/p&gt;

&lt;p&gt;Kaggle has a feature that allows you to use secrets more securely: In the "Add-ons" menu, choose "Secrets", then you can add key-value pairs, and Kaggle will store these secrets for you, acting like a secret manager:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/content/images/2024/06/kaggle-secrets.png" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hm6Rk9XQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.gitguardian.com/content/images/2024/06/kaggle-secrets.png" alt="kaggle secrets" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you need to read it from your notebook, it's only a couple of lines:&lt;/p&gt;

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

&lt;p&gt;OK, so far, we've covered a couple of different methods on how to handle secrets in Jupyter notebooks, from the simple, enter-on-the-fly method &lt;code&gt;getpass&lt;/code&gt;, all the way to the full-blown enterprise-grade secret managers.&lt;/p&gt;

&lt;p&gt;To sum up, there are a few best practices to keep in mind when handling secrets safely in Jupyter notebooks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Instead of hard-coded secrets in the code, use the &lt;code&gt;getpass&lt;/code&gt; library to securely prompt you on the fly, or use environment variables to access them.&lt;/li&gt;
&lt;li&gt;  Keep your secrets in a separate configuration file that is not visible to Git; if you have to store it in Git, store it encrypted, and use &lt;code&gt;sops&lt;/code&gt; or equivalents to minimize the overhead.&lt;/li&gt;
&lt;li&gt;  To securely use and share secrets in a big project, especially when sharing secrets across teams is necessary, secret managers are worth considering.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>datascience</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>Open-Source Software Security</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Wed, 22 May 2024 12:51:29 +0000</pubDate>
      <link>https://dev.to/gitguardian/open-source-software-security-5a60</link>
      <guid>https://dev.to/gitguardian/open-source-software-security-5a60</guid>
      <description>&lt;p&gt;On March 29, which seemed to be another normal Friday, a Microsoft developer shocked the world by revealing an XZ Utils (data-compression utilities) backdoor. This backdoor could potentially enable unauthorized access via SSH and remote code execution (read the full story &lt;a href="https://blog.gitguardian.com/the-backdoor-that-almost-compromised-ssh-security/"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;But wait a minute, because how on earth does compression have anything to do with SSH access? Short answer: dependencies. Part of the XZ Utils is a compression library &lt;code&gt;liblzma&lt;/code&gt;, which isn't used directly by OpenSSH, but Debian and several other distributions patch OpenSSH to support &lt;code&gt;systemd&lt;/code&gt; notifications, and &lt;code&gt;systemd&lt;/code&gt; links to the &lt;code&gt;libsystemd&lt;/code&gt; C library, which depends on &lt;code&gt;liblzma&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Got it? No? Don't worry, because dependency is complicated, and that's one of the reasons why the attack happened in the first place.&lt;/p&gt;

&lt;p&gt;But wait a minute again, because how could the project maintainers miss the malicious commits in the first place? It appears this backdoor was well-planned three years ago when a user started working on open-source projects. Over the years, they gained control of the project by becoming increasingly involved, gaining trust, and pressuring the founder.&lt;/p&gt;

&lt;p&gt;Luckily, the backdoor's blast radius wasn't huge because although XZ Utils is included in many Linux distributions, the malicious version hadn't been widely deployed; it was only present in development versions of major distributions. Still, the attack served as a timely wake-up call in the open-source security world.&lt;/p&gt;

&lt;p&gt;So, today, let's examine &lt;strong&gt;open-source software security&lt;/strong&gt;: what it is, why you should care, and how to improve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Introduction to Open Source Software Security
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Open Source Software Security
&lt;/h3&gt;

&lt;p&gt;I could refer to &lt;a href="https://en.wikipedia.org/wiki/Open-source_software_security?ref=blog.gitguardian.com"&gt;Wikipedia for a formal definition&lt;/a&gt; of this subject matter, but I guess it won't interest you that much. Just in case you wonder what the definition is: "Open-source software security is the measure of assurance or guarantee in the freedom from danger and risk inherent to an open-source software system."&lt;/p&gt;

&lt;p&gt;To explain it in my own words, open-source software security is two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  the risks that come with third-party, open-source software;&lt;/li&gt;
&lt;li&gt;  the tools and measures taken to improve open-source software's security.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why You Should Care about Open Source Software Security
&lt;/h3&gt;

&lt;p&gt;Although the XZ Utils backdoor story sounds terrifying, many people are actually pretty confident about open-source software security for some reason.&lt;/p&gt;

&lt;p&gt;You may think you don't use open-source software, so you don't care about its security. But the fact is, you do. Whether you like to admit it or not, &lt;a href="https://www.linuxfoundation.org/blog/blog/a-summary-of-census-ii-open-source-software-application-libraries-the-world-depends-on?ref=blog.gitguardian.com"&gt;as much as 90% of code in any modern software is open-source&lt;/a&gt;. This is especially true in cloud-native time, where almost all apps rely on some open-source components.&lt;/p&gt;

&lt;p&gt;Or, you might be pretty confident that your applications don't have any libraries with known vulnerabilities.&lt;/p&gt;

&lt;p&gt;I know I was, until a minute ago, because out of curiosity just now, when writing this very paragraph, I ran a check on a project I have been working on recently, only to find 6 known vulnerabilities. There was no automated check in the project to make sure there weren't any known vulnerabilities, either. God, I wish I was making this up just for the sake of this article, but unfortunately, it is very real.&lt;/p&gt;

&lt;p&gt;And I'm not alone, because &lt;a href="https://www.sonatype.com/state-of-the-software-supply-chain/introduction?ref=blog.gitguardian.com"&gt;according to a survey done by Sonatype&lt;/a&gt;, as much as two-thirds of the surveyed users feel confident that their applications do not rely on known vulnerable libraries, despite 10% of respondents reporting their organizations had security breaches due to open source vulnerabilities in the past year.&lt;/p&gt;

&lt;p&gt;Starting to feel a bit worried? Here are more statistics to up your anxiety levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  1 in 8 open-source downloads has known risks.&lt;/li&gt;
&lt;li&gt;  245,000 malicious packages were discovered in 2023 alone. If you don't know that's "a lot" or "maybe not so much compared to previous years," let me put it this way: it was twice the number of previous years &lt;em&gt;combined&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;  18.6% of open source projects across Java and JavaScript that were maintained in 2022 are no longer maintained today, just one year later. How can you trust something when it is not even actively maintained? What's more, how can you know if something you depend on is still maintained?&lt;/li&gt;
&lt;li&gt;  If you think these numbers are already frightening, think this: open source software is growing rapidly - with just Java (Maven), JavaScript (npm), and Python (PyPI) three languages/package managers, the total number of projects is 3.6 million, with 54 million different versions, requested 3.8 &lt;em&gt;trillion&lt;/em&gt; times in 2023 alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OK, now that I think I have scared you enough and caught your attention, next let's take a look at open-source software dependencies, which only make the open-source software security matter worse.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complexity of Open Source Dependencies
&lt;/h2&gt;

&lt;p&gt;As mentioned in the previous section, you only write 10% code of your application, and the other 90% are actually dependencies - open-source components. The situation is more or less the same if you take a closer look at one of your open-source dependencies: the author probably also didn't write 100% of their code but used many dependencies.&lt;/p&gt;

&lt;p&gt;Let's say you are writing a simple Python web application using Flask. If we have a look at the dependencies of Flask itself, it depends on 6 other packages:&lt;/p&gt;

&lt;p&gt;The list isn't so long, but it's 1 package depending on 6. And let's not forget that Flask is a micro web framework—emphasis on &lt;em&gt;micro&lt;/em&gt;—meaning it doesn't require particular tools or libraries. The same can't be said for many other bigger frameworks or even platforms. For example, you are creating a state-of-the-art machine learning model using TensorFlow. It seems you only have TensorFlow as your only dependency, but under the hood of TensorFlow, it depends on:&lt;/p&gt;

&lt;p&gt;You know TensorFlow depends on Keras since you use it very often. But are you even aware that Keras, in turn, depends on &lt;code&gt;rich&lt;/code&gt;, which depends on &lt;code&gt;markdown-it-py&lt;/code&gt;, which then depends on &lt;code&gt;mdurl&lt;/code&gt;? Do you know what &lt;code&gt;mdurl&lt;/code&gt; actually is? I don't if I'm being honest.&lt;/p&gt;

&lt;p&gt;If the dependency tree looks neat, let's convert it to a graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UO-3yP-v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.gitguardian.com/content/images/2024/04/tensor-flow-dependency-graph--1-.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UO-3yP-v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.gitguardian.com/content/images/2024/04/tensor-flow-dependency-graph--1-.png" alt="TensorFlow dependency graph" width="800" height="748"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a colloquial term in software called "dependency hell," where users' installed software packages don't work because they depend on specific versions of other software packages.&lt;/p&gt;

&lt;p&gt;The main software relies on a multitude of large software libraries. It might depend on product A, but A relies on B to function, and B needs C to work properly. Then there are conflicting programs, such as app X requiring some lib v1, but app Y requires the same lib in v2. There could even be circular dependencies.&lt;/p&gt;

&lt;p&gt;Now, we don't have to work these out since in most languages, such as Python, Go, JavaScript, etc., there are package managers that do this for us. But this doesn't change the fact that our dependency depends on other dependencies which are indirect dependencies to our project, and this is known as "transitive dependencies."&lt;/p&gt;

&lt;p&gt;This could be a special concern because they are less visible to us, security tools, and audits. The nature of the complicated dependencies means that there is a chance that your project's direct dependencies are fine, but their dependencies contain CVEs or malicious code.&lt;/p&gt;

&lt;p&gt;Although many reasons could lead to vulnerabilities in open-source software, such as inconsistent quality, unsupported code/unmaintained packages, etc., transitive dependencies by far caused the most trouble. According to the &lt;a href="https://www.endorlabs.com/learn/state-of-dependency-management-2023?ref=blog.gitguardian.com"&gt;State of Dependency Management report&lt;/a&gt; done by Station 9, the Endor Labs research team, 95% of all vulnerabilities are found in transitive dependencies instead of direct dependencies, making it extremely difficult for developers to assess the real impact of these issues, or whether they're even reachable.&lt;/p&gt;

&lt;p&gt;I hope the open source situation doesn't scare you away from open source and lead you to move to proprietary software. Some argue that open source software is less secure because of the inconsistent quality of different contributors, human errors, etc., but the very same reasons that cause vulnerabilities in open source can exist in proprietary software as well, and proprietary software can include vulnerabilities.&lt;/p&gt;

&lt;p&gt;It's not all bad news about open source software, though, because according to the same State of the Software Supply Chain report done by Sonatype:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  96% of downloaded vulnerable releases have a fixed version available.&lt;/li&gt;
&lt;li&gt;  For every nonoptimal component upgrade that could potentially cause security risks, there are 10 superior versions of components available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We just need to figure out how to improve security with the right tools. So, next, let's look at some security tools that can help improve open-source software security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vulnerability Scanning
&lt;/h3&gt;

&lt;p&gt;To improve the security of our projects, let's start with the vulnerabilities themselves. We need to know if there are any known CVEs in them before we can fix them. So, the first step is to get some visibility on the Known CVEs, and luckily, there are some very good tools for this.&lt;/p&gt;

&lt;p&gt;One of the most popular open-source security scanners is &lt;a href="https://github.com/aquasecurity/trivy?ref=blog.gitguardian.com"&gt;trivy&lt;/a&gt; by &lt;a href="https://www.aquasec.com/?ref=blog.gitguardian.com"&gt;aquasec&lt;/a&gt;. It's an easy-to-use and fast CLI tool written in Golang that can scan container images, file systems, remote git repositories, and more. Besides known CVEs, it can also scan for secrets and misconfigurations. For Mac users, you can simply install it by &lt;code&gt;brew install trivy&lt;/code&gt;, then &lt;code&gt;trivy fs --scanners vuln myproject/&lt;/code&gt; to scan for vulnerabilities in your project. And trivy can be integrated with many popular platforms and applications, such as your CI pipelines or even with your IDE.&lt;/p&gt;

&lt;h3&gt;
  
  
  SBOM, and Software Composition Analysis
&lt;/h3&gt;

&lt;p&gt;Since we heavily rely on dependencies, most of which are open-source components, it's important to know what dependencies are there. And, open-source packages, especially small ones, are usually maintained by a small team with only a few developers (or even a single developer), if they are maintained at all. Developers of open-source packages do not commit to maintaining the software and can decide to stop maintaining them at any time, for any reason. If it happens, there will be no one updating the package to eliminate known CVEs. Therefore, organizations must be able to inventory open-source components.&lt;/p&gt;

&lt;p&gt;SBOM - Software Bill of Materials, comes into play: It has emerged as a critical component in software security, providing critical visibility into software components and supply chains, and helping identify and avoid vulnerabilities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/why-you-need-an-sbom-software-bill-of-materials/"&gt;Why you need an SBOM (Software Bill Of Materials)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Trivy can also generate SBOM in different formats, and to do so, it's one simple command: &lt;code&gt;trivy fs --format cyclonedx --output result.json /app/myproject&lt;/code&gt;. There is another CLI tool and library for generating a Software Bill of Materials from container images and filesystems, which is &lt;a href="https://github.com/anchore/syft?ref=blog.gitguardian.com"&gt;&lt;code&gt;syft&lt;/code&gt;&lt;/a&gt;. It's easy to install (for Mac users, &lt;code&gt;brew install syft&lt;/code&gt;), supports many ecosystems such as JavaScript, Python, Go, Ruby, etc., and can be integrated with CI. For more on SBOM, see &lt;a href="https://blog.gitguardian.com/what_is_sbom/"&gt;this blog post here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Generating SBOM alone isn't enough: we need to make sure that what's in the SBOM list (our dependencies) is OK. We need to scan the dependencies to detect vulnerabilities, and this is known as SCA—Software Composition Analysis—the process of analyzing dependencies to determine if they are affected by known security vulnerabilities.&lt;/p&gt;

&lt;p&gt;One tool for this on the rise is &lt;a href="https://osv.dev/?ref=blog.gitguardian.com"&gt;OSV&lt;/a&gt;, a database of open-source vulnerabilities built by Google in 2021. It has a CLI tool &lt;a href="https://github.com/google/osv-scanner?ref=blog.gitguardian.com"&gt;&lt;code&gt;osv-scanner&lt;/code&gt;&lt;/a&gt; that serves as the frontend to the OSV database, and you can install it with a simple command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;go install github.com/google/osv-scanner/cmd/osv-scanner@v1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then, your project's list of dependencies and the vulnerabilities that affect them is only one command away:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;osv-scanner—r path/to/your/project&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;OSV also provides API access where you can query vulnerabilities for a particular project at a given commit hash or version, and currently, it's not rate-limited yet.&lt;/p&gt;

&lt;p&gt;Finally, GitGuardian recently integrated a SCA module into their code security platform. It scans your apps' dependencies to detect vulnerabilities, and allows you to prioritize incidents by criticality:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fLCCGoYY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.gitguardian.com/content/images/2024/03/sources-list-and-details.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fLCCGoYY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.gitguardian.com/content/images/2024/03/sources-list-and-details.gif" alt="Scan your dependencies for critical vulnerabilities" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It also allows you to generate per project SBOM really easily:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rWwQi_6I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.gitguardian.com/content/images/2024/03/dependencies-generate-sboms.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rWwQi_6I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://blog.gitguardian.com/content/images/2024/03/dependencies-generate-sboms.gif" alt="Combine multiple SBOMs into one report for complete visibility over your apps" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out the complete features &lt;a href="https://docs.gitguardian.com/sca/home?ref=blog.gitguardian.com"&gt;here&lt;/a&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  Licensing
&lt;/h3&gt;

&lt;p&gt;Despite being open-source, most open-source applications and packages come with their own usage licenses, describing how you can use the code. Risks could occur if how you use it doesn't match its intended purposes and usages, and a single dependency component could violate laws or requirements the company needs to follow.&lt;/p&gt;

&lt;p&gt;So, understanding different types of licenses so that the code is used compliantly, and this is no trivial effort since it requires knowledge of different types of licenses and requirements of the project throughout the software development life cycle. Given the complicated nature of dependencies, licenses of some dependency components could even be incompatible with each other, making the matter even more grim.&lt;/p&gt;

&lt;p&gt;For container license security, trivy scans any container image for license files and offers an opinionated view on the risk, and by default, trivy scans licenses for packages installed by &lt;code&gt;apk&lt;/code&gt;, &lt;code&gt;apt-get&lt;/code&gt;, &lt;code&gt;dnf&lt;/code&gt;, &lt;code&gt;npm&lt;/code&gt;, &lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;gem&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Another tool that can help scan for license issues is &lt;a href="https://fossa.com/?ref=blog.gitguardian.com"&gt;fossa&lt;/a&gt;. It has a free plan which is limited to 5 projects. You can install the CLI by &lt;code&gt;brew update &amp;amp;&amp;amp; brew install --cask fossa&lt;/code&gt;, getting an API key from the platform and setting it in an environment variable, then simply run &lt;code&gt;fossa analyze&lt;/code&gt; in the root directory of your project. It shows direct and indirect licenses used in the project and flags potential issues. For example, one dependency might declare itself with one license, but it contains copied open-source code declared with another license.&lt;/p&gt;

&lt;p&gt;PS: GitGuardian SCA also &lt;a href="https://docs.gitguardian.com/sca/dependencies-and-sboms?ref=blog.gitguardian.com"&gt;allows to easily keep an eye on the open-source licences&lt;/a&gt; in use in your codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As software development increasingly relies on open-source components, understanding the complexity and potential risks becomes crucial. The transitive dependencies nature of open source presents a significant challenge because it amplifies the potential for vulnerabilities within the software supply chain.&lt;/p&gt;

&lt;p&gt;Fortunately, various tools and techniques, such as vulnerability scanning, license analysis, and software composition analysis, are available to mitigate these risks. By proactively addressing security concerns and adopting best practices, developers and organizations can safeguard their projects and maintain the integrity of their software ecosystem.&lt;/p&gt;

&lt;p&gt;Embracing open-source software security is not only a responsibility but also an opportunity to build trust and collaboration in the software industry.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Which is better for security, open source or proprietary software?
&lt;/h3&gt;

&lt;p&gt;Open-source software is not less secure than proprietary code, and a commercial license doesn't guarantee security: Both can include vulnerabilities leading to security issues.&lt;/p&gt;

&lt;p&gt;With proprietary software, the only choice is to trust the vendor; with open-source projects, you should not unquestioningly trust community-validated code. And since open source is transparent about potential vulnerabilities, you can make an effort on your part and improve its security.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you make open-source software secure?
&lt;/h3&gt;

&lt;p&gt;Scan for vulnerabilities, inventory your project, map your open source dependencies to known security vulnerabilities, and use automation to continuously monitor for new risks.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between Software Composition Analysis (SCA) and Open Source Software (OSS)?
&lt;/h3&gt;

&lt;p&gt;Open-source software (OSS) is computer software that is released under a license in which the copyright holder grants users the rights to use, study, change, and distribute the software and its source code to anyone and for any purpose. Software composition analysis (SCA) is the practice of analyzing custom-built software applications to detect embedded open-source software and detect if they are up-to-date, contain vulnerabilities, or have licensing requirements.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>security</category>
      <category>learning</category>
    </item>
    <item>
      <title>The New Frontier in Cybersecurity: Embracing Security as Code</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Tue, 26 Dec 2023 12:37:44 +0000</pubDate>
      <link>https://dev.to/gitguardian/the-new-frontier-in-cybersecurity-embracing-security-as-code-o15</link>
      <guid>https://dev.to/gitguardian/the-new-frontier-in-cybersecurity-embracing-security-as-code-o15</guid>
      <description>&lt;p&gt;Security as Code (SaC) is a term often used with DevSecOps, but what does it mean exactly? Learn best practices and key components for a more secure and efficient development process.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Used to Handle Security
&lt;/h2&gt;

&lt;p&gt;A few years ago, I was working on a completely new project for a Fortune 500 corporation, trying to bring a brand new cloud-based web service to life simultaneously in 4 different countries in the EMEA region, which would later serve millions of users.&lt;/p&gt;

&lt;p&gt;It took me and my team two months to handle everything: cloud infrastructure as code, state-of-the-art CI/CD workflows, containerized microservices in multiple environments, frontend distributed to CDN, and tests passing in the staging environment. We were so prepared that we could go live immediately with just one extra click of a button. And we still had a whole month before the planned release date.&lt;/p&gt;

&lt;p&gt;I know, things looked pretty good for us; until they didn't: because it was precisely at that moment a "security guy" stepped in out of nowhere and caused us two whole weeks.&lt;/p&gt;

&lt;p&gt;Of course, the security guy. I knew vaguely that they were from the same organization but maybe a different operational unit. I also had no idea that they were involved in this project before they showed up. But I could make a good guess: writing security reports and conducting security reviews, of course. What else could it be?&lt;/p&gt;

&lt;p&gt;After finishing those reports and reviews, I was optimistic: "We still have plenty of time," I told myself. But it wasn't long before my thought was rendered untrue by another unexpected development: an external QA team jumped in and started security tests. Oh, by the way, to make matters worse, the security tests were manual.&lt;/p&gt;

&lt;p&gt;It was two crazy weeks of fixing and testing and rinsing and repeating. The launch was delayed, and even months after the big-bang release, the whole team was still miserable: busy on-call, fixing issues, etc.&lt;/p&gt;

&lt;p&gt;Later, I would continue to see many other projects like this one, and I am sure you also have similar experiences. This is actually how we used to do security. Everything is smooth until it isn't because we traditionally tend to handle the security stuff at the end of the development lifecycle, which adds cost and time to fix those discovered security issues and causes delays.&lt;/p&gt;

&lt;p&gt;Over the years, software development has evolved to agile and automatic, but how we handle security hasn't changed much: security isn't tackled until the last minute. I keep asking myself: what could've been done differently?&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding DevSecOps and Security as Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DevSecOps: Shift Security to the Left of the SDLC
&lt;/h3&gt;

&lt;p&gt;Based on the experience of the project above (and many other projects), we can easily conclude why the traditional way of handling security doesn't always work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Although security is an essential aspect of software, we put that aspect at the very end of the software development lifecycle (SDLC). When we only start handling the critical aspect at the very end, it's likely to cause delays because it might cause extra unexpected changes and even rework.&lt;/li&gt;
&lt;li&gt;Since we tend to do security only once (at least we hope so), in the end, we usually wouldn't bother automating our security tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To make security great again, we make two intuitive proposals for the above problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shift left&lt;/strong&gt;: why does security work at the end of the project, risking delays and rework? To change this, we want to integrate security work into every stage of the SDLC: shifting from the end (right side) to the left (beginning of the SDLC, i.e., planning, developing, etc.), so that we can discover potential issues earlier when it's a lot easier and takes much less effort to fix or even rework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt;: why do we do security work manually, which is time-consuming, error-prone, and hard to repeat? Automation comes to the rescue. Instead of manually defining policies and security test cases, we take a code-based approach that can be automated and repeated easily.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rbnAoF0---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tlu9vs8oiwsoo5wk16j7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rbnAoF0---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tlu9vs8oiwsoo5wk16j7.png" alt="shift left" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we combine the "shift left" part and the "automation" part, bam, we get DevSecOps: a practice of integrating security at every stage, throughout the SDLC to accelerate delivery via automation, collaboration, fast feedback, and incremental, iterative improvements.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Security as Code (SaC)?
&lt;/h3&gt;

&lt;p&gt;Shifting security to the left is more of a change in the mindset; what's more important is the automation, because it's the driving force and the key to achieving a better security model: without proper automation, it's difficult, if not impossible at all, to add security checks and tests at every stage of the SDLC without introducing unnecessary costs or delays. &lt;strong&gt;And this is the idea of Security as Code:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Security as Code (SaC) is the practice of building and integrating security into tools and workflows by identifying places where security checks, tests, and gates may be included.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For those tests and checks to run automatically on every code commit, we should define security policies, tests, and scans as code in the pipelines. Hence, security "as code".&lt;/p&gt;

&lt;p&gt;From the definition, we can see that Security as Code is part of DevSecOps, or rather, &lt;strong&gt;it's how we can achieve DevSecOps&lt;/strong&gt;. The key differences between Security as Code/DevSecOps and the traditional way of handling security are shifting left and automation: we try to define security in the early stages of SDLC and tackle it in every stage automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Importance and Benefits of Security as Code (SaC)
&lt;/h3&gt;

&lt;p&gt;The biggest benefit of Security as Code, of course, is that it's accelerating the SDLC. How so? I'll talk about it from three different standpoints:&lt;/p&gt;

&lt;p&gt;First of all, &lt;strong&gt;efficiency-boosting&lt;/strong&gt;: security requirements are defined early at the beginning of a project when shifting left, which means there won't be major rework in the late stage of the project with clearly defined requirements in the first place, and there won't be a dedicated security stage before the release. With automated tests, developers can make sure every single incremental code commit is secure.&lt;/p&gt;

&lt;p&gt;Secondly, codified security allows &lt;strong&gt;repeatability, reusability, and consistency&lt;/strong&gt;. Development velocity is increased by shorter release cycles without manual security tests; security components can be reused at other places and even in other projects; changes to security requirements can be adopted comprehensively in a "change once, apply everywhere" manner without repeated and error-prone manual labor.&lt;/p&gt;

&lt;p&gt;Last, but not least, &lt;strong&gt;it saves a lot of time, resources, and even money&lt;/strong&gt; because, with automated checks, potential vulnerabilities in the development and deployment process will be caught early on in the SDLC when remediating the issues has a much smaller footprint, cost- and labor-wise.&lt;/p&gt;

&lt;p&gt;To learn more about how adding security into DevSecOps accelerates the SDLC in every stage read &lt;a href="https://blog.gitguardian.com/how-adding-security-into-devops-accelerates-the-sdlc-pt-1/"&gt;this blog here&lt;/a&gt; and &lt;a href="https://blog.gitguardian.com/how-adding-security-into-devops-accelerates-the-sdlc-pt-2-2/"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Components of Security as Code
&lt;/h2&gt;

&lt;p&gt;The components of Security as Code for application development are automated security tests, automated security scans, automated security policies, and IaC security.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Automated security tests&lt;/strong&gt;: automate complex and time-consuming manual tests (and even penetration tests) via automation tools and custom scripts, making sure they can be reused across different environments and projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated security scans&lt;/strong&gt;: we can integrate security scans CI/CD pipelines so that they can be triggered automatically and can be reused across different environments and projects. We can do all kinds of scans and analyses here, for example, static code scans, dynamic analyses, and vulnerability scans against known vulnerabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated security policies&lt;/strong&gt;: we can define different policies as code using a variety of tools, and integrate the policy checks with our pipelines. For example, we can define access control in RBAC policies for different tools; we can enforce policies in microservices, Kubernetes, and even CI/CD pipelines (for example, with the &lt;a href="https://www.openpolicyagent.org/?ref=blog.gitguardian.com"&gt;Open Policy Agent&lt;/a&gt;). To know more about Policy as Code and Open Policy Agent, read &lt;a href="https://blog.gitguardian.com/what-is-policy-as-code-an-introduction-to-open-policy-agent/"&gt;this blog here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IaC security&lt;/strong&gt;: in modern times, we often define our infrastructure (especially cloud-based) as code (IaC) and deploy it automatically. We can use IaC to ensure the same security configs and best practices are applied across all environments, and we can use Security as Code measures to make sure the infrastructure code itself is secure. To do so, we integrate security tests and checks within the IaC pipeline, as with the &lt;a href="https://github.com/GitGuardian/ggshield?ref=blog.gitguardian.com"&gt;ggshield&lt;/a&gt; security scanner for your Terraform code.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Best Practices for Security as Code
&lt;/h2&gt;

&lt;p&gt;With the critical components of Security as Code sorted out, let's move on to a few best practices to follow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security-First Mindset for Security as Code/DevSecOps
&lt;/h3&gt;

&lt;p&gt;First of all, since Security as Code and DevSecOps are all about shifting left, which is not only a change of how and when we do things, but more importantly, a change of the mindset, the very first best practice for Security as Code and DewvSecOps is to build (or rather, transition into) a security-first mindset.&lt;/p&gt;

&lt;p&gt;At Amazon, there is a famous saying, which is "Security is job zero". Why do we say that? Since it's important, if you only start dealing with it in the end, there will be consequences. Similar to writing tests, trying to fix issues or even rework components because of security issues found at the end of a project's development lifecycle can be orders of magnitude harder compared to when the code is still fresh, the risk just introduced, and no other components relying on it yet.&lt;/p&gt;

&lt;p&gt;Because of its importance and close relationship with other moving parts, we want to shift security to the left, and the way to achieve that is by transitioning into a security-first mindset.&lt;/p&gt;

&lt;p&gt;If you want to know more about DevSecOps and why "adding" security into your SDLC doesn't slow down, but rather speeds up things, I've got another &lt;a href="https://blog.gitguardian.com/devsecops-introduction-accelerating-software-development/"&gt;blog here&lt;/a&gt; detailing exactly that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Reviews + Automated Scanning
&lt;/h3&gt;

&lt;p&gt;Security as Code is all about automation, so it makes sense to start writing those automated tests as early as possible, so that they can be used at the very beginning of the SDLC, acting as checks and gates, accelerating the development process. For example, we can automate SAST/DAST (static application security testing and dynamic application security testing) with well-known tools (for example, SonarQube, Synopsys, etc.) into our CI/CD pipelines so that everything runs automatically on new commits.&lt;/p&gt;

&lt;p&gt;One thing worth pointing out is that &lt;a href="https://dev.toSAST%20+%20DAST%20isn't%20enough"&gt;SAST + DAST isn't enough&lt;/a&gt;: while static and dynamic application tests are the cornerstones of security, there are blind spots. For example, one hard-coded secret in the code is more than enough to compromise the entire system (to know more about this topic, read &lt;a href="https://blog.gitguardian.com/why-sast-dast-cant-be-enough/"&gt;this blog here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Two approaches are recommended as complements to the automated security tests. First of all, &lt;strong&gt;regular code reviews help&lt;/strong&gt;. It's always nice to have another person's input on the code change because the four-eyes principle can be useful. For more tips on conducting secure code reviews, read &lt;a href="https://blog.gitguardian.com/secure-code-review-cheat-sheet-included/"&gt;this blog here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, code reviews can only be so helpful to a certain extent, because, first of all, humans still tend to miss mistakes, and second of all, during code reviews, we mainly focus on the diffs rather than what's already in the code base.&lt;/p&gt;

&lt;p&gt;As a complement to code reviews, having &lt;strong&gt;security scanning and detection&lt;/strong&gt; in place also helps. To know more about secrets in source code and how secrets sprawl works, see &lt;a href="https://blog.gitguardian.com/secret-sprawl/"&gt;this blog here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous Monitoring, Feedback Loops, Knowledge Sharing
&lt;/h3&gt;

&lt;p&gt;Having automated security policies as checks can only help so much if the automated security policies themselves aren't of high quality, or worse, the results can't reach the team.&lt;/p&gt;

&lt;p&gt;Thus, &lt;strong&gt;creating a feedback loop&lt;/strong&gt; to continuously deliver the results to the developers and monitor the checks is critical, too. It's even better if the monitoring can create logs automatically and display the result in a dashboard to make sure no security risk is found, no sensitive data or secret is shared, and developers can find breaches early so that they can remediate issues early.&lt;/p&gt;

&lt;p&gt;Knowledge sharing and continuous learning can also be helpful, allowing developers to learn best practices during the coding process. &lt;a href="https://blog.gitguardian.com/from-code-to-cloud-security-for-developers-snyk/"&gt;Here is a cheat sheet&lt;/a&gt; for developers to do security in different stages of the SDLC in the cloud-native era.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security as Code: What You Should Keep in Mind
&lt;/h2&gt;

&lt;p&gt;Besides the best practices above, there are a few other considerations and challenges when putting Security as Code into action:&lt;/p&gt;

&lt;p&gt;First of all, we need to &lt;strong&gt;balance speed and security&lt;/strong&gt; when implementing Security as Code/DevSecOps. Yes, I know, earlier that we mentioned how security is job zero and how doing DevSecOps actually speeds things up, but still, implementing Security as Code in the early stages of the SDLC &lt;strong&gt;still costs some time&lt;/strong&gt; upfront, and this is one of the balances we need to consider carefully, to which, unfortunately there is no one-size-fits-all answer. In general, for big and long-lasting projects, paying those upfront costs will most likely be very beneficial in the long run, but it could be worth a second thought if it's only a one-sprint or even one-day minor task with relatively clear changes and confined attack surface.&lt;/p&gt;

&lt;p&gt;Secondly, to effectively adopt Security as Code, the &lt;strong&gt;skills and gaps&lt;/strong&gt; in the team need to be identified, and continuous knowledge sharing and learning are required. This can be challenging in the DevOps/DevSecOps/Cloud world, where things evolve fast with new tools and even new methodologies emerging from time to time. Under this circumstance, it's critical to keep up with the pace, &lt;strong&gt;identify what could potentially push engineering productivity&lt;/strong&gt; and security to another level, figure out what needs to be learned because we've only got limited time and can't learn everything, and learn those highly-prioritized skills quickly.&lt;/p&gt;

&lt;p&gt;Last but not least, keep a close eye on newly discovered security threats and changes regarding security regulations, which are also evolving with time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusions and Next Steps
&lt;/h2&gt;

&lt;p&gt;Security as Code isn't just a catchphrase; it requires continuous effort to make the best out of it: a change of mindset, continuous learning, new skills, new tooling, automation, and collaboration.&lt;/p&gt;

&lt;p&gt;As a recap, here's a list of the key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated security tests&lt;/li&gt;
&lt;li&gt;Automated security scans&lt;/li&gt;
&lt;li&gt;Automated security policies&lt;/li&gt;
&lt;li&gt;IaC security&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here's a list of the important aspects when adopting it proactively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security-first, mindset change, shift left&lt;/li&gt;
&lt;li&gt;Automated testing/scanning with regular code reviews&lt;/li&gt;
&lt;li&gt;Continuous feedback and learning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At last, let's end this article with an FAQ list on Security as Code and DevSecOps.&lt;/p&gt;




&lt;h2&gt;
  
  
  F.A.Q.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Security as Code (SaC)?
&lt;/h3&gt;

&lt;p&gt;Security as Code (SaC) is the practice of building and integrating security into tools and workflows by defining security policies, tests, and scans as code. It identifies places where security checks, tests, and gates may be included in pipelines without adding extra overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the main purpose of Security as Code?
&lt;/h3&gt;

&lt;p&gt;The main purpose of Security as Code is to boost the SDLC by increasing efficiency and saving time and resources while minimizing vulnerabilities and risks. This approach integrates security measures into the development process from the start, rather than adding them at the end.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the relationship between Security as Code and DevSecOps?
&lt;/h3&gt;

&lt;p&gt;DevSecOps is achieved by shifting left and automation; Security as Code handles the automation part. Security as Code is the key to DevSecOps.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between DevSecOps and secure coding?
&lt;/h3&gt;

&lt;p&gt;DevSecOps focuses on automated security tests and checks, whereas secure coding is the practice of developing computer software in such a way that guards against the accidental introduction of security vulnerabilities. Defects, bugs, and logic flaws are consistently the primary cause of commonly exploited software vulnerabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Infrastructure as Code (IaC) security?
&lt;/h3&gt;

&lt;p&gt;IaC security uses the security as code approach to enhance infrastructure code. For example, consistent cloud security policies can be embedded into the infrastructure code itself and the pipelines to reduce security risks.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>devsecops</category>
      <category>securityascode</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>How to Handle Secrets in Helm</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Thu, 16 Nov 2023 16:17:04 +0000</pubDate>
      <link>https://dev.to/gitguardian/how-to-handle-secrets-in-helm-1kib</link>
      <guid>https://dev.to/gitguardian/how-to-handle-secrets-in-helm-1kib</guid>
      <description>&lt;p&gt;Kubernetes (K8s), an open-source container orchestration system, has become the de-facto standard for running containerized workloads thanks to its scalability and resilience.&lt;/p&gt;

&lt;p&gt;Although K8s has the capabilities to streamline deployment processes, the actual deployment of applications can be cumbersome, since deploying an app to a K8s cluster typically involves managing multiple K8s manifests (like Deployment, Service, ConfigMap, Secret, Ingress, etc.) in YAML format. This isn't ideal because it introduces additional operational overhead due to the increased number of files for one app. Moreover, it often leads to duplicated, copy-pasted sections of the same app across different environments, making it more susceptible to human errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://helm.sh/?ref=blog.gitguardian.com"&gt;Helm&lt;/a&gt;, a popular package manager for Kubernetes, is designed to solve these deployment issues and help us manage K8s apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Helm Charts and Helm Secrets
&lt;/h2&gt;

&lt;p&gt;Helm provides a straightforward way to define, install, and upgrade apps on K8s clusters. It is based on reusable templates called Helm charts, which encapsulate all the necessary K8s manifests, configurations, and dependencies into one single package, making it a whole lot easier to consistently version-control, publish/share, and deploy apps.&lt;/p&gt;

&lt;p&gt;Since most apps rely on configurations, Helm charts often rely on ConfigMaps and Secrets (for sensitive information) to pass values to the apps as environment variables, following &lt;a href="https://12factor.net/?ref=blog.gitguardian.com"&gt;the Twelve-Factor App methodology&lt;/a&gt;. &lt;strong&gt;However, handling secrets in Helm can be challenging due to security concerns and collaboration/access control reasons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Security concerns: Secrets contain sensitive information such as passwords, API keys, and database credentials. Managing secrets securely is crucial to protect sensitive data from unauthorized access. Helm must ensure that secrets are properly encrypted, stored, and transmitted.&lt;/li&gt;
&lt;li&gt;  Collaboration/access control: Helm promotes collaboration among teams by sharing charts and configurations. However, if secrets are included in these shared charts, controlling access becomes challenging. Ensuring that only authorized individuals can access and modify secrets is crucial for maintaining security and compliance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this blog, we aim to provide comprehensive solutions to solve these challenges once and for all. Here's what you can expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Hands-on Tutorials: We will guide you through practical tutorials that demonstrate the usage of specialized tools such as the &lt;code&gt;helm-secrets&lt;/code&gt; plugin and the External Secrets Operator. These tutorials will equip you with the knowledge and skills to effectively manage secrets in Helm deployments.&lt;/li&gt;
&lt;li&gt;  Integration with CI/CD: Helm secrets are also a critical aspect of CI/CD pipelines. We will explore the possibilities, pros, and cons of CI/CD integrations, making sure Helm secrets and security are handled not only in manual deployment but also in automated workflows.&lt;/li&gt;
&lt;li&gt;  Secrets Rotation: Secrets rotation is a necessary security practice. We will introduce a tool that simplifies the redeployment of apps when secrets rotation occurs.&lt;/li&gt;
&lt;li&gt;  Tool Comparison and FAQs: To assist you in selecting the right tool for your specific needs, we will list the pros and cons and a flowchart to help you decide. Additionally, we will address some frequently asked questions to further clarify any doubts you may have.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without further ado, let's dive in and explore the solutions to revolutionize your secrets management within Helm charts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial: The helm-secrets Plugin
&lt;/h2&gt;

&lt;p&gt;As mentioned in the previous section, managing secrets for Helm charts can be challenging because Helm chart Secrets contain sensitive information, and it's difficult to control access between team members.&lt;/p&gt;

&lt;p&gt;With that in mind, there are two (and maybe only two) approaches to managing secrets in Helm charts: either we store sensitive information in Helm charts encrypted, or we don't store them in the charts at all:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Encrypt the Secrets values in the values file of the Helm charts. This way, we ensure the chart is safe to share and check into version control without worrying about leaking sensitive information. We still need to figure out a control mechanism to share access (decrypt the values) among team members.&lt;/li&gt;
&lt;li&gt;  Do not store the Secrets values in the Helm charts at all. This way, the sensitive information isn't in the Helm charts at all, so it's completely secure. Then we need to figure out a way to actually deploy Secrets into K8s clusters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A Quick Introduction to the helm-secrets Plugin
&lt;/h3&gt;

&lt;p&gt;TL;DR: the &lt;a href="https://github.com/jkroepke/helm-secrets?ref=blog.gitguardian.com"&gt;&lt;code&gt;helm-secrets&lt;/code&gt;&lt;/a&gt; plugin can work in both of the two above-mentioned methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  encrypting sensitive information in the Helm charts and decrypting the values on the fly.&lt;/li&gt;
&lt;li&gt;  storing secrets elsewhere (like in a secrets manager) and injecting them into the Helm charts when Helm deployments happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more about how to use major cloud providers' secrets manager services, read my blogs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://blog.gitguardian.com/handling-secrets-with-aws-secrets-manager/"&gt;Handling Secrets with AWS Secrets Manager&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://blog.gitguardian.com/how-to-handle-secrets-with-google-cloud-secret-manager/"&gt;How to Handle Secrets with Google Cloud Secret Manager&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://blog.gitguardian.com/how-to-handle-secrets-with-azure-key-vault/"&gt;How to Handle Secrets with Azure Key Vault&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How helm-secrets works
&lt;/h3&gt;

&lt;p&gt;OK, now the long version.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Helm-secrets&lt;/code&gt; is a Helm plugin that manages secrets.&lt;/p&gt;

&lt;p&gt;What's a Helm plugin, then? Good question.&lt;/p&gt;

&lt;p&gt;Helm plugins are extensions that add additional functionalities and capabilities to Helm. These can be new commands, features, or integrations. Plugins can be used to perform various tasks, such as managing secrets, encrypting values, validating charts, etc. To use a Helm plugin, we typically install it using the &lt;code&gt;helm plugin install&lt;/code&gt; command, and then we can invoke the plugin's commands just like any other native Helm command.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;helm-secrets&lt;/code&gt;, the values can be stored encrypted in Helm charts. &lt;strong&gt;However, the plugin does not handle the encryption/decryption operations itself; it offloads and delegates the cryptographic work to another tool: &lt;a href="https://github.com/getsops/sops?ref=blog.gitguardian.com"&gt;&lt;strong&gt;&lt;code&gt;SOPS&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SOPS, short for &lt;strong&gt;S&lt;/strong&gt;ecrets &lt;strong&gt;OP&lt;/strong&gt;eration*&lt;em&gt;S&lt;/em&gt;*, is an open-source text file editor by Mozilla that encrypts/decrypts files automatically. With SOPS, when we write a file, SOPS automatically encrypts the file before saving it to the disk. For that, it uses the encryption key of our choice: this can be a PGP key, an AWS KMS key, or many others. To learn more about SOPS and its possible integrations with different encryption key service providers, read my other blog here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/a-comprehensive-guide-to-sops/"&gt;A Comprehensive Guide to SOPS: Managing Your Secrets Like A Visionary, Not a Functionary&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;helm-secrets&lt;/code&gt; can also work in a "cloud" mode, where secrets are &lt;em&gt;not&lt;/em&gt; stored in the Helm chart, but in a cloud secret manager. We then simply refer to the path of the secret in the cloud in the file, and the secret is automatically injected upon invoking &lt;code&gt;helm install&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: Helm-secrets with SOPS
&lt;/h3&gt;

&lt;p&gt;In this example, we will store encrypted secrets in the Helm charts. Since this relies on SOPS, we first need to install it. The easiest way to install SOPS is via &lt;code&gt;brew&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;For other OS users, refer to &lt;a href="https://github.com/getsops/sops?ref=blog.gitguardian.com"&gt;the official GitHub repo of SOPS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then, let's configure SOPS to use PGP keys for encryption:&lt;/p&gt;

&lt;p&gt;If you are using another OS, for example, Linux, you can use the corresponding package manager. Most likely, this would work:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo apt-get install gnupg&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With GnuPG, Creating a key is as simple as the following (remember to put your name as the value of &lt;code&gt;KEY_NAME&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;Get the GPG key fingerprint:&lt;/p&gt;

&lt;p&gt;In the "pub" part of the output, you can get the GPG key fingerprint (in my case, it's "BE574406FE117762E9F4C8B01CB98A820DCBA0FC").&lt;/p&gt;

&lt;p&gt;Then we need to configure SOPS to use this PGP key for encryption/decryption. To do so, create a file named &lt;code&gt;.sops.yaml&lt;/code&gt; under your &lt;code&gt;$HOME&lt;/code&gt; directory with the following content:&lt;/p&gt;

&lt;p&gt;Remember to replace the key fingerprint generated in the previous step.&lt;/p&gt;

&lt;p&gt;Installing and configuring SOPS with PGP keys is not simple; refer to &lt;a href="https://blog.gitguardian.com/a-comprehensive-guide-to-sops/"&gt;my blog on SOPS&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;Finally, we can install &lt;code&gt;helm-secrets&lt;/code&gt;. Click &lt;a href="https://github.com/jkroepke/helm-secrets/releases/latest?ref=blog.gitguardian.com"&gt;here&lt;/a&gt; to get the latest version (at the time of writing, the latest version is &lt;code&gt;v4.5.1&lt;/code&gt;). Then, run the following command to install:&lt;/p&gt;

&lt;p&gt;Let's create a secret file and name it as &lt;code&gt;credentials.yaml.dec&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;p&gt;To encrypt this file using &lt;code&gt;helm-secrets&lt;/code&gt;, run the following command:&lt;/p&gt;

&lt;p&gt;If you open the generated &lt;code&gt;credentials.yaml&lt;/code&gt; file, you will see that its content is encrypted by SOPS.&lt;/p&gt;

&lt;p&gt;Next, we can refer to the encrypted value in the Helm chart's Secrets. Suppose we have a file named &lt;code&gt;your-chart/templates/secrets.yaml&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;p&gt;This template will use the value from the &lt;code&gt;helm-secrets&lt;/code&gt; encrypted &lt;code&gt;credentials.yaml&lt;/code&gt;. When installing the Helm chart, instead of using &lt;code&gt;helm install&lt;/code&gt;, use &lt;code&gt;helm secrets install&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Remember not to submit the &lt;code&gt;credentials.yaml.dec&lt;/code&gt; file to git repositories as it contains clear text passwords. However, the SOPS encrypted result &lt;code&gt;credentials.yaml&lt;/code&gt; can be submitted as part of the Helm chart (although it is generally recommended not to include secret values in Helm charts). If you choose to submit encrypted values, make sure to use a private chart repository for internal use only.&lt;/p&gt;

&lt;p&gt;In this case, we can store sensitive information in an encrypted values file. To share the encrypted values file with your team members, you can add their public key fingerprints to the SOPS configuration. This way, access control is managed by SOPS rather than &lt;code&gt;helm-secrets&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;SOPS supports various encryption methods, such as using AWS KMS keys for encryption and sharing access through AWS IAM policies. For more encryption methods supported by SOPS, refer to &lt;a href="https://blog.gitguardian.com/a-comprehensive-guide-to-sops/"&gt;my other blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For more details on using &lt;code&gt;helm-secrets&lt;/code&gt; with SOPS, refer to &lt;a href="https://github.com/jkroepke/helm-secrets/wiki/Usage?ref=blog.gitguardian.com"&gt;the official doc here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: helm-secrets with Cloud Secrets Managers
&lt;/h3&gt;

&lt;p&gt;In the previous example, we discussed how to store sensitive information in an encrypted form within the values file. However, &lt;code&gt;helm-secrets&lt;/code&gt; offers another mode that integrates with popular cloud secrets managers like Hashicorp Vault or AWS Secrets Manager.&lt;/p&gt;

&lt;p&gt;To enable the integration with cloud secrets managers, you need to set the environment variable &lt;code&gt;HELM_SECRETS_BACKEND=vals&lt;/code&gt; before running Helm. This will activate the vals integration in &lt;code&gt;helm-secrets&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Vals is a tool specifically designed for managing secret values in Helm. It requires cloud provider credentials to fetch secrets from the secret services. Make sure you have the necessary credentials in place before attempting to use them.&lt;/p&gt;

&lt;p&gt;Let's assume you have a file called &lt;code&gt;secrets.yaml&lt;/code&gt; located at &lt;code&gt;your-chart/templates/secrets.yaml&lt;/code&gt;. Here's an example of its content:&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;values.yaml&lt;/code&gt; file, you can include the following snippet:&lt;/p&gt;

&lt;p&gt;Finally, you can install everything together using the following command:&lt;/p&gt;

&lt;p&gt;This command will inject the secret value from AWS Secrets Manager, located at "path/to/my/secret/value", into the variable "password" defined in the values file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simplifying Continuous Deployment Integration with helm-secrets
&lt;/h3&gt;

&lt;p&gt;If you're already using SOPS, then &lt;code&gt;helm-secrets&lt;/code&gt; is a great choice for seamless integration. It also offers cloud integrations if you prefer not to store encrypted data in values files.&lt;/p&gt;

&lt;p&gt;While &lt;code&gt;helm-secrets&lt;/code&gt; can be integrated with major CD tools like Argo CD, there is some operational overhead involved. This is because both SOPS and the helm-secrets plugin are required by the CD tool, as shown in the previous examples.&lt;/p&gt;

&lt;p&gt;For instance, to integrate Argo CD with &lt;code&gt;helm-secrets&lt;/code&gt;, you need to ensure that the Argo CD server container has both SOPS and &lt;code&gt;helm-secrets&lt;/code&gt;. This can be achieved by building a customized Docker image: more details can be found &lt;a href="https://github.com/jkroepke/helm-secrets/wiki/ArgoCD-Integration?ref=blog.gitguardian.com#option-1-custom-docker-image"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another option is to install SOPS or vals and &lt;code&gt;helm-secret&lt;/code&gt; through an init container on the &lt;code&gt;argocd-repo-server&lt;/code&gt; Deployment. This requires changing the initContainers args. More details can be found &lt;a href="https://github.com/jkroepke/helm-secrets/wiki/ArgoCD-Integration?ref=blog.gitguardian.com#option-2-init-container"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, both options have their drawbacks. Customizing the Docker image means maintaining an additional image while customizing initContainers commands results in a more complex values file for Argo CD, which can be challenging to maintain.&lt;/p&gt;

&lt;p&gt;Is there a better or alternative way to manage Helm secrets? Let's continue exploring.&lt;/p&gt;




&lt;h2&gt;
  
  
  External Secrets Operator
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A Quick Introduction to External Secrets Operator
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/external-secrets/external-secrets?ref=blog.gitguardian.com"&gt;External Secrets Operator&lt;/a&gt; is a &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/?ref=blog.gitguardian.com"&gt;K8s operator&lt;/a&gt; that facilitates the integration of external secret management systems, such as AWS Secrets Manager, HashiCorp Vault, Google Secrets Manager, and Azure Key Vault, with K8s.&lt;/p&gt;

&lt;p&gt;Simply put, this operator automatically retrieves secrets from these secrets managers using external APIs and injects them into Kubernetes Secrets.&lt;/p&gt;

&lt;p&gt;Unlike &lt;code&gt;helm-secrets&lt;/code&gt; which either stores encrypted data in the values file using another tool (SOPS) or refers to secrets stored in cloud secrets managers in the values file, the External Secrets Operator does not require including &lt;code&gt;secrets.yaml&lt;/code&gt; as part of the Helm templates. It uses another custom resource ExternalSecret, which contains the reference to cloud secrets managers.&lt;/p&gt;

&lt;p&gt;What does the custom resource do? Let's dive deeper to take a look under the hood of External Secrets Operator.&lt;/p&gt;

&lt;h3&gt;
  
  
  How External Secrets Operator Works
&lt;/h3&gt;

&lt;p&gt;Here's an overview of how the External Secrets Operator works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  SecretStore configuration: First, we define a SecretStore resource that specifies the connection details and authentication credentials for the external secret management system with which we want to integrate.&lt;/li&gt;
&lt;li&gt;  ExternalSecret Configuration: Next, we create an ExternalSecret resource that defines the mapping between the external secrets and Kubernetes Secrets.&lt;/li&gt;
&lt;li&gt;  Syncing secrets: The External Secrets Operator continuously monitors the ExternalSecret resources. When a new or updated ExternalSecret is detected, the operator retrieves the specified secrets from the external secret management system using the configured SecretStore.&lt;/li&gt;
&lt;li&gt;  Automatic Synchronization: The External Secrets Operator periodically synchronizes the secrets based on a defined refresh interval.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  External-Secrets Helm Chart Example
&lt;/h3&gt;

&lt;p&gt;Let's see an example of using external secrets to manage Helm secrets.&lt;/p&gt;

&lt;p&gt;The idea is simple: we &lt;em&gt;do not&lt;/em&gt; include K8s Secrets as part of the Helm chart templates, but rather, we use ExternalSecret, which contains no sensitive information at all.&lt;/p&gt;

&lt;p&gt;First, let's install the External Secret Operator itself:&lt;/p&gt;

&lt;p&gt;We need to make sure access to AWS Secrets Manager is granted to the external secret operator. For a quick test with AWS Secrets Manager, we can create a secret containing our AWS credentials (&lt;em&gt;do not do this in production&lt;/em&gt;) with access to Secrets Manager. Execute the following commands:&lt;/p&gt;

&lt;p&gt;Make sure the access key has permission to access AWS Secrets Manager. For more information, check out AWS IAM policies.&lt;/p&gt;

&lt;p&gt;This approach (access key as a K8s Secret) is only suitable for tutorials. For a production environment, it is recommended to use IAM roles for service accounts. Refer to the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html?ref=blog.gitguardian.com"&gt;AWS official documentation&lt;/a&gt; and the &lt;a href="https://external-secrets.io/latest/provider/kubernetes/?ref=blog.gitguardian.com#authenticating-with-serviceaccount"&gt;External Secret Operator official documentation&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;Then, let's create a SecretStore pointing to AWS Secrets Manager in a specific account and region. Create a file named &lt;code&gt;secretstore.yaml&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;p&gt;Apply the configuration by running the command:&lt;/p&gt;

&lt;p&gt;Make sure to update the region with the appropriate AWS Secrets Manager region where your secrets are stored.&lt;/p&gt;

&lt;p&gt;Then we can create an ExternalSecret as part of a Helm chart template that synchronizes a secret from AWS Secrets Manager as a Kubernetes Secret. Create a file named &lt;code&gt;your-chart/templates/externalsecret.yaml&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;p&gt;The above ExternalSecret will create a K8s Secret named "helloworld" (specified in the "target" section) with one key "password", whose value is retrieved from &lt;code&gt;MyTestSecret1.password&lt;/code&gt; in the AWS Secrets Manager.&lt;/p&gt;

&lt;p&gt;Without changing anything else, we can simply install the chart by running:&lt;/p&gt;

&lt;p&gt;After installation, we can verify the Secret is already created:&lt;/p&gt;

&lt;p&gt;In the output, you should see the details of the created K8s Secret, which is automatically synchronized by the External Secrets Operator, fetching values from AWS Secrets Manager.&lt;/p&gt;

&lt;p&gt;We can now utilize &lt;code&gt;envFrom&lt;/code&gt; and &lt;code&gt;secretRef&lt;/code&gt; in the Helm chart's Deployment to pass these secret values as environment variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seamless Integration of External Secrets Operator with Continuous Deployment Tools
&lt;/h3&gt;

&lt;p&gt;Unlike &lt;code&gt;helm-secrets&lt;/code&gt;, which necessitates the installation of plugins to Helm and additional command-line tools like SOPS, the External Secrets Operator offers a more streamlined approach. It does not require any modifications to Helm or local binaries. Instead, it is solely installed on the K8s cluster side, utilizing an operator that needs to be installed and a SecretStore custom resource that must be defined.&lt;/p&gt;

&lt;p&gt;Due to this inherent simplicity, integrating the external secret operator with continuous deployment tools such as Argo CD is effortless and trouble-free. No changes need to be made on the continuous deployment tool side. The only modification required is to the Helm chart itself: Secrets should not be included in the Helm chart template; instead, ExternalSecret should be used in the templates.&lt;/p&gt;




&lt;h2&gt;
  
  
  Vault Secrets Operator for Kubernetes Secrets and Helm Secrets
&lt;/h2&gt;

&lt;p&gt;There is another major choice regarding managing secrets for Helm charts: the &lt;a href="https://developer.hashicorp.com/vault/docs/platform/k8s/vso?ref=blog.gitguardian.com"&gt;Vault Secrets Operator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Vault Secrets Operator works more or less similarly to the External Secrets Operator, but since it's made by HashiCorp, it only works with HashiCorp Vault. It works by watching for changes in the vault and synchronizing from the vault to a K8s Secret. The operator writes the source secret data directly to the destination Kubernetes Secret, ensuring that any changes made to the source are replicated to the destination over its lifetime.&lt;/p&gt;

&lt;p&gt;It's worth noting that the External Secrets Operator also works with HashiCorp Vault. Still, if you are already using HashiCorp Vault, maybe the vault secrets operator can be a better choice since it's specifically designed for HashiCorp Vault and provides additional features tailored to Vault's capabilities, like &lt;a href="https://developer.hashicorp.com/vault/tutorials/kubernetes/vault-secrets-operator?ref=blog.gitguardian.com#dynamic-secrets"&gt;dynamic secrets&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Automatically Restart Pods When Secrets Are Updated
&lt;/h2&gt;

&lt;p&gt;In this section, we will discuss how to automatically restart pods when secrets are updated. This is an important requirement for most teams and companies. We will explore different approaches to achieve this.&lt;/p&gt;

&lt;p&gt;If you are using a standard Helm chart without &lt;code&gt;helm-secrets&lt;/code&gt; or the External Secrets Operator, you can use a hack to ensure that a Deployment's annotation section is updated when the secrets.yaml file changes. This can be done by using the &lt;code&gt;sha256sum&lt;/code&gt; function. Here's an example:&lt;/p&gt;

&lt;p&gt;This would work, and based on my experience, a lot of teams and companies use this in production, but in my eyes, this isn't ideal. For starters, it looks a bit messy in the annotations section.&lt;/p&gt;

&lt;p&gt;In the case of helm-secrets, if the values are updated, we must do another &lt;code&gt;helm secrets upgrade&lt;/code&gt;, since the values are either part of the Helm chart or injected in run time. This could be a little bit redundant, because what if neither the app nor the chart is updated, and only a secret value is updated? A full-on Helm upgrade seems a bit much.&lt;/p&gt;

&lt;p&gt;Using the External Secrets Operator with Helm is even trickier: there is no &lt;code&gt;secrets.yaml&lt;/code&gt; anymore, and the ExternalSecret only refers to secrets managers in the cloud, meaning the &lt;code&gt;externalsecret.yaml&lt;/code&gt; doesn't change even if the values are updated in secrets managers, so we can't even use the checksum function.&lt;/p&gt;

&lt;p&gt;To address these challenges, we recommend using &lt;a href="https://github.com/stakater/Reloader?ref=blog.gitguardian.com"&gt;Reloader&lt;/a&gt;. Reloader can watch for changes in secrets and ConfigMaps, and perform rolling upgrades on pods associated with DeploymentConfigs, Deployments, Daemonsets, Statefulsets, and Rollouts.&lt;/p&gt;

&lt;p&gt;To install Reloader, simply run:&lt;/p&gt;

&lt;p&gt;Once Reloader is installed, you can configure it to automatically discover Deployments where specific config maps or secrets are used. To do this, add the &lt;code&gt;reloader.stakater.com/auto&lt;/code&gt; annotation to the main metadata of your Deployment as part of the Helm chart template:&lt;/p&gt;

&lt;p&gt;This will discover Deployments automatically where a configmap or a secret is being used, and it will perform rolling upgrades on related pods when either is updated. Combined with External Secrets Operator, everything is solved without untidy hacks like the checksum annotation!&lt;/p&gt;

&lt;p&gt;For more detailed usage of Reloader, check out the official doc &lt;a href="https://github.com/stakater/Reloader/blob/master/README.md?ref=blog.gitguardian.com"&gt;here&lt;/a&gt;.&lt;/p&gt;




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

&lt;p&gt;Learning to use &lt;code&gt;helm-secrets&lt;/code&gt; can be challenging due to its integration with SOPS and the various encryption options it offers. Additionally, integrating &lt;code&gt;helm-secrets&lt;/code&gt; with CD tools can result in increased operational overhead. However, if you only need to securely store sensitive data in the values file and do not plan on using a cloud secrets manager, &lt;code&gt;helm-secrets&lt;/code&gt; is a suitable choice.&lt;/p&gt;

&lt;p&gt;The Vault Secrets Operator and External Secrets Operator function similarly, but the Vault Secrets Operator, designed specifically for HashiCorp Vault, offers additional features such as dynamic secret generation.&lt;/p&gt;

&lt;p&gt;For most users, the External Secrets Operator is likely the better choice as it is compatible with major public cloud providers' secrets managers and seamlessly integrates with CD tools and the Reloader tool. This conclusion is supported by the higher number of GitHub stars for the External Secrets Operator (3k stars) compared to the Vault Secrets Operator (0.3k stars) and helm-secrets (1k stars).&lt;/p&gt;

&lt;p&gt;Of course, your own experience may differ, depending on your preferences. To make things easier, we created a workflow helping you choose among these solutions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/content/images/2023/10/helm-secrets.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iDSG_Q8w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.gitguardian.com/content/images/2023/10/helm-secrets.png" alt="Decision Tree: Choose What's Best For You" width="800" height="881"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Secret in Helm: what's the best solution?&lt;/p&gt;




&lt;h2&gt;
  
  
  F.A.Q.
&lt;/h2&gt;

&lt;p&gt;How does Helm Handle Secrets?&lt;/p&gt;

&lt;p&gt;When it comes to handling secrets, Helm treats them as part of its templates, just like other Kubernetes resources such as Deployments and ConfigMaps. These templates can reference values defined in the values.yaml file. However, since secrets contain sensitive information, it is bad practice to store clear-text credentials directly in the Helm values file. Instead, the values file should only contain sample default values.&lt;/p&gt;

&lt;p&gt;How Do You Use Secrets with Helm Charts?&lt;/p&gt;

&lt;p&gt;There are two options for using Secrets with Helm Charts. The first is to encrypt the sensitive information in the Helm values file. The second option is to use an alternative mechanism to handle Kubernetes Secrets, rather than including them as part of the Helm chart.&lt;/p&gt;

&lt;p&gt;How Does helm-secrets Work?&lt;/p&gt;

&lt;p&gt;helm-secrets provides a solution for encrypting sensitive values files using SOPS. This ensures that the encrypted files can be safely committed to git repositories and shared among team members. SOPS also offers access control capabilities and supports different encryption methods.&lt;/p&gt;

&lt;p&gt;Additionally, helm-secrets provides the option to store sensitive data in cloud secrets managers instead of the values file. These values can then be injected during helm installs.&lt;/p&gt;

&lt;p&gt;What is the Alternative to helm-secrets?&lt;/p&gt;

&lt;p&gt;For those looking for an alternative to helm-secrets, the External Secrets Operator is a reliable choice. HashiCorp Vault users may also consider the Vault Secrets Operator.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Handling Secrets with AWS Secrets Manager</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Sat, 07 Oct 2023 03:33:17 +0000</pubDate>
      <link>https://dev.to/gitguardian/handling-secrets-with-aws-secrets-manager-2l9</link>
      <guid>https://dev.to/gitguardian/handling-secrets-with-aws-secrets-manager-2l9</guid>
      <description>&lt;p&gt;In my previous tutorials, we looked at Azure Key Vault and Google Secret Manager:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://blog.gitguardian.com/how-to-handle-secrets-with-azure-key-vault/"&gt;How to Handle Secrets with Azure Key Vault&lt;/a&gt;: In this piece, we had a look at the Zero Trust security strategy, how to put it into practice to secure applications and data, and how secrets managers can help to achieve the Zero Trust goal. We also included a tutorial on Kubernetes/SPC to use secrets from secret managers.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.toINSERT%20LINK%20HERE"&gt;How to Handle Secrets with Google Secret Manager&lt;/a&gt;: In this piece, we did a tutorial on using secrets from secret managers in your CI workflows (GitHub Actions).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you haven't read them yet, please give them a quick read, because even if you are not an Azure or a GCP user, they still might be worth reading.&lt;/p&gt;

&lt;p&gt;Today, we will have a look at AWS's equivalent: AWS Secrets Manager. We will do a quick introduction and some tutorials (cheat sheet included).&lt;/p&gt;

&lt;p&gt;Let's get started.&lt;/p&gt;




&lt;h2&gt;
  
  
  1 What is AWS Secrets Manager?
&lt;/h2&gt;

&lt;p&gt;In general, a secret manager helps you improve your security posture by getting rid of hard-coded credentials in application source code, CI/CD workflows, and maybe even Kubernetes YAML manifests. Hard-coded credentials are replaced with a runtime call to the secret manager (or other mechanisms) to retrieve/sync credentials dynamically when you need them, and this helps avoid possible compromise by anyone who can inspect your application or the components.&lt;/p&gt;

&lt;p&gt;AWS Secrets Manager is AWS's offering, which can help you manage, retrieve, and even rotate secrets, which can be database credentials, application credentials, OAuth tokens, API keys, and other secrets throughout their lifecycles.&lt;/p&gt;




&lt;h2&gt;
  
  
  2 What Secrets Can Be Stored in Secrets Manager?
&lt;/h2&gt;

&lt;p&gt;You can manage secrets such as database credentials, on-premises resource credentials, SaaS application credentials, third-party API keys, and Secure Shell (SSH) keys.&lt;/p&gt;

&lt;p&gt;Please note that although Secrets Manager enables you to store a JSON document which allows you to manage any text blurb that is 64 KB or smaller, making it possible to be used in a wide range of scenarios, for certain types of secrets, there are better ways to manage them in AWS, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS credentials: we can use AWS IAM instead of storing/accessing AWS credentials with Secrets Manager.Management.&lt;/li&gt;
&lt;li&gt;Encryption keys: use AWS KMS service.&lt;/li&gt;
&lt;li&gt;SSH keys: use AWS EC2 Instance Connect instead.&lt;/li&gt;
&lt;li&gt;Private keys and certificates: use AWS Certificate Manager.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3 Secrets Manager Features
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7pGB_VjA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wn8pb7h9715tjd38py7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7pGB_VjA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wn8pb7h9715tjd38py7w.png" alt="AWS secrets manager" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Secrets Manager enables us to store, retrieve, control access to, rotate, audit, and monitor secrets centrally. Here are some features worth mentioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fine-grained access control policies: IAM policies and resource-based policies can be used together to manage access to our secrets.&lt;/li&gt;
&lt;li&gt;Multi-region replication: we can automatically replicate secrets to multiple AWS Regions to meet our disaster recovery and cross-regional redundancy requirements.&lt;/li&gt;
&lt;li&gt;Secrets rotation: the credentials for AWS RDS (Relational Database Service), DocumentDB, and Redshift can be natively rotated by Secrets Manager. We can also extend Secrets Manager to rotate other secrets, such as credentials for Oracle databases hosted on EC2 or OAuth refresh tokens, by modifying sample AWS Lambda functions available in the Secrets Manager documentation.&lt;/li&gt;
&lt;li&gt;Audit and monitoring: Secrets Manager can be integrated with many AWS logging, monitoring, and notification services, making auditing and monitoring secrets easy. For example, after enabling CloudTrail for a region, we can audit when a secret is created or rotated by viewing CloudTrail logs; for another, we can configure CloudWatch to receive email messages using SNS when secrets remain unused for a period or configure CloudWatch events to receive push notifications when Secrets Manager rotates our secrets.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4 How Secrets Manager Works under the Hood
&lt;/h2&gt;

&lt;p&gt;To understand how it works, think of Secrets Manager as a super secure vault for our sensitive data. It uses powerful encryption keys, which we own and store in AWS Key Management Service (KMS). We are in full control of who can access our secrets with the help of AWS Identity and Access Management (IAM) policies.&lt;/p&gt;

&lt;p&gt;When a secret needs to be retrieved, Secrets Manager ensures it's done securely. It first decrypts the secret, then sends it to our application over a secure connection (TLS). And don't worry, it doesn't write down or stash our secret anywhere else.&lt;/p&gt;

&lt;p&gt;For the encryption part, Secrets Manager uses what's called envelope encryption (AES-256 encryption algorithm) to encrypt our secrets in AWS Key Management Service (KMS):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When start using Secrets Manager, the AWS KMS keys can be specified to encrypt secrets. If not specified, don't worry, because Secrets Manager will automatically create AWS KMS default keys.&lt;/li&gt;
&lt;li&gt;When a secret is stored, Secrets Manager works some magic behind the scenes. It asks KMS for plain text and an encrypted data key. Then, it uses that plain text data key to encrypt secrets right in memory. This encrypted secret and data key are then stored safely away.&lt;/li&gt;
&lt;li&gt;When accessing a secret, Secrets Manager decrypts the data key (using AWS KMS default keys) and uses the plain text data key to decrypt the secret. Rest assured, the data key is always stored encrypted and never written to the disk in plain text. Plus, the secret won't be written or cached to persistent storage in plain text either.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5 Creating a Secret in Secrets Manager
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5.1 Create/Update
&lt;/h3&gt;

&lt;p&gt;We can create a secret in the AWS web console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wotbH955--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fspgaewy7xera9yu1vpk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wotbH955--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fspgaewy7xera9yu1vpk.png" alt="create a secret in aws secrets manager" width="800" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, I chose "other type of secret", and named it "MyTestSecret1":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DlOp38Qh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sn343w1fx5uo0lwhm9b5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DlOp38Qh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sn343w1fx5uo0lwhm9b5.png" alt="create a secret in aws secrets manager" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other settings are left as default.&lt;/p&gt;

&lt;p&gt;Of course, as a hardcore DevOps engineer, I prefer the CLI way because the web console is too user-friendly and I don't like it (just joking).&lt;/p&gt;

&lt;p&gt;To create a secret using the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager create-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; MyTestSecret2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"My test secret created with the CLI."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-string&lt;/span&gt; &lt;span class="s2"&gt;"{'user':'tiexin','password':'EXAMPLE-PASSWORD'}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command above creates a secret with two key-value pairs.&lt;/p&gt;

&lt;p&gt;Alternatively, we can store the content of the secret in a JSON file, and create a secret using that JSON file. Create a file named &lt;code&gt;mycreds.json&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tiexin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EXAMPLE-PASSWORD"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And create a secret from the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager create-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; MyTestSecret3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-string&lt;/span&gt; file://mycreds.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can get the value with the &lt;code&gt;get-secret-value&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager get-secret-value &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can also update the value using the &lt;code&gt;put-secret-value&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager put-secret-value &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-string&lt;/span&gt; &lt;span class="s2"&gt;"{'user':'tiexin','password':'NEW-EXAMPLE-PASSWORD'}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above command creates &lt;em&gt;a new version&lt;/em&gt; (discussed in the next subsection) of a secret with two key-value pairs.&lt;/p&gt;

&lt;h2&gt;
  
  
  5.2 Secret Versions
&lt;/h2&gt;

&lt;p&gt;A secret has versions that hold copies of the encrypted secret value. When you change the secret value or the secret is rotated, Secrets Manager creates a new version. Secrets Manager doesn't store a linear history of secrets with versions. Instead, it keeps track of three specific versions by labeling them, like AWSCURRENT, AWSPREVIOUS. A secret always has a version labeled AWSCURRENT, and Secrets Manager returns that version by default when you retrieve the secret value.&lt;/p&gt;

&lt;p&gt;For example, if we have a look at the secret "MyTestSecret3", on which we did an update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager list-secret-version-ids &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret3
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Versions"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"VersionId"&lt;/span&gt;: &lt;span class="s2"&gt;"a11a8133-96ae-4abc-9bfb-e737ae39266e"&lt;/span&gt;,
            &lt;span class="s2"&gt;"VersionStages"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"AWSPREVIOUS"&lt;/span&gt;
            &lt;span class="o"&gt;]&lt;/span&gt;,
            &lt;span class="s2"&gt;"CreatedDate"&lt;/span&gt;: 1692428755.262,
            &lt;span class="s2"&gt;"KmsKeyIds"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"DefaultEncryptionKey"&lt;/span&gt;
            &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"VersionId"&lt;/span&gt;: &lt;span class="s2"&gt;"a2477f83-02a9-457c-b473-c9589c5d7309"&lt;/span&gt;,
            &lt;span class="s2"&gt;"VersionStages"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"AWSCURRENT"&lt;/span&gt;
            &lt;span class="o"&gt;]&lt;/span&gt;,
            &lt;span class="s2"&gt;"CreatedDate"&lt;/span&gt;: 1692428770.661,
            &lt;span class="s2"&gt;"KmsKeyIds"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"DefaultEncryptionKey"&lt;/span&gt;
            &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;,
    &lt;span class="s2"&gt;"ARN"&lt;/span&gt;: &lt;span class="s2"&gt;"arn:aws:secretsmanager:ap-southeast-1:xxx:secret:MyTestSecret3-vGMlXZ"&lt;/span&gt;,
    &lt;span class="s2"&gt;"Name"&lt;/span&gt;: &lt;span class="s2"&gt;"MyTestSecret3"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that there are two versions of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 List/Find
&lt;/h3&gt;

&lt;p&gt;We can use the command &lt;code&gt;aws secretsmanager list-secrets&lt;/code&gt; to list all secrets, and use the &lt;code&gt;--filter&lt;/code&gt; parameter to filter wanted secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager list-secrets &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;,Values&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MyTest"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will show all secrets whose names start with "MyTest".&lt;/p&gt;

&lt;p&gt;Other than the "name" filter we used in the above command, there are other filter keys you can use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;description&lt;/li&gt;
&lt;li&gt;tag-key&lt;/li&gt;
&lt;li&gt;tag-value&lt;/li&gt;
&lt;li&gt;owning-service&lt;/li&gt;
&lt;li&gt;primary-region&lt;/li&gt;
&lt;li&gt;all (searches all of the above keys)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.4 Delete
&lt;/h3&gt;

&lt;p&gt;Because of the critical nature of secrets, AWS Secrets Manager intentionally makes deleting a secret difficult.&lt;/p&gt;

&lt;p&gt;Secrets Manager DOES NOT immediately delete secrets by default. Instead, Secrets Manager immediately makes the secrets inaccessible and scheduled for deletion after a recovery window of a minimum of 7 days. Until the recovery window ends, you can recover a secret you previously deleted.&lt;/p&gt;

&lt;p&gt;To delete a secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager delete-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--recovery-window-in-days&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the recovery window, if wanted, we can recover the secret by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager restore-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-secret-id&lt;/span&gt; MyTestSecret3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the CLI, it's possible to do a force-delete without a recovery window but please use it with caution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager delete-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--force-delete-without-recovery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6 Accessing Secrets
&lt;/h2&gt;

&lt;p&gt;In the previous two tutorials (link &lt;a href="https://blog.gitguardian.com/how-to-handle-secrets-with-azure-key-vault/"&gt;here&lt;/a&gt; and &lt;a href="https://dev.toINSERT%20LINK%20HERE"&gt;here&lt;/a&gt;), we have covered a few ways to access secrets stored in a secret manager, like from an application directly using the SDK provided by the public cloud provider; from CI pipelines, like GitHub Actions; and from a Kubernetes cluster using the Secret Provider Class (SPC). Although the previous two tutorials are done with Azure and GCP, the principles are the same for AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 From Applications Directly Using SDK
&lt;/h3&gt;

&lt;p&gt;For example, to access secrets from a Python application, we will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.6 or later.&lt;/li&gt;
&lt;li&gt;botocore 1.12 or higher. See AWS SDK for Python and Botocore.&lt;/li&gt;
&lt;li&gt;setuptools_scm 3.2 or higher. See &lt;a href="https://pypi.org/project/setuptools-scm/"&gt;https://pypi.org/project/setuptools-scm/&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To install the component, use 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;pip &lt;span class="nb"&gt;install &lt;/span&gt;aws-secretsmanager-caching
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Python application needs the following IAM permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;secretsmanager:DescribeSecret&lt;/li&gt;
&lt;li&gt;secretsmanager:GetSecretValue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if you are running the Python app on an EC2 virtual machine, the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html"&gt;instace profile&lt;/a&gt; of that EC2 needs the above permissions.&lt;/p&gt;

&lt;p&gt;The following code snippet example shows how to get the secret value for a secret named MyTestSecret1:&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;import&lt;/span&gt; &lt;span class="nn"&gt;botocore&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;botocore.session&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;aws_secretsmanager_caching&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SecretCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SecretCacheConfig&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;botocore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_session&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;create_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'secretsmanager'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cache_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecretCacheConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecretCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_secret_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'MyTestSecret1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more detail on accessing secrets in a Python app using the SDK, see &lt;a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_cache-python.html"&gt;the official doc here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 From CI Workflows (Example: GitHub Actions)
&lt;/h3&gt;

&lt;p&gt;Similarly, as we did in the &lt;a href="https://dev.toINSERT%20LINK%20HERE"&gt;GCP tutorial&lt;/a&gt;, we can use a secret in a GitHub job to retrieve secrets from AWS Secrets Manager and add them as masked Environment variables in our GitHub workflows.&lt;/p&gt;

&lt;p&gt;To do this, we first need to allow GitHub Actions to access AWS Secrets Manager, which can be achieved by using the GitHub OIDC provider. &lt;a href="https://blog.gitguardian.com/securing-your-ci-cd-an-oidc-tutorial/"&gt;My previous blog here&lt;/a&gt; details how to do this, allowing us to use short-lived credentials and avoid storing additional access keys outside of Secrets Manager.&lt;/p&gt;

&lt;p&gt;The IAM role assumed by the GitHub Actions must have the following permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GetSecretValue on the secrets we want to retrieve&lt;/li&gt;
&lt;li&gt;ListSecrets on all secrets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then we can simply add a step in our GitHub Actions workflow using the following syntax to access secrets from Secrets Manager:&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;Step name&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/aws-secretsmanager-get-secrets@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;secret-ids&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyTestSecret1&lt;/span&gt;
    &lt;span class="na"&gt;parse-json-secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(Optional) &lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="s"&gt;|false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6.3 From K8s/EKS Clusters with the Secret Provider Class (SPC)
&lt;/h3&gt;

&lt;p&gt;Similarly, as we did in the &lt;a href="https://blog.gitguardian.com/how-to-handle-secrets-with-azure-key-vault/"&gt;Azure tutorial&lt;/a&gt;, we can use Kubernetes Secret Provider Class to access secrets from Kubernetes clusters.&lt;/p&gt;

&lt;p&gt;We use YAML to describe which secrets to mount in AWS EKS. The SPC is in the following format:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets-store.csi.x-k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretProviderClass&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;&amp;lt;NAME&amp;gt;&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;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
  &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;failoverRegion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pathTranslation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;objects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's an example:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets-store.csi.x-k8s.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretProviderClass&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;nginx-deployment-aws-secrets&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;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
  &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;objects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;- objectName: "MySecret"&lt;/span&gt;
        &lt;span class="s"&gt;objectType: "secretsmanager"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more details on using SPC with AWS EKS and Secrets Manager, see &lt;a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/integrating_csi_driver_tutorial.html"&gt;the official doc here&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  7 Tutorial: AWS EKS + External Secrets Operator + Secrets Manager
&lt;/h2&gt;

&lt;p&gt;Today, let's do a tutorial using a different approach for accessing Secrets Manager from Kubernetes clusters: the &lt;a href="https://external-secrets.io/latest/"&gt;External Secrets Operator&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1 The Secret Store CSI Driver/SPC Method Explained
&lt;/h3&gt;

&lt;p&gt;SPC works by ways of &lt;a href="https://secrets-store-csi-driver.sigs.k8s.io/"&gt;Secrets Store CSI Driver&lt;/a&gt; to mount secrets, and since the CSI is Container Storage Interface, it mounds secrets as a container storage volume. This means secrets must be mounted as a volume on the Pod.&lt;/p&gt;

&lt;p&gt;In the cloud-native era, following the 12-factor app principle, we don't want to write our apps in a way that they read secrets from a local file path. We prefer to read secrets from ENV vars, for example, using a Kubernetes Secret as ENV vars with &lt;a href="https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/"&gt;&lt;code&gt;envFrom&lt;/code&gt; and &lt;code&gt;secretRef&lt;/code&gt;&lt;/a&gt;. In the case of the Secrets Store CSI Driver/SPC method, &lt;a href="https://secrets-store-csi-driver.sigs.k8s.io/topics/sync-as-kubernetes-secret.html"&gt;it can sync secrets from a secret manager as a Kubernetes Secret&lt;/a&gt;, but the volume is still required to do so, which seems to be a bit of redundancy since we won't be using that volume anyways.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 External Secret Operator
&lt;/h3&gt;

&lt;p&gt;External Secrets Operator is a Kubernetes operator that integrates external secret management systems like AWS Secrets Manager (and HashiCorp Vault, Google Secrets Manager, Azure Key Vault, etc.) The operator reads information from external APIs and automatically injects the values into a Kubernetes Secret.&lt;/p&gt;

&lt;p&gt;Although it sounds very similar to the SPC method, the key difference is that the External Secret Operator does not use the CSI driver, hence requiring no volumes to sync secrets from a secret manager.&lt;/p&gt;

&lt;p&gt;With the key difference cleared out of the way, let's continue with the tutorial.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.3 Create an EKS Cluster
&lt;/h3&gt;

&lt;p&gt;On macOS, the easiest way to install &lt;code&gt;eksctl&lt;/code&gt; is by using brew:&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 weaveworks/tap
brew &lt;span class="nb"&gt;install &lt;/span&gt;weaveworks/tap/eksctl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For other operating systems and installation methods, see &lt;a href="https://github.com/eksctl-io/eksctl"&gt;the README here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After installation, we can run a simple command to bootstrap a cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eksctl create cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will create an EKS cluster in the default region (as specified by AWS CLI config) with one managed node group containing two m5.large nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.4 Install External Secret Operator
&lt;/h3&gt;

&lt;p&gt;After the cluster is up and running, we can install the External Secret Operator using helm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm &lt;span class="nb"&gt;install &lt;/span&gt;external-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  external-secrets/external-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; external-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--create-namespace&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we need to allow External Secret Operator to access AWS Secrets Manager. Since this is a tutorial and not meant for a production environment, we will do so by creating a secret containing our AWS credentials which has access to Secret Manager:&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;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'KEYID'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./access-key
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'SECRETKEY'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./secret-access-key
kubectl create secret generic awssm-secret &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./access-key &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./secret-access-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Please note that this is only for tutorial purposes; for a production environment, use the IAM role for service accounts. See the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html"&gt;AWS official doc here&lt;/a&gt; and &lt;a href="https://external-secrets.io/latest/provider/kubernetes/#authenticating-with-serviceaccount"&gt;External Secret Operator official doc here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  7.5 Create a Secret Store
&lt;/h3&gt;

&lt;p&gt;A SecretStore points to AWS Secrets Manager in a certain account within a defined region. Create a file named &lt;code&gt;secretstore.yaml&lt;/code&gt; with the following content:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretStore&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;secretstore-sample&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;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretsManager&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ap-southeast-1&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;accessKeyIDSecretRef&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;awssm-secret&lt;/span&gt;
            &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;access-key&lt;/span&gt;
          &lt;span class="na"&gt;secretAccessKeySecretRef&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;awssm-secret&lt;/span&gt;
            &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret-access-key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And apply it by running: &lt;code&gt;kubectl apply -f secretstore.yaml&lt;/code&gt;. Remember to update the region to the region where you store your secrets in AWS Secrets Manager.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.6 Create an External Secret
&lt;/h3&gt;

&lt;p&gt;Then, we create an External Secret that syncs a secret from AWS Secrets Manager as a Kubernetes Secret. Create a file named &lt;code&gt;externalsecret.yaml&lt;/code&gt; with the following content:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ExternalSecret&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;example&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;refreshInterval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;
  &lt;span class="na"&gt;secretStoreRef&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;secretstore-sample&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretStore&lt;/span&gt;
  &lt;span class="na"&gt;target&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;secret-to-be-created&lt;/span&gt;
    &lt;span class="na"&gt;creationPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Owner&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret-key-to-be-managed&lt;/span&gt;
    &lt;span class="na"&gt;remoteRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyTestSecret1&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;username&lt;/span&gt;
    &lt;span class="na"&gt;remoteRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyTestSecret1&lt;/span&gt;
      &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretKey&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;remoteRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyTestSecret1&lt;/span&gt;
      &lt;span class="na"&gt;property&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;And apply it by running: &lt;code&gt;kubectl apply -f externalsecret.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The ExternalSecret above means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a K8s Secret named "secret-to-be-created" (defined in the "target) section&lt;/li&gt;
&lt;li&gt;The K8s Secret will have two keys, one is "username" (from MyTestSecret1.user in AWS Secrets Manager) and the other is "password" (from MyTestSecret1.password in AWS Secrets Manager).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After applying the YAML, we can verify by running:&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;kubectl describe secret secret-to-be-created
Name:         secret-to-be-created
Namespace:    default
Labels:       reconcile.external-secrets.io/created-by&lt;span class="o"&gt;=&lt;/span&gt;default_example
Annotations:  reconcile.external-secrets.io/data-hash: 49d444f36fb63e85d07ccc7a8d10441c

Type:  Opaque

Data
&lt;span class="o"&gt;====&lt;/span&gt;
password:  4 bytes
username:  6 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that the Kubernetes secret is already created automatically by the External Secrets Operator, synchronizing values from AWS Secrets Manager. From here, you can use &lt;code&gt;envFrom&lt;/code&gt; and &lt;code&gt;secretRef&lt;/code&gt; in your Kubernetes Deployment/Pod.&lt;/p&gt;




&lt;h2&gt;
  
  
  8 Tear Down
&lt;/h2&gt;

&lt;p&gt;Delete all secrets created in this tutorial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager delete-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--force-delete-without-recovery&lt;/span&gt;

aws secretsmanager delete-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--force-delete-without-recovery&lt;/span&gt;

aws secretsmanager delete-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--force-delete-without-recovery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete the EKS cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eksctl delete cluster &lt;span class="nt"&gt;--name&lt;/span&gt; NAME_OF_THE_EKS_CLUSTER_CREATED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9 Cheat Sheet
&lt;/h2&gt;

&lt;h3&gt;
  
  
  9.1 Secrets CRUD
&lt;/h3&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager create-secret &lt;span class="nt"&gt;--name&lt;/span&gt; MyTestSecret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"xxx"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-string&lt;/span&gt; &lt;span class="s2"&gt;"xxx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create using a file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager create-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; MyTestSecret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-string&lt;/span&gt; file://mycreds.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager get-secret-value &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager put-secret-value &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--secret-string&lt;/span&gt; &lt;span class="s2"&gt;"yyy"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager list-secret-version-ids &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List/search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager list-secrets &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;,Values&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MyTest"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager delete-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--recovery-window-in-days&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Force delete without a recovery window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager delete-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; MyTestSecret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--force-delete-without-recovery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  9.2 Access Secrets from GitHub Actions
&lt;/h3&gt;

&lt;p&gt;OIDC provider usage example: &lt;a href="https://blog.gitguardian.com/securing-your-ci-cd-an-oidc-tutorial/"&gt;https://blog.gitguardian.com/securing-your-ci-cd-an-oidc-tutorial/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub Actions step to read a secret:&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;Step name&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/aws-secretsmanager-get-secrets@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;secret-ids&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyTestSecret1&lt;/span&gt;
    &lt;span class="na"&gt;parse-json-secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(Optional) &lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="s"&gt;|false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  9.3 SPC/External Secrets Operator
&lt;/h3&gt;

&lt;p&gt;SPC format reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets-store.csi.x-k8s.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretProviderClass&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;nginx-deployment-aws-secrets&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;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
  &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;objects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;- objectName: "MySecret"&lt;/span&gt;
        &lt;span class="s"&gt;objectType: "secretsmanager"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SecretStore format reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretStore&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;secretstore-sample&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;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretsManager&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ap-southeast-1&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;accessKeyIDSecretRef&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;awssm-secret&lt;/span&gt;
            &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;access-key&lt;/span&gt;
          &lt;span class="na"&gt;secretAccessKeySecretRef&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;awssm-secret&lt;/span&gt;
            &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret-access-key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ExternalSecret template reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ExternalSecret&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;example&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;refreshInterval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;
  &lt;span class="na"&gt;secretStoreRef&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;secretstore-sample&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretStore&lt;/span&gt;
  &lt;span class="na"&gt;target&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;secret-to-be-created&lt;/span&gt;
    &lt;span class="na"&gt;creationPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Owner&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret-key-to-be-managed&lt;/span&gt;
    &lt;span class="na"&gt;remoteRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyTestSecret1&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;username&lt;/span&gt;
    &lt;span class="na"&gt;remoteRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyTestSecret1&lt;/span&gt;
      &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretKey&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;remoteRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyTestSecret1&lt;/span&gt;
      &lt;span class="na"&gt;property&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;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we did a quick introduction to AWS Secrets Manager and demonstrated how to use AWS CLI to do CRUD for secrets. Then we covered how to use secrets using SDK, in CI workflows, and in Kubernetes with SPC.&lt;/p&gt;

&lt;p&gt;Due to space limitations, in this tutorial, we didn't rewrite much on the SDK/SPC/GitHub Actions usage of secrets. If you have more questions on these parts, please refer to the previous two tutorials on Azure and GCP secret managers, where more details are available.&lt;/p&gt;

&lt;p&gt;Then We did an extra extended tutorial on the External Secrets Operator; choose between that and the SPC method based on your requirements.&lt;/p&gt;

&lt;p&gt;In the end, like always, a detailed cheat sheet is provided, covering the most common use cases regarding AWS Secrets Manager.&lt;/p&gt;

&lt;p&gt;I hope this tutorial and the whole series are useful to AWS/GCP/Azure users. Please like, comment, subscribe. Thanks!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>devsecops</category>
      <category>aws</category>
      <category>security</category>
    </item>
    <item>
      <title>Platform Engineering: Building Your Developer Portal with Backstage (Pt 1)</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Sun, 09 Jul 2023 03:02:47 +0000</pubDate>
      <link>https://dev.to/gitguardian/platform-engineering-building-your-developer-portal-with-backstage-pt-1-3ak1</link>
      <guid>https://dev.to/gitguardian/platform-engineering-building-your-developer-portal-with-backstage-pt-1-3ak1</guid>
      <description>&lt;h1&gt;
  
  
  Platform Engineering: Building Your Developer Portal with Backstage - Part 1
&lt;/h1&gt;

&lt;p&gt;In my previous article, we looked at platform engineering: what it is, how it became a thing, platform engineering V.S. DevOps, and how it could help improve security.&lt;/p&gt;

&lt;p&gt;If you haven't read it yet, here is the link to it for you:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/platform-engineering-and-security-a-very-short-introduction/"&gt;Platform Engineering and Security: A Very Short Introduction&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today, let's get hands-on and build a developer portal from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  1 What Are We Talking about When We Talk about Developer Portal
&lt;/h2&gt;

&lt;p&gt;Since platform engineering is all about self-service, the internal developer portal is crucial in the new paradigm, in which all the self-service capabilities and integrations are implemented.&lt;/p&gt;

&lt;p&gt;Given the significance of the developer portal, before building one, we need to figure out what exactly we are expecting from it. Typically, to get the most out of platform engineering, a developer portal would contain some key features, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a service catalog: giving developers a bird's-eye view of all the projects, services, deployment status, documentation, ownership, and even on-call schedules and incidents, etc.;&lt;/li&gt;
&lt;li&gt;some kind of repository scaffolding/project generation tool: for example, something like &lt;a href="https://github.com/cookiecutter/cookiecutter"&gt;cookiecutter&lt;/a&gt;, which enables developers to spin up new services from within the portal itself;&lt;/li&gt;
&lt;li&gt;customization: you might want to integrate whatever toolchain you use into your developer portal to make it a genuinely unified one-stop experience. Possible integrations include Kubernetes, CI/CD status, on-call schedules, incident management systems, secrets managers, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, the portal can handle everything, like getting information about services, on-call schedules, triggering incidents, deploying things, and dealing with incident management. With these features, developers can have a truly unified experience where creating services and subsequently keeping track of and operating/maintaining those services in one place, maximizing the potential of a developer portal.&lt;/p&gt;




&lt;h2&gt;
  
  
  2 Choosing the Tool: Backstage
&lt;/h2&gt;

&lt;p&gt;Now that we know what we really want from a developer portal, let's build one.&lt;/p&gt;

&lt;p&gt;Since it will have multiple features, the designing/coding part could be a massive project in itself, and not all teams could afford that. That said, we need a tool for building the portal for a quick start, and luckily, we've got one - &lt;a href="https://github.com/backstage/backstage"&gt;Backstage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Backstage is an open platform for building developer portals. Backstage itself isn't a developer portal but a tool to build your developer portal. Think of "create-react-app" V.S., the actual react app you created with it. Out of the box, Backstage includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;software catalog for managing all your software, such as microservices, libraries, data pipelines, websites, and ML models;&lt;/li&gt;
&lt;li&gt;software templates for quickly spinning up new projects and standardizing your tooling with your organization's best practices;&lt;/li&gt;
&lt;li&gt;docs for making it easy to create, maintain, find, and use technical documentation, using a "docs like code" approach;&lt;/li&gt;
&lt;li&gt;a growing ecosystem of opensource plugins that further expand Backstage's customizability and functionality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It can be so flexible in its architecture: it has a frontend written in React/TypeScript and a backend in Node.js, and it extends its power by adding plugins (which we will probably cover in the second part of this tutorial.&lt;/p&gt;

&lt;p&gt;What's more, Backstage (created by Spotify) is now hosted by the &lt;a href="https://www.cncf.io/"&gt;Cloud Native Computing Foundation (CNCF)&lt;/a&gt; as an Incubation level project, meaning you can get all the community support you want. It's also got &lt;a href="https://info.backstage.spotify.com/office-hours"&gt;office hours&lt;/a&gt;, where you can join interactively to learn precisely how the opensource platform can drive better developer effectiveness and experience every Thursday.&lt;/p&gt;

&lt;p&gt;Enough said; today, we will start from scratch and build a developer portal ourselves. After this tutorial, you can bootstrap a new service using templates with baked-in security CI workflows, check the CI status, and view the documentation for that service in the same place.&lt;/p&gt;

&lt;p&gt;Without further adieu, let's get started.&lt;/p&gt;




&lt;h2&gt;
  
  
  3 Building The Portal
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Prerequisites
&lt;/h3&gt;

&lt;p&gt;Not much; probably all DevOps engineers already got them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Unix-based operating system. For example, you can run this on macOS, Linux, or Windows Subsystem for Linux (WSL).&lt;/li&gt;
&lt;li&gt;curl, git, Docker&lt;/li&gt;
&lt;li&gt;Node.js, yarn&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.2 Creating the Portal
&lt;/h3&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @backstage/create-app@0.5.2-next.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Author's note:&lt;/p&gt;

&lt;p&gt;The reason we use &lt;code&gt;backstage/create-app@0.5.2-next.3&lt;/code&gt; instead of &lt;code&gt;backstage/create-app@latest&lt;/code&gt; is because we need &lt;a href="https://github.com/backstage/backstage/issues/17723"&gt;this feature&lt;/a&gt; to create secrets in GitHub to be used in GitHub Actions workflows. This feature has been developed, but as of now (June 18, 2023), it's not yet integrated into the latest version of backstage/create-app.&lt;/p&gt;

&lt;p&gt;If you read this article later when the &lt;code&gt;latest&lt;/code&gt; version points to a version released in June 2023 (or later), you can safely run &lt;code&gt;npx @backstage/create-app@latest&lt;/code&gt; instead without specifying the weird version.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This command gives you an interactive mode where you need to enter the name, for example, "my-portal", press enter, then wait till the app is finished.&lt;/p&gt;

&lt;p&gt;Enter the directory, and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you should have the portal up and running already! It's that simple! Now, you can poke around with it for a bit and have a feeling of the software catalog, templates, documents, and stuff there:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sn-94-G1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1va1yidu5jckv4eap8zg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sn-94-G1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1va1yidu5jckv4eap8zg.png" alt="Image description" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But, at this moment, it's not of much use yet. So, let's continue configuring it.&lt;/p&gt;




&lt;h2&gt;
  
  
  4 GitHub Authentication and Integration
&lt;/h2&gt;

&lt;p&gt;Since the developer portal will be in charge of bootstrapping new repositories, it requires permissions to operate GitHub, and that's why we need to do a GitHub authentication and integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Personal Access Token for Integration
&lt;/h3&gt;

&lt;p&gt;While using GitHub Apps might be the best way to set up integrations because of its fine-grained permission settings, for this tutorial, we'll use a Personal Access Token for a quicker start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create your Personal Access Token by opening the &lt;a href="https://github.com/settings/tokens/new"&gt;GitHub token creation page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Use a name to identify this token and put it in the notes field. Choose the number of days for expiration.&lt;/li&gt;
&lt;li&gt;If you have a hard time picking a number, we suggest going for 7 days; it's a lucky number :) (And we will only be testing it locally, not running in production.)&lt;/li&gt;
&lt;li&gt;For this tutorial, let's set the scope to the maximum so that your experience won't be blocked by struggling with GitHub permissions. &lt;em&gt;Although, please note that you should NEVER do this in production!&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, export the token as an environment variable:&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;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;app-config.yaml&lt;/code&gt; file, change the &lt;code&gt;integrations&lt;/code&gt; section to the following:&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;integrations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;github.com&lt;/span&gt;
      &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GITHUB_TOKEN}&lt;/span&gt; &lt;span class="c1"&gt;# leave it like this, read values from env var&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.2 Creating a GitHub OAuth App
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="https://github.com/settings/applications/new"&gt;https://github.com/settings/applications/new&lt;/a&gt; to create your OAuth App. The Homepage URL should point to Backstage's frontend; in our tutorial, it would be &lt;code&gt;http://localhost:3000&lt;/code&gt;. The Authorization callback URL will point to the auth backend, which will most likely be &lt;a href="http://localhost:7007/api/auth/github/handler/frame"&gt;http://localhost:7007/api/auth/github/handler/frame&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---FjK6nyJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7abamj9qmyu2byyw0gj9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---FjK6nyJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7abamj9qmyu2byyw0gj9.png" alt="Image description" width="800" height="961"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, let's open our &lt;code&gt;app-config.yaml&lt;/code&gt; file again and configure the authentication by updating the &lt;code&gt;auth&lt;/code&gt; section to the following:&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;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;environment&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;providers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR CLIENT ID&lt;/span&gt; &lt;span class="c1"&gt;# put your values here&lt;/span&gt;
        &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR CLIENT SECRET&lt;/span&gt; &lt;span class="c1"&gt;# put your values here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that storing the client secret in the config file as a hardcoded secret is against security best practices and should only be used for local development. For production usage, we can read them from environment variables, following the &lt;a href="https://12factor.net/"&gt;12-factor app&lt;/a&gt; rules&lt;/p&gt;

&lt;p&gt;&lt;em&gt;After these config changes, we must stop our yarn dev servers and re-run &lt;code&gt;yarn dev&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5 Creating a Template
&lt;/h2&gt;

&lt;p&gt;Next, let's prepare a software template, which could be used to bootstrap a new service in no time.&lt;/p&gt;

&lt;p&gt;The template should contain the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;some basic source code/directory structure common to your services&lt;/li&gt;
&lt;li&gt;some documentation&lt;/li&gt;
&lt;li&gt;some CI workflows to test/build/deploy your service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this tutorial, we will use &lt;a href="https://github.com/IronCore864/backstage-test-template"&gt;this template&lt;/a&gt; that I created as an example.&lt;/p&gt;

&lt;p&gt;The directory structure is relatively simple; there are only two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;skeleton&lt;/code&gt; folder,&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;template.yaml&lt;/code&gt; file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.1 The &lt;code&gt;skeleton&lt;/code&gt; Folder
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;skeleton&lt;/code&gt; folder contains all the templates that will be rendered when using this template to create a new service, and the variables are in the format of &lt;code&gt;${{ values.varName }}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you are familiar with Helm, YAML, or Go templates (or just about any template tool), you will find this easy to read and understand at no cost whatsoever.&lt;/p&gt;

&lt;p&gt;The only item worth mentioning is a file named &lt;code&gt;catalog-info.yaml&lt;/code&gt;, which is used by Backstage, so this file must exist; otherwise, you can't register the created service as a component in the portal.&lt;/p&gt;

&lt;p&gt;As you can see, in the template, we have already got some baked-in GitHub Actions workflows, one would test on pull requests and push to the main branch, and the other will use &lt;a href="https://github.com/GitGuardian/ggshield"&gt;&lt;code&gt;ggshield&lt;/code&gt;&lt;/a&gt; to scan repositories for hardcoded secrets.&lt;/p&gt;

&lt;p&gt;In this way, we can put all the CI/CD best practices in the template so that when others launch a new service, they already have everything they need with security features enabled by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 The &lt;code&gt;template.yaml&lt;/code&gt; File
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/IronCore864/backstage-test-template/blob/main/template.yaml"&gt;The &lt;code&gt;template.yaml&lt;/code&gt; file&lt;/a&gt; defines how the template looks in the portal UI and what it actually does.&lt;/p&gt;

&lt;p&gt;It is long and seems overwhelming at first glance, but once you have a closer look at it, you will notice it's pretty straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the syntax is like a Kubernetes custom resource;&lt;/li&gt;
&lt;li&gt;it has two major parts, one is parameters, and the other is steps;&lt;/li&gt;
&lt;li&gt;the parameters define required input and their types when using this template;&lt;/li&gt;
&lt;li&gt;the steps define what actually happens when you execute this template, and it looks a whole lot just like a GitHub Actions workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Parameters example:&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;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Provide some simple information&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;service_name&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;owner&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;service_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Name&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&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;Unique name of the service.&lt;/span&gt;
        &lt;span class="s"&gt;ui:field: EntityNamePicker&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Description&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&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;Help others understand what this service is for; optional.&lt;/span&gt;
      &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Owner&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&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;Owner of the component&lt;/span&gt;
        &lt;span class="s"&gt;ui:field: OwnerPicker&lt;/span&gt;
        &lt;span class="s"&gt;ui:options:&lt;/span&gt;
          &lt;span class="s"&gt;allowedKinds&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Group&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Steps example:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;template&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;Fetch Skeleton + Template&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fetch:template&lt;/span&gt;
  &lt;span class="na"&gt;input&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="s"&gt;./skeleton&lt;/span&gt;
  &lt;span class="na"&gt;copyWithoutTemplating&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;- .github/workflows/\*&lt;/span&gt;
  &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.service_name }}&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;${{ parameters.description }}&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.repoUrl | parseRepoUrl }}&lt;/span&gt;
  &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.owner }}&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;From the above file, we can infer what exactly it defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;first, you need to enter two input parameters: &lt;code&gt;service_name&lt;/code&gt; and &lt;code&gt;owner&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;then you need to choose a repository location with one extra parameter (&lt;code&gt;GITGUARDIAN_API_KEY&lt;/code&gt;, to be used in CI pipelines);&lt;/li&gt;
&lt;li&gt;it then fetches the template, renders it, publishes it to GitHub, and registers it in our portal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.3 Register the Template
&lt;/h3&gt;

&lt;p&gt;Finally, let's add our template to our portal's catalog so that others can use this template.&lt;/p&gt;

&lt;p&gt;The most straightforward configuration for the catalog is to declaratively add locations pointing to YAML files with static location configuration. Locations are added to the catalog under the &lt;code&gt;catalog.locations&lt;/code&gt; key in the &lt;code&gt;app-config.yaml&lt;/code&gt; file:&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;catalog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;locations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;url&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;https://github.com/IronCore864/backstage-test-template/blob/main/template.yaml&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;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Template&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule above allows us to add a template from the specified URL.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Remember to restart yarn dev servers.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6 Putting Everything Together
&lt;/h2&gt;

&lt;p&gt;Now that we've got everything ready, it's time to see the magic in action.&lt;/p&gt;

&lt;p&gt;Visit &lt;code&gt;http://localhost:3000&lt;/code&gt;, and click the "Create" button, choose our template:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QH6L-FAA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4njx5mfasqsqdtonusib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QH6L-FAA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4njx5mfasqsqdtonusib.png" alt="Image description" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Input necessary information. For the GitGuardian API key, create one here: &lt;a href="https://dashboard.gitguardian.com/api/personal-access-tokens"&gt;https://dashboard.gitguardian.com/api/personal-access-tokens&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When everything is set, click "next step", and the magic will happen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SZL4x4x1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fkj10xgtezkgb75iqafm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SZL4x4x1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fkj10xgtezkgb75iqafm.png" alt="Image description" width="800" height="623"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything should be created now.&lt;/p&gt;

&lt;p&gt;We can view it in our catalog:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Bel0jcDV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcf1c25drew1fue88afu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Bel0jcDV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcf1c25drew1fue88afu.png" alt="Image description" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we've also got documentation created and rendered:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1vUMdo-b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ww0if792o5s36kf479g8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1vUMdo-b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ww0if792o5s36kf479g8.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last but not least, let's check out the CI status:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Bo-_f41p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d97a5b2dpfcac7eg453f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Bo-_f41p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d97a5b2dpfcac7eg453f.png" alt="Image description" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seems the pipelines are already finished successfully, and we can click on them for more details with detailed steps and logs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NufN4sRW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d7psbj4w42nqp3qyfhc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NufN4sRW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d7psbj4w42nqp3qyfhc3.png" alt="Image description" width="800" height="912"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you prefer to view them in the CI software (in this case, GitHub Actions), you can click the corresponding links to jump to them.&lt;/p&gt;

&lt;p&gt;For your reference, &lt;a href="https://github.com/IronCore864/node-test/"&gt;this repo&lt;/a&gt; is the one I created using the template above.&lt;/p&gt;

&lt;p&gt;This means no matter what toolchain the team uses, the team members don't have to remember 10 different URLs for 10 different tools, and they don't have to keep those 10 tabs open all the time. When they need some information, any information, they go to the developer portal, and they've got everything, and that's precisely the power of an internal developer portal.&lt;/p&gt;




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

&lt;p&gt;In the first part of the tutorial, we reviewed the features of developer portals, learned how to use the opensource Backstage tool to create a portal ourselves, configured our portal with GitHub, created a software template, and bootstrapped a service from it.&lt;/p&gt;

&lt;p&gt;A developer portal could be much more than this if more and more integrations are added. Imagine if you deploy services in Kubernetes, use Argo CD for GitOps deployment, use HashiCorp Vault for secrets management, and all the integrations are in your portal: when you need to check the deployment status, when you want to see the actual resources in K8s, you don't have to visit Vault, Argo CD, K8s Dashboard. Heck, you don't even have to remember the URLs for them or even know the existence of those things because you have the one tool to rule them all, and that's the developer portal.&lt;/p&gt;

&lt;p&gt;This tutorial only works for a local quick start; there are many things to think about for production usage. For example, we use static config for now; nothing is persistent; if you restart the dev server, the catalog info is lost. For this, we need to &lt;a href="https://backstage.io/docs/getting-started/configuration/#install-and-configure-postgresql"&gt;configure Postgres for our portal&lt;/a&gt;. For another example, we use &lt;code&gt;yarn dev&lt;/code&gt; to start both the frontend and backend; for production usage, you might want to separate the frontend from the backend, deploy them in K8s as containers, and maybe create Ingress and stuff for them.&lt;/p&gt;

&lt;p&gt;In the next part of this tutorial, we will look at the Backstage plugin's mechanism to see how we can expand its power to the next level.&lt;/p&gt;

&lt;p&gt;If you enjoy this article, please like, comment, and subscribe. See you in the next part!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>devsecops</category>
      <category>backstage</category>
      <category>platformengineering</category>
    </item>
    <item>
      <title>Platform Engineering and Security: A Very Short Introduction</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Tue, 30 May 2023 06:25:51 +0000</pubDate>
      <link>https://dev.to/gitguardian/platform-engineering-and-security-a-very-short-introduction-1moj</link>
      <guid>https://dev.to/gitguardian/platform-engineering-and-security-a-very-short-introduction-1moj</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ifLe802k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.gitguardian.com/content/images/size/w2000/2023/05/23W21-blog-Platform-Engineering-and-Security--1-.png%2520align%3D%2522left%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ifLe802k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.gitguardian.com/content/images/size/w2000/2023/05/23W21-blog-Platform-Engineering-and-Security--1-.png%2520align%3D%2522left%2522" alt="Platform Engineering and Security: A Very Short Introduction" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm a DevOps engineer. I hope that's still a thing because, &lt;a href="https://thenewstack.io/platform-engineering-in-2023-dev-first-collaboration-and-apis/?ref=blog.gitguardian.com"&gt;according to some&lt;/a&gt;, "&lt;strong&gt;DevOps is dead; long live platform engineering.&lt;/strong&gt;"&lt;/p&gt;

&lt;p&gt;There is no denying that recently we started to see terms like "&lt;strong&gt;platform engineering&lt;/strong&gt;," "&lt;strong&gt;developer portal&lt;/strong&gt;," or even "&lt;strong&gt;Backstage&lt;/strong&gt;" a lot more often than before.&lt;/p&gt;

&lt;p&gt;This article will answer a few essential questions: &lt;strong&gt;is DevOps really dead&lt;/strong&gt;? What is platform engineering? What are the differences? How does platform engineering handle DevSecOps/security?&lt;/p&gt;

&lt;h2&gt;
  
  
  Is DevOps Dead?
&lt;/h2&gt;

&lt;p&gt;Short answer, no.&lt;/p&gt;

&lt;p&gt;If I learned anything over the years in the tech world, it's the fact that technologies, frameworks, and methodologies never really die. They grow, they adapt, and they evolve.&lt;/p&gt;

&lt;p&gt;For example, think agile development. What's the first time you heard of that? Is it dead now? That's the point.&lt;/p&gt;

&lt;p&gt;But, in a way, DevOps did "die." Although it didn't die in the conventional sense, where it simply disappeared from the tech community, it did "die" in a way that many companies and teams trying to utilize DevOps failed to meet their expectations.&lt;/p&gt;

&lt;p&gt;This is not news. Back in 2019, a data analyst from Gartner, a technological research and consulting firm, &lt;a href="https://www.gartner.com/smarterwithgartner/the-secret-to-devops-success?ref=blog.gitguardian.com"&gt;predicted&lt;/a&gt; that by 2022, 75% of DevOps initiatives would fail to meet expectations, and this number might increase to 90% in 2023.&lt;/p&gt;

&lt;p&gt;It's worth pointing out that those DevOps initiatives failed not because of technological challenges but because of &lt;strong&gt;overlooked human factors like trust, ownership, and teamwork&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Initially, DevOps was touted as the ultimate solution to all issues within traditional operations silos, promising to revolutionize the industry. Still, many teams have unfortunately fallen short of their original expectations. Worse, it seems that, in the end, developers don't actually want to do Ops work!&lt;/p&gt;

&lt;h2&gt;
  
  
  Devs Don't Want to Do Ops?
&lt;/h2&gt;

&lt;p&gt;To be fair, it depends on who you ask. Some developers think the DevOps "you build it, you run it" paradigm is definitely beneficial, even sometimes necessary; others don't want to touch operations at all.&lt;/p&gt;

&lt;p&gt;Take, for example, this poll:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Devs… be real with me. Do y’all want to do Ops? 🧵&lt;/p&gt;

&lt;p&gt;— Luca (@luca_cloud) &lt;a href="https://twitter.com/luca_cloud/status/1562349679660122112?ref_src=twsrc%5Etfw&amp;amp;ref=blog.gitguardian.com"&gt;August 24, 2022&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The responses highlight the divide, with 41.8% of respondents saying yes to operations tasks, 42.1% saying no, and 16.1% being indifferent.&lt;/p&gt;

&lt;p&gt;As it turns out, developers tend to steer clear of infrastructure and operations, which is not very positive for the success of DevOps.&lt;/p&gt;

&lt;p&gt;The consequence is that &lt;strong&gt;platform engineering&lt;/strong&gt; has become increasingly popular in response to this trend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform Engineering
&lt;/h2&gt;

&lt;p&gt;Platform engineering, just like DevOps, emerged in response to the increasing complexity of modern software architecture.&lt;/p&gt;

&lt;p&gt;In today's world, non-expert end users, such as developers, are frequently tasked with operating a complex array of services that can be incredibly challenging to manage.&lt;/p&gt;

&lt;p&gt;While DevOps tries to solve the collaboration and velocity problem of the software development lifecycle (SDLC) from a &lt;strong&gt;cultural standpoint&lt;/strong&gt;—where shared responsibility, continuous growth, and learning mindset are valued, teamwork, best practices, and modern toolchains encouraged—platform engineering tries to approach the problem from a different angle: that of &lt;strong&gt;self-service&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's have a look at a concrete example:&lt;/p&gt;

&lt;p&gt;Consider a scenario where a team of developers requires a relational database (RDS) for their new application, but they lack the necessary knowledge to create one in a specific cloud provider, such as AWS, using the appropriate automation tool, such as Terraform. Not all developers are familiar with the infrastructure and orchestration of their systems.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;DevOps approach&lt;/strong&gt; to this problem involves creating a cross-functional team that includes an expert in AWS services and infrastructure as code who possesses the proper permissions to provision the cloud resources &lt;em&gt;automagically&lt;/em&gt; by writing Terraform modules and deploying the RDS in AWS.&lt;/p&gt;

&lt;p&gt;On the other hand, in the &lt;strong&gt;platform engineering approach&lt;/strong&gt;, there should be an integrated product that serves as the "internal developer portal", so that the developers request an RDS in AWS through self-service, and the platform will automatically create it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that this is only one example; internal developer portals can achieve way more if appropriately crafted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In short, platform engineering involves designing and constructing an integrated product that includes toolchains and workflows to address the operational requirements of the entire SDLC. This approach enables developers to access the appropriate level of self-service and abstraction that suits their organization or team in the cloud-native era.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform Engineering vs DevOps
&lt;/h2&gt;

&lt;p&gt;Although these self-service capabilities improve the developer experience and productivity, it's not for everyone, especially not for smallish teams/companies: even if you decide to purchase some internal developer portal-as-a-service instead of building it from scratch, you still need to maintain automation and templates behind the scene: think of the previous RDS-in-the-cloud example, where you need to have the knowledge to maintain Terraform templates to generate the module.&lt;/p&gt;

&lt;p&gt;If a team or company is too small for platform engineering, &lt;strong&gt;DevOps may be a more effective approach&lt;/strong&gt;. In smaller environments, collaboration tends to be more intimate, the pace is more agile, and there is typically less organizational friction.&lt;/p&gt;

&lt;p&gt;It is important to note that platform engineering does not serve as a replacement for DevOps. It does not emerge to displace or replace DevOps. Although many say that DevOps is dead with the rise of platform engineering, it is not a situation where one will replace the other: &lt;strong&gt;the two approaches can come together&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Platform engineering can be seen as an evolution of DevOps, as it shares the same objective and can enhance the effectiveness of DevOps.&lt;/p&gt;

&lt;p&gt;Likely, we will still see DevOps culture a lot, and it's also likely that many software engineering organizations will have established platform teams to help bring together software developers and IT operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Does Security Fit in this New Paradigm?
&lt;/h2&gt;

&lt;p&gt;We can't talk about DevOps without talking about security (or DevSecOps); after all, &lt;a href="https://blog.gitguardian.com/aws-iam-security-best-practices/"&gt;&lt;strong&gt;security is job zero&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;DevOps tackles security by "shifting left." In contrast to traditional software development methods, where security-related tasks were only addressed at the end of the SDLC, which could lead to project delays and was not scalable, DevOps/DevSecOps incorporates security at every stage of the SDLC as early as possible, using automation tools. This approach is known as "baking in" security.&lt;/p&gt;

&lt;p&gt;To illustrate, when writing code, developers consider potential security concerns, such as securely storing and retrieving secrets and credentials, rather than waiting for a final audit or review before pushing to production. Additionally, when creating virtual machine or container images, vulnerability scans are automatically conducted during each build, and any vulnerabilities found are immediately addressed, rather than conducting a one-time scan before shipping to production.&lt;/p&gt;

&lt;p&gt;This is "shift left."&lt;/p&gt;

&lt;p&gt;All these methods of dealing with securities in a more agile, responsive, and automated "shifting-left" manner are still applicable to platform engineering; &lt;strong&gt;there is no contradiction here regarding DevOps and platform engineering.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the assistance of platform engineering, internal developer portals can automate the entire process to a greater extent. Consider three concrete examples:&lt;/p&gt;

&lt;p&gt;Firstly, imagine creating a new microservice. Instead of writing boilerplate code and manually assembling CI/CD pipelines, Dockerfiles, and Kubernetes manifests, &lt;strong&gt;the internal developer portal can bootstrap your entire repository using existing templates&lt;/strong&gt;. These templates include scaffolding code for the app, as well as pre-configured pipelines that follow DevSecOps best practices. They also scan for hard-coded secrets in the source code and lint YAMLs, among other things.&lt;/p&gt;

&lt;p&gt;Secondly, imagine needing to store secrets. Instead of searching for the secrets manager your team is using and determining whether you have permission to create secrets, you can go to the internal developer portal, which integrates with your secrets manager. Here, you can create secrets in a self-service manner without worrying about the underlying details, thanks to the appropriate level of abstraction provided by platform engineering.&lt;/p&gt;

&lt;p&gt;Finally, imagine launching a new virtual machine in the cloud. Instead of writing automation code from scratch or copying and pasting from the existing codebase and reviewing for security groups settings before deploying, &lt;strong&gt;what if you can simply go to your internal developer portal&lt;/strong&gt;, click a button, and everything is done using a template maintained and secured by the platform engineering team?&lt;/p&gt;

&lt;p&gt;All these are the beauties of platform engineering:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;- Self-service&lt;br&gt;&lt;br&gt;
- The right amount of abstraction&lt;br&gt;&lt;br&gt;
- Baked-in best practices and automation&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;I hope this introduction to platform engineering has sparked your interest! You should now have a better understanding of why it emerged and its core proposition value for developers, as well as the differences with DevOps and how security is addressed with this new paradigm. If you are interested in platform engineering, please subscribe, as we will be working on some tutorials to help you build your own developer portal from scratch.&lt;/p&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>devsecops</category>
      <category>supplychainsecurity</category>
      <category>security</category>
    </item>
    <item>
      <title>Open Policy Agent with Kubernetes - Tutorial (Pt. 2)</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Wed, 22 Feb 2023 16:26:15 +0000</pubDate>
      <link>https://dev.to/gitguardian/open-policy-agent-with-kubernetes-tutorial-pt-2-34c4</link>
      <guid>https://dev.to/gitguardian/open-policy-agent-with-kubernetes-tutorial-pt-2-34c4</guid>
      <description>&lt;p&gt;In my previous articles, we discussed what Policy-as-Code is, why we need it, and how to use the Open Policy Agent (OPA) tool. If you haven't read the introduction yet, please take some time to read it first:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/what-is-policy-as-code-an-introduction-to-open-policy-agent/" rel="noopener noreferrer"&gt;What is Policy-as-Code? An Introduction to Open Policy Agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Following the OPA introduc how to use OPA to enforce policies inside a Kubernetes cluster. Here's the link to the first part of the tutorial:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.gitguardian.com/open-policy-agent-with-kubernetes-tutorial-pt-1/" rel="noopener noreferrer"&gt;Open Policy Agent with Kubernetes - Tutorial (Pt. 1)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: we used OPA as the admission controller with the &lt;code&gt;kube-mgmt&lt;/code&gt; sidecar enforcing ConfigMap-based policies.&lt;br&gt;&lt;br&gt;
Today, we will look at another way of using OPA in a Kubernetes cluster: Gatekeeper - policy controller for Kubernetes.&lt;/p&gt;


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

&lt;p&gt;The OPA &lt;a href="https://github.com/open-policy-agent/gatekeeper" rel="noopener noreferrer"&gt;Gatekeeper&lt;/a&gt; is a project under the OPA umbrella. Although I did not mention Gatekeeper in the previous tutorial, the technique I described there (using OPA with its sidecar &lt;code&gt;kube-mgmt&lt;/code&gt;) is also referred to as &lt;strong&gt;Gatekeeper v1.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Today, we will get familiar with &lt;strong&gt;Gatekeeper v3&lt;/strong&gt; (hereafter: just "Gatekeeper", omitting the "v3" part), which builds a Kubernetes admission controller around the policy engine to integrate OPA and the Kubernetes API service.&lt;/p&gt;
&lt;h3&gt;
  
  
  CRD-Based Policies
&lt;/h3&gt;

&lt;p&gt;Gatekeeper's most significant value is the ability to configure OPA policies dynamically using Gatekeeper's &lt;strong&gt;Custom Resource Definitions (CRDs)&lt;/strong&gt;. &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/" rel="noopener noreferrer"&gt;Custom resources&lt;/a&gt; are extensions of the Kubernetes API that allows for the customization of a Kubernetes installation.&lt;/p&gt;

&lt;p&gt;CRD-based policies allow for a deeper integration of OPA within the Kubernetes ecosystem: it enables the creation of policy templates for Rego policies, the creation of policies as CRDs, and the storage of audit results on policy CRDs.&lt;/p&gt;
&lt;h3&gt;
  
  
  How is Gatekeeper different from OPA?
&lt;/h3&gt;

&lt;p&gt;Compared to using OPA with its sidecar &lt;code&gt;kube-mgmt&lt;/code&gt; (aka Gatekeeper v1.0), Gatekeeper introduces the following functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  An extensible, parameterized policy library&lt;/li&gt;
&lt;li&gt;  Native Kubernetes CRDs for instantiating the policy library (aka "constraints")&lt;/li&gt;
&lt;li&gt;  Native Kubernetes CRDs for extending the policy library (aka "constraint templates")&lt;/li&gt;
&lt;li&gt;  Native Kubernetes CRDs for mutation support&lt;/li&gt;
&lt;li&gt;  Audit functionality&lt;/li&gt;
&lt;li&gt;  External data support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This may sound a bit too abstract, so let's get down to the nitty-gritty of how Gatekeeper works.&lt;/p&gt;


&lt;h2&gt;
  
  
  Install Gatekeeper
&lt;/h2&gt;

&lt;p&gt;First, let's start minikube:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this tutorial, we will deploy a released version of Gatekeeper in our minikube cluster with a prebuilt image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: it's also possible to deploy it with Helm:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts 
helm &lt;span class="nb"&gt;install &lt;/span&gt;gatekeeper/gatekeeper &lt;span class="nt"&gt;--name-template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gatekeeper &lt;span class="nt"&gt;--namespace&lt;/span&gt; gatekeeper-system &lt;span class="nt"&gt;--create-namespace&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the deployment, a new namespace &lt;code&gt;gatekeeper-system&lt;/code&gt; will be created, and the following resources will be created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tiexin@mbp ~ &lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get deployments
NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
gatekeeper-audit                1/1     1            1           2m20s
gatekeeper-controller-manager   3/3     3            3           2m20s

tiexin@mbp ~ &lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get services
NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;   AGE
gatekeeper-webhook-service   ClusterIP   10.103.86.204   &amp;lt;none&amp;gt;        443/TCP   2m22s

tiexin@mbp ~ &lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get crd
NAME                                                 CREATED AT
assign.mutations. Gatekeeper.sh                       2023-01-01T08:59:54Z
assignmetadata.mutations.gatekeeper.sh               2023-01-01T08:59:54Z
configs.config.gatekeeper.sh                         2023-01-01T08:59:55Z
constraintpodstatuses.status.gatekeeper.sh           2023-01-01T08:59:55Z
constrainttemplatepodstatuses.status.gatekeeper.sh   2023-01-01T08:59:55Z
constrainttemplates.templates.gatekeeper.sh          2023-01-01T08:59:55Z
expansiontemplate.expansion. Gatekeeper.sh            2023-01-01T08:59:55Z
modifyset.mutations. Gatekeeper.sh                    2023-01-01T08:59:55Z
mutatorpodstatuses.status. Gatekeeper.sh              2023-01-01T08:59:55Z
providers.externaldata. Gatekeeper.sh                 2023-01-01T08:59:55Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Gatekeeper Concepts: Constraints and Constraint Templates
&lt;/h2&gt;

&lt;p&gt;Before moving on to the actual tutorial, let's have a look at two essential concepts of Gatekeeper with concrete examples: &lt;strong&gt;constraints&lt;/strong&gt; and &lt;strong&gt;constraint templates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In short, constraints use constraint templates to inform Gatekeeper what policies to be enforced and how.&lt;/p&gt;

&lt;p&gt;I know this sounds a bit confusing, so let's have a look at an example:&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ConstraintTemplate&lt;/code&gt; example below contains Rego code which checks if a resource object has a label named "team":&lt;/p&gt;

&lt;p&gt;File &lt;code&gt;constraint_template.yaml&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;templates.gatekeeper.sh/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConstraintTemplate&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;teamlabel&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;crd&lt;/span&gt;&lt;span class="pi"&gt;:&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;names&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TeamLabel&lt;/span&gt;
  &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&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;admission.k8s.gatekeeper.sh&lt;/span&gt;
      &lt;span class="na"&gt;rego&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;package teamlabel&lt;/span&gt;

        &lt;span class="s"&gt;labels := input.review.object.metadata.labels&lt;/span&gt;

        &lt;span class="s"&gt;has_team {&lt;/span&gt;
          &lt;span class="s"&gt;labels.team&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;

        &lt;span class="s"&gt;violation[{"msg": msg}] {&lt;/span&gt;
          &lt;span class="s"&gt;not has_team&lt;/span&gt;
          &lt;span class="s"&gt;msg := "You should have the team label"&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ConstraintTemplate&lt;/code&gt; object above doesn't trigger policy enforcement on its own. However, it creates a new custom resource in our cluster of the type &lt;code&gt;TeamLabel&lt;/code&gt;. If we want to enforce our &lt;code&gt;TeamLabel&lt;/code&gt; policy, we create a constraint by using that new resource type:&lt;/p&gt;

&lt;p&gt;File &lt;code&gt;constraint.yaml&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;constraints.gatekeeper.sh/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TeamLabel&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;teampods&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;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kinds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&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;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;kinds&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;Pod"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;excludedNamespaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kube-system&lt;/span&gt;
  &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This constraint uses the TeamLabel constraint template above to let Gatekeeper enforce our TeamLabel policy for all pods not in the &lt;code&gt;kube-system&lt;/code&gt; namespace.&lt;/p&gt;

&lt;p&gt;The Gatekeeper service also continually and constantly monitors and audits existing cluster objects to detect policy violations.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Use Gatekeeper in Kubernetes
&lt;/h2&gt;

&lt;p&gt;Create files &lt;code&gt;constraint_template.yaml&lt;/code&gt; and &lt;code&gt;constraint.yaml&lt;/code&gt; with the content in the previous section, and apply them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; constraint_template.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; constraint.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's deploy a simple pod into the default namespace as a test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; default &lt;span class="nt"&gt;-f&lt;/span&gt; https://k8s.io/examples/pods/simple-pod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will get the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Error from server &lt;span class="o"&gt;(&lt;/span&gt;Forbidden&lt;span class="o"&gt;)&lt;/span&gt;: error when creating &lt;span class="s2"&gt;"https://k8s.io/examples/pods/simple-pod.yaml"&lt;/span&gt;: admission webhook &lt;span class="s2"&gt;"validation.gatekeeper.sh"&lt;/span&gt; denied the request: &lt;span class="o"&gt;[&lt;/span&gt;teampods] You should have the team label
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's get in compliance and add a "team" label to the simple pod:&lt;/p&gt;

&lt;p&gt;File &lt;code&gt;simple-pod.yaml&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;nginx&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;team&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;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&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;nginx&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;nginx:1.14.2&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="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create this file with the above content and apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; default &lt;span class="nt"&gt;-f&lt;/span&gt; simple-pod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should encounter no error, and the pod will be created successfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tiexin@mbp ~ &lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; default
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          34s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How to Scale OPA?
&lt;/h2&gt;

&lt;p&gt;So far, we have demonstrated a simple use case of OPA/Kubernetes integration with Gatekeeper.&lt;/p&gt;

&lt;p&gt;However, the policies can be much more complicated in real-world scenarios, and you will have multiple policies. How does OPA work at a much larger scale?&lt;/p&gt;

&lt;p&gt;Let's talk about three crucial topics on this subject matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  repository structure&lt;/li&gt;
&lt;li&gt;  testing policies&lt;/li&gt;
&lt;li&gt;  other tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Repository Structure
&lt;/h3&gt;

&lt;p&gt;A well-structured directory helps to make your code more manageable:&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;.&lt;/span&gt;
└── team-label-policy
    ├── README.md
    ├── constraint.yaml
    ├── constraint_template.yaml
    ├── simple-pod.yaml
    ├── src.rego
    └── src_test.rego
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, we put everything that belongs to the team label policy under the folder "team-label-policy", which contains the core files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;src.rego&lt;/code&gt;: the OPA Rego code&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;src_test.rego&lt;/code&gt;: the corresponding test cases for our policy Rego&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;constraint_template.yaml&lt;/code&gt;: ConstraintTemplate for our policy. Note that this file also contains the code from &lt;code&gt;src.rego&lt;/code&gt; inline, but the OPA tool cannot parse the manifest YAML, so we need to  the Rego code to a separate file for testing. If you use this layout for your policies, &lt;strong&gt;you must remember to synchronize code changes between the two files&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;constraint.yaml&lt;/code&gt;: the manifest for a test of the constraint template&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;simple-pod.yaml&lt;/code&gt;: a minimalist pod definition to demonstrate the constraint in practice&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;README.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to Test a Rego Policy
&lt;/h3&gt;

&lt;p&gt;For essential use cases and complicated policies, we should also write tests for those policies.&lt;/p&gt;

&lt;p&gt;When writing test coverage for your Gatekeeper policy, you want to consider the following points carefully:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  What Kubernetes API resource fields do my policy query? Are any of them optional? Can they appear more than once in a spec?&lt;/li&gt;
&lt;li&gt;  How many positive test cases do I need to write to ensure my policy will do what I expect?&lt;/li&gt;
&lt;li&gt;  How many negative test cases do I need to write to ensure my policy will not produce results I do not want?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Policy tests are also written in Rego.&lt;/p&gt;

&lt;p&gt;By convention, they live in the same directory as the source file. In our case, we can  the policy from the &lt;code&gt;constraint_template.yaml&lt;/code&gt; file into &lt;code&gt;src.rego&lt;/code&gt; and write tests in the file &lt;code&gt;src_test.rego&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note the matching package name at the top of each file:&lt;/p&gt;

&lt;p&gt;File &lt;code&gt;src.rego&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="s"&gt;package teamlabel&lt;/span&gt;

&lt;span class="c1"&gt;# copied from file constraint_template.yaml&lt;/span&gt;

&lt;span class="s"&gt;labels := input.review.object.metadata.labels&lt;/span&gt;

&lt;span class="s"&gt;has_team {&lt;/span&gt;
  &lt;span class="s"&gt;labels.team&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="s"&gt;violation[{"msg"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;msg}] {&lt;/span&gt;
  &lt;span class="s"&gt;not has_team&lt;/span&gt;
  &lt;span class="s"&gt;msg := "You should have the team label"&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;File &lt;code&gt;src_test.rego&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="s"&gt;package teamlabel&lt;/span&gt;
&lt;span class="s"&gt;import future.keywords&lt;/span&gt;

&lt;span class="s"&gt;test_pod_allowed if {&lt;/span&gt;
  &lt;span class="s"&gt;results := violation with input as {"review"&lt;/span&gt;&lt;span class="err"&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;object"&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;metadata"&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;labels"&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;team"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test"&lt;/span&gt; &lt;span class="pi"&gt;}}}}&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="s"&gt;count(results) == &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="s"&gt;test_pod_denied if {&lt;/span&gt;
  &lt;span class="s"&gt;results := violation with input as {"review"&lt;/span&gt;&lt;span class="err"&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;object"&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;metadata"&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;labels"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}}}}&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="s"&gt;count(results) &amp;gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test method names should always begin with the prefix &lt;code&gt;test_&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can use the OPA command-line tool to evaluate our tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tiexin@mbp ~ &lt;span class="nv"&gt;$ &lt;/span&gt;opa &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--explain&lt;/span&gt; fails src.rego src_test.rego
src_test.rego:
data.teamlabel.test_pod_allowed: PASS &lt;span class="o"&gt;(&lt;/span&gt;7.852791ms&lt;span class="o"&gt;)&lt;/span&gt;
data.teamlabel.test_pod_denied: PASS &lt;span class="o"&gt;(&lt;/span&gt;301.75µs&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;--------------------------------------------------------------------------------&lt;/span&gt;
PASS: 2/2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you haven't installed the OPA CLI tool, follow the instructions &lt;a href="https://blog.gitguardian.com/what-is-policy-as-code-an-introduction-to-open-policy-agent/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Helpful Tools
&lt;/h3&gt;

&lt;p&gt;There are some exciting tools (for example, &lt;a href="https://www.styra.com/styra-das/" rel="noopener noreferrer"&gt;this one&lt;/a&gt;) available that can help integrate OPA with your systems and provide ways of writing policies and deploying them across your infrastructure, as well as tools for unit testing, monitoring policy usage, and more. But, even without them, OPA can be managed at a larger scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  Going from Here
&lt;/h2&gt;

&lt;p&gt;In this 2-part OPA tutorial mini-series, we did two demos on integrating OPA with Kubernetes. If you are interested in OPA and OPA/Kubernetes integration, here is some more reading material for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://cloud.redhat.com/blog/better-kubernetes-security-with-open-policy-agent-opa-part-2" rel="noopener noreferrer"&gt;A more complicated real-world example&lt;/a&gt; with &lt;a href="https://github.com/stackrox/blog-examples/tree/master/code/opa-gatekeeper-taint-tolerations" rel="noopener noreferrer"&gt;code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.openpolicyagent.org/docs/latest/" rel="noopener noreferrer"&gt;Open Policy Agent Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://open-policy-agent.github.io/gatekeeper/website/docs/howto/" rel="noopener noreferrer"&gt;Gatekeeper Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you like the OPA introduction and the tutorials, please like, comment, and subscribe. See you in the next one!&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>DevOps with Python: Python ”concurrent.futures“ Concurrency Tutorial - A Real-World Example</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Thu, 02 Feb 2023 03:52:39 +0000</pubDate>
      <link>https://dev.to/ironcore864/devops-with-python-python-concurrentfutures-concurrency-tutorial-a-real-world-example-3734</link>
      <guid>https://dev.to/ironcore864/devops-with-python-python-concurrentfutures-concurrency-tutorial-a-real-world-example-3734</guid>
      <description>&lt;p&gt;Author's note: this blog post ISN'T a beginner's guide to Python or DevOps.&lt;/p&gt;

&lt;p&gt;Basic knowledge of Python, DevOps, Kubernetes, and Helm is assumed.&lt;/p&gt;

&lt;h2&gt;
  
  
  0 Background
&lt;/h2&gt;

&lt;h3&gt;
  
  
  0.1 Why Python
&lt;/h3&gt;

&lt;p&gt;Programming languages rise and fall over time.&lt;/p&gt;

&lt;p&gt;TIOBE, a Dutch software quality assurance company, has been tracking the popularity of programming languages. According to its &lt;a href="https://www.tiobe.com/tiobe-index/" rel="noopener noreferrer"&gt;programming community index&lt;/a&gt; (and its CEO, Paul Jansen, for that matter), Python ranks No.1 now: "for the first time in more than 20 years we have a new leader of the pack: the Python programming language. The long-standing hegemony of Java and C is over."&lt;/p&gt;

&lt;h2&gt;
  
  
  0.2 Why DevOps with Python
&lt;/h2&gt;

&lt;p&gt;To quote &lt;a href="https://realpython.com/tutorials/devops" rel="noopener noreferrer"&gt;Real Python&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Python is one of the primary technologies used by teams practicing DevOps. Its flexibility and accessibility make Python a great fit for this job, enabling the whole team to build web applications, data visualizations, and to improve their workflow with custom utilities.&lt;/p&gt;

&lt;p&gt;On top of that, Ansible and other popular DevOps tools are written in Python or can be controlled via Python.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Plus, I'm a big fan of Python's easy-to-read, no-bracket code style.&lt;/p&gt;

&lt;p&gt;One might wonder why easy-to-read is so essential. The 'puter has no problem executing code with ambiguous variable names, lengthy functions, a single file of a thousand (if not thousands) of lines of code, or all of them together, anyway. It will run properly, right?&lt;/p&gt;

&lt;p&gt;Well, yes. But to quote &lt;a href="https://en.wikipedia.org/wiki/Donald_Knuth" rel="noopener noreferrer"&gt;Knuth&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Programs are meant to be read by humans and only incidentally for computers to execute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;All the methodologies and ideas, like refactoring, clean code, naming conventions, code smell, etc., are invented so that we, humans, can read the code better, not computers can run it better.&lt;/p&gt;

&lt;h3&gt;
  
  
  0.3 Why Concurrency
&lt;/h3&gt;

&lt;p&gt;OK, this one is easy:&lt;/p&gt;

&lt;p&gt;Because we can.&lt;/p&gt;

&lt;p&gt;Jokes aside, the reason is, of course, performance:&lt;/p&gt;

&lt;p&gt;Concurrent is faster (usually).&lt;/p&gt;

&lt;p&gt;For instance, if you have multiple Helm charts installed in one namespace of a Kubernetes cluster and you want to purge all the Helm releases, of course, you can uninstall them one by one, waiting for the first release to be uninstalled, then start uninstalling the second, etc.&lt;/p&gt;

&lt;p&gt;For some applications, the Helm uninstall part can be slow.&lt;/p&gt;

&lt;p&gt;Even if for a few simple charts, uninstalling them concurrently can still drastically save time.&lt;/p&gt;

&lt;p&gt;Based on a local test, uninstalling three helm charts (Nginx, Redis, and MySQL) one by one takes c.a. 0.8 second, while it takes 0.48s if done concurrently, a whopping 40% reduction.&lt;/p&gt;

&lt;p&gt;If the scale of the problem goes up, like you have tens of charts to uninstall and you need to do them in multiple namespaces, the amount of time saved must be addressed. &lt;/p&gt;

&lt;p&gt;Next, let's deal with this particular example using Python.&lt;/p&gt;




&lt;h2&gt;
  
  
  1 The Task
&lt;/h2&gt;

&lt;p&gt;You have multiple teams and developers who share the same Kubernetes cluster as the dev ENV.&lt;/p&gt;

&lt;p&gt;To achieve resource segregation, one namespace is assigned to each developer. Each developer needs to do some Helm install to get their apps and dependencies up and running so that they can develop and test them.&lt;/p&gt;

&lt;p&gt;Now, since there are many namespaces, many Helm releases in each namespace, and many pods, which take up many nodes, you might wanna optimize the cost by deleting all those pods at the end of the working hours so that the cluster can scale down to save some dollars of the VM cost.&lt;/p&gt;

&lt;p&gt;You want some form of automation that uninstalls all releases in some namespaces.&lt;/p&gt;

&lt;p&gt;Let's tackle this issue in Python. For demonstration purposes, we will install &lt;code&gt;nginx&lt;/code&gt;, &lt;code&gt;redis&lt;/code&gt; and &lt;code&gt;mysql&lt;/code&gt; into the &lt;code&gt;default&lt;/code&gt; namespace, then write some automagic stuff to delete 'em.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  2 Preparing the Environment for Testing Our Automation
&lt;/h2&gt;

&lt;p&gt;Not that we are doing test-driven development, but before writing any code, let's create a local environment as a mock of this issue at hand so that we have something to test our automation script.&lt;/p&gt;

&lt;p&gt;Here, we use Docker, minikube, and Helm. If you haven't installed them yet, check out the official websites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-docker/" rel="noopener noreferrer"&gt;https://docs.docker.com/get-docker/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://minikube.sigs.k8s.io/docs/start/" rel="noopener noreferrer"&gt;https://minikube.sigs.k8s.io/docs/start/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://helm.sh/docs/intro/install/" rel="noopener noreferrer"&gt;https://helm.sh/docs/intro/install/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start your local Kubernetes cluster by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, install some Helm charts in the &lt;code&gt;default&lt;/code&gt; namespace, which we will use automation to delete later:&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;# make sure we select the default namespace&lt;/span&gt;
kubectl config set-context &lt;span class="nt"&gt;--current&lt;/span&gt; &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default

&lt;span class="c"&gt;# add some helm repos and update&lt;/span&gt;
helm repo add nginx-stable https://helm.nginx.com/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

&lt;span class="c"&gt;# install three applications that we will use automation to delete&lt;/span&gt;
helm &lt;span class="nb"&gt;install &lt;/span&gt;nginx nginx-stable/nginx-ingress
helm &lt;span class="nb"&gt;install &lt;/span&gt;redis bitnami/redis
helm &lt;span class="nb"&gt;install &lt;/span&gt;mysql bitnami/mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local testing mock done.&lt;/p&gt;




&lt;h2&gt;
  
  
  3 Non-Concurrent Version
&lt;/h2&gt;

&lt;p&gt;First, let's write some single-thread, non-concurrent code to solve this issue.&lt;/p&gt;

&lt;p&gt;We will use the &lt;code&gt;subprocess&lt;/code&gt; module to run &lt;code&gt;helm list&lt;/code&gt; to get all the releases, run a simple loop over all the releases, and then &lt;code&gt;helm uninstall&lt;/code&gt; them. Nothing fancy here; only use Python to run some CLI commands.&lt;/p&gt;

&lt;p&gt;Talk is cheap; show me the code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/IronCore864/ca1e74a65f4a97937d93c63c094e9d32" rel="noopener noreferrer"&gt;https://gist.github.com/IronCore864/ca1e74a65f4a97937d93c63c094e9d32&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4 Introducing &lt;code&gt;concurrent.futures&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 &lt;code&gt;multiprocessing&lt;/code&gt; and &lt;code&gt;threading&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Before jumping right into &lt;code&gt;concurrent.futures&lt;/code&gt; (as advertised in the title of this blog), let's talk &lt;code&gt;multiprocessing&lt;/code&gt; and &lt;code&gt;threading&lt;/code&gt; for a bit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;threading&lt;/code&gt; module lets you work with multiple threads (also called lightweight processes or tasks) — multiple threads of control sharing their global data space.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;multiprocessing&lt;/code&gt; is a package that supports spawning processes. The &lt;code&gt;multiprocessing&lt;/code&gt; solves the Global Interpreter Lock issue using subprocesses instead of threads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When choosing between the two, simply put (might not be 100% precise, but that's the gist):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your task is CPU-intensive, go for &lt;code&gt;multiprocessing&lt;/code&gt; (which bypasses the GIL issue by utilizing multiple processes instead of threads).&lt;/li&gt;
&lt;li&gt;If your task is I/O-intensive, the &lt;code&gt;threading&lt;/code&gt; module should work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.2 What is &lt;code&gt;concurrent.futures&lt;/code&gt;, anyway?
&lt;/h3&gt;

&lt;p&gt;Now that we've got these two modules out of the way, what's &lt;code&gt;concurrent.futures&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;It is a higher-level interface to start async tasks and an abstraction layer on top of &lt;code&gt;threading&lt;/code&gt; and &lt;code&gt;multiprocessing&lt;/code&gt;. It's the preferred tool when you just want to run a piece of code concurrently and don't need the extra functionalities provided by the &lt;code&gt;threading&lt;/code&gt; or &lt;code&gt;multiprocessing&lt;/code&gt; module's API.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Learn with an Example
&lt;/h3&gt;

&lt;p&gt;OK, enough theory, let's get our hands dirty and learn by an example, an example from the official documentation:&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;import&lt;/span&gt; &lt;span class="n"&gt;concurrent.futures&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;

&lt;span class="n"&gt;URLS&lt;/span&gt; &lt;span class="o"&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;http://www.cnn.com/&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;http://www.bbc.co.uk/&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;http://some-made-up-domain.com/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# We can use a with statement to ensure threads are cleaned up promptly
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Start the load operations and mark each future with its URL
&lt;/span&gt;    &lt;span class="n"&gt;future_to_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;load_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;URLS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future_to_url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;future_to_url&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%r page is %d bytes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some observations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;code&gt;executor&lt;/code&gt;, whatever it is, is required. (Which can either be &lt;code&gt;ThreadPoolExecutor&lt;/code&gt; or &lt;code&gt;ProcessPoolExecutor&lt;/code&gt;, according to the doc.)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Executor.submit()&lt;/code&gt; method "submits" (or, in plain English, "schedules") the function calls (with parameters) and returns a Future object.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;concurrent.futures.as_completed&lt;/code&gt; method returns an iterator over the Future instance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5 The Concurrent Code to Solve the Task
&lt;/h2&gt;

&lt;p&gt;Once we understand the syntax and get a basic understanding of how it actually works, by copying and pasting from the example and being a little creative, it's easy to convert our non-concurrent version from the previous section into something concurrent. To put it all together:&lt;/p&gt;

&lt;p&gt;See the code below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/IronCore864/ad21130aa796d407624805c5342201db" rel="noopener noreferrer"&gt;https://gist.github.com/IronCore864/ad21130aa796d407624805c5342201db&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Voila!&lt;/p&gt;

&lt;p&gt;Note that the &lt;code&gt;concurrent.futures&lt;/code&gt; part is of precisely the same structure as the official &lt;code&gt;concurrent.futures&lt;/code&gt; example.&lt;/p&gt;




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

&lt;p&gt;&lt;code&gt;concurrent.futures&lt;/code&gt; cheat sheet:&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="c1"&gt;# or, with concurrent.futures.ProcessPoolExecutor()
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;future_objects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;some_func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt; &lt;span class="p"&gt;...):&lt;/span&gt; &lt;span class="n"&gt;param1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;param1&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xxx&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future_objects&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;future_objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="nf"&gt;do_something&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule of thumb: use &lt;code&gt;ThreadPoolExecutor&lt;/code&gt; for I/O-intensive workload and &lt;code&gt;ProcessPoolExecutor&lt;/code&gt; for CPU-intensive workload.&lt;/p&gt;

&lt;p&gt;If you enjoyed this article, please like, comment, subscribe. See you in the next piece.&lt;/p&gt;

</description>
      <category>crypto</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>offers</category>
    </item>
    <item>
      <title>A Comprehensive Tutorial on Service Mesh, Istio, Envoy, Access Log, and Log Filtering</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Thu, 19 Jan 2023 08:24:24 +0000</pubDate>
      <link>https://dev.to/ironcore864/a-comprehensive-tutorial-on-service-mesh-istio-envoy-access-log-and-log-filtering-2j3i</link>
      <guid>https://dev.to/ironcore864/a-comprehensive-tutorial-on-service-mesh-istio-envoy-access-log-and-log-filtering-2j3i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;First things first, I confess: I haven’t used photos that I took as featured images for a while, and I truly miss that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article, we will briefly introduce Envoy, enable Envoy access log in Istio, play with Envoy's access log filters, and figure out ways to configure Envoy access log filters with Istio.&lt;/p&gt;

&lt;p&gt;Some basic knowledge of Istio is expected, but even if you have none, you can follow this tutorial to have a successful local setup.&lt;/p&gt;

&lt;p&gt;Without further adieu, let's get started.&lt;/p&gt;




&lt;h2&gt;
  
  
  1 A Very Short Introduction to Envoy
&lt;/h2&gt;

&lt;p&gt;Envoy is an L7 high-performance proxy and communication bus developed in C++ and designed for large modern service-oriented architectures.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is a self-contained process designed to run alongside every application server.&lt;/li&gt;
&lt;li&gt;At its very core, Envoy is, in fact, an L3/L4 network proxy. A pluggable filter chain mechanism allows filters to be written to perform different TCP/UDP proxy tasks and inserted into the main server.&lt;/li&gt;
&lt;li&gt;Envoy supports an additional HTTP L7 filter layer; HTTP filters can be plugged into the HTTP connection management subsystem that performs different tasks such as buffering, rate limiting, routing/forwarding, etc.&lt;/li&gt;
&lt;li&gt;When operating in HTTP mode, Envoy supports a routing subsystem capable of routing and redirecting requests based on path, authority, content type, runtime values, etc. This functionality is most useful when using Envoy as a front/edge proxy but is also leveraged when building a service-to-service mesh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Envoy has many other high-level features, but the ones mentioned above make it perfect to be used as a sidecar proxy in a service mesh.&lt;/p&gt;

&lt;p&gt;Two core concepts about Envoy are related to this tutorial:&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1 HTTP Connection Management
&lt;/h3&gt;

&lt;p&gt;HTTP is such a critical component of modern service-oriented architectures that Envoy implements a large amount of HTTP-specific functionality.&lt;/p&gt;

&lt;p&gt;Envoy has a built-in network-level filter called the &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/http_conn_man#config-http-conn-man" rel="noopener noreferrer"&gt;HTTP connection manager&lt;/a&gt;, which translates raw bytes into HTTP level messages and events (e.g., headers received, body data received, trailers received, etc.). It also handles functionality common to all HTTP connections and requests, such as access logging, request ID generation and tracing, request/response header manipulation, route table management, and statistics.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Access Logging
&lt;/h3&gt;

&lt;p&gt;When used in a service-mesh scenario, for example, in &lt;a href="https://istio.io/" rel="noopener noreferrer"&gt;Istio&lt;/a&gt;, the simplest kind of logging is Envoy's access logging.&lt;/p&gt;

&lt;p&gt;Envoy proxies print access information to their standard output. The kubectl logs command can print Envoy's containers' standard output.&lt;/p&gt;




&lt;h2&gt;
  
  
  2 A Very Introduction to Istio (And Service Mesh)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 Service Mesh
&lt;/h3&gt;

&lt;p&gt;A service mesh is a dedicated infrastructure layer added to distributed microservices. It allows us to add capabilities transparently like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;observability&lt;/li&gt;
&lt;li&gt;traffic management&lt;/li&gt;
&lt;li&gt;security&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And these are achieved without changing the application code.&lt;/p&gt;

&lt;p&gt;As the deployment of distributed services grows in size and complexity, it becomes harder to understand and manage all the services, the requirements of which include discovery, load balancing, failure recovery, metrics, monitoring, etc. And inter-service communication is what makes a distributed application possible. Routing this communication within and across application clusters becomes increasingly complex as the number of services grows.&lt;/p&gt;

&lt;p&gt;A service mesh helps reduce the aforementioned while easing the strain on development teams.&lt;/p&gt;

&lt;p&gt;It can often handle more complex operational things like A/B testing, canary deployments, rate limiting, access control, encryption, and end-to-end authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Istio
&lt;/h3&gt;

&lt;p&gt;Istio is an open-source service mesh that layers transparently onto existing distributed applications. Istio's robust features provide a uniform and more efficient way to secure, connect, and monitor services. Istio is the path to load balancing, service-to-service authentication, and monitoring – with few or no service code changes. Its powerful control plane brings vital features, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure service-to-service communication in a cluster with TLS encryption, strong identity-based authentication, and authorization&lt;/li&gt;
&lt;li&gt;Automatic load balancing for HTTP, gRPC, WebSocket, and TCP traffic&lt;/li&gt;
&lt;li&gt;Fine-grained control of traffic behavior with rich routing rules, retries, failovers, and fault injection&lt;/li&gt;
&lt;li&gt;A pluggable policy layer and configuration API supporting access controls, rate limits, and quotas&lt;/li&gt;
&lt;li&gt;Automatic metrics, logs, and traces for all traffic within a cluster, including cluster ingress and egress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Istio is designed for extensibility and can handle various deployment needs. Istio's control plane runs on Kubernetes. You can add applications deployed in that cluster to your mesh, extend the mesh to other clusters, or even connect VMs or other endpoints outside Kubernetes.&lt;/p&gt;

&lt;p&gt;A large ecosystem of contributors, partners, integrations and distributors extend and leverage Istio for a wide variety of scenarios. You can install Istio yourself, or some vendors have products that integrate Istio and manage it for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Istio and Envoy
&lt;/h3&gt;

&lt;p&gt;Istio uses Envoy as proxies, deployed as sidecars. These proxies mediate and control all network communication between microservices. They also collect and report telemetry on all mesh traffic.&lt;/p&gt;

&lt;p&gt;It's worth mentioning that, for the sake of unbias, there are other choices of service mesh besides Istio, some of which are even better from certain standpoints. But since Envoy and Istio are closely related, today we will use Istio to demo Envoy's access log settings.&lt;/p&gt;

&lt;p&gt;OK, now that we cleared the nomenclature of service mesh and Istio out of the way, let's play with Envoy's access log. We will do this with Istio.&lt;/p&gt;




&lt;h2&gt;
  
  
  3 Prepare a Local Kubernetes Cluster with Istio
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Prepare a Local Kubernetes Cluster
&lt;/h3&gt;

&lt;p&gt;One of the easiest ways to start a local Kubernetes cluster is to use &lt;a href="https://minikube.sigs.k8s.io/docs/start/" rel="noopener noreferrer"&gt;minikube&lt;/a&gt;. Follow the instructions in the official installation documentation, then run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2 Install Istio
&lt;/h3&gt;

&lt;p&gt;Download Istio, install, then enable istio-injection in the default namespace by running the following commands:&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;-L&lt;/span&gt; https://istio.io/downloadIstio | sh -
&lt;span class="c"&gt;# your version might differ&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;istio-1.16.1  
&lt;span class="c"&gt;# for easier usage&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt; 
&lt;span class="c"&gt;# here, we use the minimal profile so that the access log isn't enabled by default, which we want to do ourselves&lt;/span&gt;
istioctl &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;minimal &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="c"&gt;# enable injection in the default namespace&lt;/span&gt;
kubectl label namespace default istio-injection&lt;span class="o"&gt;=&lt;/span&gt;enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.3 Deploy the Testing Apps
&lt;/h3&gt;

&lt;p&gt;First, let's deploy some sample apps for testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; samples/sleep/sleep.yaml
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SOURCE_POD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get pod &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;.items..metadata.name&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; samples/httpbin/httpbin.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This set of commands will deploy two applications: one is the "curl" command in a pod, which we will use to send out HTTP requests, and the other is an HTTP server that receives requests, which we will use to demonstrate the access logs of Envoy.&lt;/p&gt;




&lt;h2&gt;
  
  
  4 Envoy Access Logs in Istio
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Enable Access Logs
&lt;/h3&gt;

&lt;p&gt;Then, let's enable access logs.&lt;/p&gt;

&lt;p&gt;Istio offers a few ways to enable access logs. Use of the Telemetry API is recommended:&lt;/p&gt;

&lt;p&gt;First, we create a file &lt;code&gt;telemetry.yaml&lt;/code&gt; with the following content:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telemetry.istio.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Telemetry&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;mesh-default&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;istio-system&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;accessLogging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;providers&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;Envoy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; telemetry.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;First, let's send a request from sleep to httpbin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SOURCE_POD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; httpbin:8000/status/418
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we check the httpbin's log, we can see something similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl logs &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;httpbin &lt;span class="nt"&gt;-c&lt;/span&gt; istio-proxy
&lt;span class="o"&gt;[&lt;/span&gt;2020-11-25T21:26:18.409Z] &lt;span class="s2"&gt;"GET /status/418 HTTP/1.1"&lt;/span&gt; 418 - via_upstream - &lt;span class="s2"&gt;"-"&lt;/span&gt; 0 135 3 1 &lt;span class="s2"&gt;"-"&lt;/span&gt; &lt;span class="s2"&gt;"curl/7.73.0-DEV"&lt;/span&gt; &lt;span class="s2"&gt;"84961386-6d84-929d-98bd-c5aee93b5c88"&lt;/span&gt; &lt;span class="s2"&gt;"httpbin:8000"&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1:80"&lt;/span&gt; inbound|8000|| 127.0.0.1:41854 10.44.1.27:80 10.44.1.23:37652 outbound_.8000_._.httpbin.foo.svc.cluster.local default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5 Envoy Access Log Filter
&lt;/h2&gt;

&lt;p&gt;Now that we have enabled access logs for Envoy, let's play with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 The Task
&lt;/h3&gt;

&lt;p&gt;Imagine the following situation: your application has some endpoints, for example, &lt;code&gt;/status&lt;/code&gt;, &lt;code&gt;/liveness&lt;/code&gt;, and &lt;code&gt;/readiness&lt;/code&gt;, which you don't want logs because there might be multiple requests per minute. These status check logs could not be a good use of logging resources.&lt;/p&gt;

&lt;p&gt;Or, you may want to customize your access logs to allow different requests and responses to be written to separate log files.&lt;/p&gt;

&lt;p&gt;Can we achieve that?&lt;/p&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;Envoy supports several built-in access logs and extension filters registered at runtime. Now let's try to demo the log filter functions:&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Download Envoy And Prepare a Config File
&lt;/h3&gt;

&lt;p&gt;The easiest way to play with Envoy's configuration is by running it locally instead of as a sidecar as part of Istio. So let's do this:&lt;/p&gt;

&lt;p&gt;Install Envoy:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Download the demo config from &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/_downloads/92dcb9714fb6bc288d042029b34c0de4/envoy-demo.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then let's start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;envoy &lt;span class="nt"&gt;-c&lt;/span&gt; envoy-demo.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, open another tab, and verify that Envoy is running on port 10000:&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;-v&lt;/span&gt; localhost:10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we go back to the tab where Envoy is running, we can see the access log from Envoy, which will look similar to the following:&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="o"&gt;[&lt;/span&gt;2023-01-19T03:10:59.085Z] &lt;span class="s2"&gt;"GET / HTTP/1.1"&lt;/span&gt; 200 - 0 17304 747 467 &lt;span class="s2"&gt;"-"&lt;/span&gt; &lt;span class="s2"&gt;"curl/7.85.0"&lt;/span&gt; &lt;span class="s2"&gt;"4b35db72-baf8-4730-885e-3e628757f0e3"&lt;/span&gt; &lt;span class="s2"&gt;"www.envoyproxy.io"&lt;/span&gt; &lt;span class="s2"&gt;"34.143.223.220:443"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.3 Envoy Log Filter
&lt;/h3&gt;

&lt;p&gt;Envoy provides a bunch of log filters, for example, status code filter (as the name suggests, filter by status code), header filter, etc. Read the &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/accesslog/v3/accesslog.proto.html" rel="noopener noreferrer"&gt;official doc here&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;The documentation could be more detailed; it doesn't provide examples of the usage of each filter. But that won't block a senior DevOps engineer. Based on some simple searches on Google (and GitHub), let's try this piece of config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
          access_log:
          - name: Envoy.access_loggers.stdout
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
            filter:
              header_filter:
                header:
                  name: :Path
                  string_match:
                    exact: /status
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not hard to understand: it works on the header, and if the path matches something, the filter catches it.&lt;/p&gt;

&lt;p&gt;If we add this piece of code into the file &lt;code&gt;envoy-demo.yaml&lt;/code&gt;, and if we access the &lt;code&gt;/status&lt;/code&gt; URL:&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;-v&lt;/span&gt; localhost:10000/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There will be some logs like:&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="o"&gt;[&lt;/span&gt;2023-01-19T03:23:08.054Z] &lt;span class="s2"&gt;"GET /status HTTP/1.1"&lt;/span&gt; 404 - 0 3413 776 690 &lt;span class="s2"&gt;"-"&lt;/span&gt; &lt;span class="s2"&gt;"curl/7.85.0"&lt;/span&gt; &lt;span class="s2"&gt;"e507c86f-004f-4cba-b622-2004c7cf97c7"&lt;/span&gt; &lt;span class="s2"&gt;"www.envoyproxy.io"&lt;/span&gt; &lt;span class="s2"&gt;"34.126.184.144:443"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if we access URLs other than &lt;code&gt;/status&lt;/code&gt;, such as &lt;code&gt;/&lt;/code&gt;:&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;-v&lt;/span&gt; localhost:10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We get no logs.&lt;/p&gt;

&lt;p&gt;OK, so the log filter is working now. Except that we want to filter out logs like &lt;code&gt;/status&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.4 More on Envoy Log Filters
&lt;/h3&gt;

&lt;p&gt;OK, let's go back to the &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/accesslog/v3/accesslog.proto.html" rel="noopener noreferrer"&gt;official doc&lt;/a&gt; and some google searches.&lt;/p&gt;

&lt;p&gt;It's not hard to see that some filters like "and_filter" are helpful. Let's try that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
            filter:
              and_filter:
                filters:
                - header_filter:
                    header:
                      name: :Path
                      string_match:
                        exact: /status
                      invert_match: true
                - header_filter:
                    header:
                      name: :Path
                      string_match:
                        exact: /liveness
                      invert_match: true
                - header_filter:
                    header:
                      name: :Path
                      string_match:
                        exact: /readiness
                      invert_match: true
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use &lt;code&gt;and_filter&lt;/code&gt; to combine a bunch of filters, and we can negate the matching result by using &lt;code&gt;invert_match&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This config updated in the &lt;code&gt;envoy-demo.yaml&lt;/code&gt; will only show logs if the URL doesn't match &lt;code&gt;/status&lt;/code&gt;, &lt;code&gt;/liveness&lt;/code&gt;, or &lt;code&gt;/readiness&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;OK, we have achieved our goal: we can use filters to control Envoy's access logging. In the example above, we used &lt;code&gt;and_filter&lt;/code&gt; and &lt;code&gt;header_filter&lt;/code&gt; to filter out specific unwanted log entries. If you mix and match all Envoy's filters, you can quickly achieve things like "direct certain logs to a specific file", "only log 4xx requests", etc.&lt;/p&gt;

&lt;p&gt;But (and this is a big but), the above config is only for local usage or running Envoy as a standalone process.&lt;/p&gt;

&lt;p&gt;How to put those filter settings into a sidecar Envoy?&lt;/p&gt;




&lt;h2&gt;
  
  
  6 Telemetry API
&lt;/h2&gt;

&lt;p&gt;As previously mentioned, Istio offers a few ways to enable access logs. We used Telemetry API in section 4.1, so let's see if we can customize Telemetry API to achieve the log filtering goal.&lt;/p&gt;

&lt;p&gt;Telemetry defines how the telemetry is generated for workloads within a mesh. It has a feature &lt;a href="https://istio.io/latest/docs/reference/config/telemetry/#AccessLogging-Filter" rel="noopener noreferrer"&gt;AccessLogging.Filter&lt;/a&gt; where you can specify an expression in a string to achieve goals like &lt;code&gt;response.code &amp;gt;= 400&lt;/code&gt; or &lt;code&gt;connection.mtls &amp;amp;&amp;amp; request.url_path.contains('v1beta3')&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;According to the official documentation (link above), the expression should be a &lt;a href="https://github.com/google/cel-spec" rel="noopener noreferrer"&gt;CEL&lt;/a&gt; expression (read more details on the language definition &lt;a href="https://github.com/google/cel-spec/blob/master/doc/langdef.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, if we convert the filters we wrote above into a CEL expression, it would be like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expression: "request.url_path != '/status' &amp;amp;&amp;amp; request.url_path != '/liveness' &amp;amp;&amp;amp; request.url_path != '/readiness'"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we update the &lt;code&gt;telemetry.yaml&lt;/code&gt; with the following content:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telemetry.istio.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Telemetry&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;mesh-default&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;istio-system&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;accessLogging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;providers&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;Envoy&lt;/span&gt;
      &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;expression&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request.url_path&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;'/status'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;request.url_path&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;'/liveness'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;request.url_path&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;'/readiness'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; telemetry.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the istio-proxy sidecar (Envoy) will only log entries where the request URL path isn't &lt;code&gt;/status&lt;/code&gt;, &lt;code&gt;/liveness&lt;/code&gt;, or &lt;code&gt;/readiness&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  7 EnvoyFilter
&lt;/h2&gt;

&lt;p&gt;EnvoyFilter provides a mechanism to customize the Envoy configuration. Use EnvoyFilter to modify values for certain fields, add specific filters, or even add entirely new listeners, clusters, etc. This feature must be used carefully, as incorrect configurations could destabilize the entire mesh. Unlike other Istio networking objects, EnvoyFilters are additively applied. Any number of EnvoyFilters can exist for a given workload in a specific namespace. The order of application of these EnvoyFilters is as follows: all EnvoyFilters in the config root namespace, followed by all matching EnvoyFilters in the workload's namespace.&lt;/p&gt;

&lt;p&gt;We can use EnvoyFilter to configure Envoy access logs, although officially, &lt;a href="https://github.com/istio/istio/wiki/EnvoyFilter-Samples#tracing-and-access-logging" rel="noopener noreferrer"&gt;"Istio Telemetry API provides a first-class way to configure access logs and traces. It is recommended to use that method."&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nevertheless, let's look at EnvoyFilter and how to use that.&lt;/p&gt;

&lt;p&gt;First, let's delete the Telemetry we created before to disable the access log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; telemetry.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, following the format in this &lt;a href="https://github.com/istio/istio/wiki/EnvoyFilter-Samples#tracing-and-access-logging" rel="noopener noreferrer"&gt;doc&lt;/a&gt;, we can create a file &lt;code&gt;envoyfilter.yaml&lt;/code&gt; with the following content:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.istio.io/v1alpha3&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EnvoyFilter&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;access-log&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;configPatches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;applyTo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NETWORK_FILTER&lt;/span&gt;
    &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ANY&lt;/span&gt;
      &lt;span class="na"&gt;listener&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;filterChain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;filter&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;Envoy.filters.network.http_connection_manager"&lt;/span&gt;
    &lt;span class="na"&gt;patch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MERGE&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;typed_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@type"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"&lt;/span&gt;
          &lt;span class="na"&gt;access_log&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;Envoy.file_access_log&lt;/span&gt;
            &lt;span class="na"&gt;typed_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@type"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog"&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;/dev/stdout&lt;/span&gt;
              &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[%START_TIME%]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;%REQ(:METHOD)%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%PROTOCOL%&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%RESPONSE_CODE%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%RESPONSE_CODE_DETAILS%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;%RESP(GRPC-STATUS)%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%RESP(GRPC-MESSAGE)%&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%RESPONSE_FLAGS%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%BYTES_RECEIVED%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%BYTES_SENT%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%DURATION%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;%REQ(X-FORWARDED-FOR)%&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;%REQ(USER-AGENT)%&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;%REQ(X-REQUEST-ID)%&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;%REQ(:AUTHORITY)%&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;%UPSTREAM_HOST%&lt;/span&gt;&lt;span class="se"&gt;\"\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
            &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;and_filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;header_filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;header&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;:Path&lt;/span&gt;
                      &lt;span class="na"&gt;string_match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                        &lt;span class="na"&gt;exact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/status&lt;/span&gt;
                      &lt;span class="na"&gt;invert_match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;header_filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;header&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;:Path&lt;/span&gt;
                      &lt;span class="na"&gt;string_match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                        &lt;span class="na"&gt;exact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/liveness&lt;/span&gt;
                      &lt;span class="na"&gt;invert_match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;header_filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;header&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;:Path&lt;/span&gt;
                      &lt;span class="na"&gt;string_match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                        &lt;span class="na"&gt;exact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/readiness&lt;/span&gt;
                      &lt;span class="na"&gt;invert_match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We create an object of the "EnvoyFilter" kind, and the content looks the same as Envoy's configuration file.&lt;/p&gt;

&lt;p&gt;If we apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; envoyfilter.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the istio-proxy sidecar (Envoy) will only log entries where the request URL path isn't &lt;code&gt;/status&lt;/code&gt;, &lt;code&gt;/liveness&lt;/code&gt;, or &lt;code&gt;/readiness&lt;/code&gt;, just like the Telemetry way.&lt;/p&gt;




&lt;h2&gt;
  
  
  8 Teardown
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;In this tutorial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We introduced Envoy, service mesh, and Istio.&lt;/li&gt;
&lt;li&gt;Then, we created a local Kubernetes cluster and installed Istio inside it.&lt;/li&gt;
&lt;li&gt;We enabled Envoy access logs in Istio via Telemetry and played with Envoy's configurations to achieve log filtering.&lt;/li&gt;
&lt;li&gt;We also looked at two ways of setting log filtering for the Envoy sidecar in Istio (Telemetry and EnvoyFilter).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I know not all people have the need to fine-tune Envoy’s access logging, but if you do, you will find it hard to get some examples off the internet. That’s one of the reasons I decided to publish my experience as a blog: if you are searching hard to figure out how to write filters, and_filter, EnvoyFilter, or Telemetry API AccessLogging, CEL expression syntax, you are not alone, and I hope this article helps.&lt;/p&gt;

&lt;p&gt;For people who are not so crazy about Envoy access logging, this tutorial still serves as a very good introduction to service mesh/Istio/Envoy.&lt;/p&gt;

&lt;p&gt;If you like this article, please like, comment, subscribe. See you in the next one!&lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>App-Centric Configuration: Adding More Value to Your Life</title>
      <dc:creator>Tiexin Guo</dc:creator>
      <pubDate>Wed, 30 Nov 2022 01:55:06 +0000</pubDate>
      <link>https://dev.to/devstream/app-centric-configuration-adding-more-value-to-your-life-8l0</link>
      <guid>https://dev.to/devstream/app-centric-configuration-adding-more-value-to-your-life-8l0</guid>
      <description>&lt;h2&gt;
  
  
  DevStream's Story
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/devstream-io/devstream/releases/tag/v0.1.0"&gt;About nine months ago, DevStream was first publicly released&lt;/a&gt;. Since then, it has evolved a lot. If this is the first time you have come across DevStream, maybe read &lt;a href="https://blog.devstream.io/posts/hello-world/"&gt;this blog&lt;/a&gt; for a quick overview.&lt;/p&gt;

&lt;p&gt;In the current incarnation (v0.9), every single DevOps tool (including integrations of tools and CI/CD pipeline setup for apps) is treated as a "Tool", which is a DevStream concept. So, if you would like to define a DevOps platform that does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repository scaffolding&lt;/li&gt;
&lt;li&gt;continuous integration w/ GitHub Actions&lt;/li&gt;
&lt;li&gt;install Argo CD as the tool for continuous deployment&lt;/li&gt;
&lt;li&gt;continuous deployment w/ Argo CD&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your DevStream &lt;code&gt;tools.yaml&lt;/code&gt; config file would look similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tools&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;repo-scaffolding&lt;/span&gt;
  &lt;span class="na"&gt;instanceID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;destinationRepo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;githubUser&lt;/span&gt; &lt;span class="pi"&gt;]]&lt;/span&gt;
      &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;app&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;repoType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github&lt;/span&gt;
    &lt;span class="na"&gt;sourceRepo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;org&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;githubUser&lt;/span&gt; &lt;span class="pi"&gt;]]&lt;/span&gt;
      &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dtm-scaffolding-python&lt;/span&gt;
      &lt;span class="na"&gt;repoType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github&lt;/span&gt;
    &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ImageRepo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;dockerUser&lt;/span&gt; &lt;span class="pi"&gt;]]&lt;/span&gt;&lt;span class="s"&gt;/[[ app ]]&lt;/span&gt;
      &lt;span class="na"&gt;AppName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;app&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;githubactions-python&lt;/span&gt;
  &lt;span class="na"&gt;instanceID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;repo-scaffolding.myapp&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;githubUser&lt;/span&gt; &lt;span class="pi"&gt;]]&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="pi"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt; &lt;span class="pi"&gt;]]&lt;/span&gt;
    &lt;span class="na"&gt;language&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;python&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;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dockerhub&lt;/span&gt;
        &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;dockerUser&lt;/span&gt; &lt;span class="pi"&gt;]]&lt;/span&gt;
        &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;app&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;helm-installer&lt;/span&gt;
  &lt;span class="na"&gt;instanceID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&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;argocdapp&lt;/span&gt;
  &lt;span class="na"&gt;instanceID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;dependsOn&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;argocd.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;githubactions-python.default"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&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="pi"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt; &lt;span class="pi"&gt;]]&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
    &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://kubernetes.default.svc&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;valuefile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;values.yaml&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;helm/[[ app ]]&lt;/span&gt;
      &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{repo-scaffolding.myapp.outputs.repoURL}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quick explanation - what DevStream does with this config is the following: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install Argo CD;&lt;/li&gt;
&lt;li&gt;repository scaffolding for myapp;&lt;/li&gt;
&lt;li&gt;CI/CD for myapp.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It looks nice and works like a charm.&lt;/p&gt;

&lt;p&gt;However, suppose you have more than one app to worry about (more often than not, that would be the case in the microservice era). In that case, things start to get a bit complicated: you'd have to repeat the &lt;code&gt;githubactions-golang&lt;/code&gt; and &lt;code&gt;argocdapp&lt;/code&gt; sections as many times as the number of apps you manage.&lt;/p&gt;

&lt;p&gt;That is to say, to manage ten apps/microservices, your DevStream YAML file may grow to well past 300 lines of config.&lt;/p&gt;

&lt;p&gt;Today, we are happy to announce that it's no longer the case. We've simplified it. By a big margin. With the release of v0.10.&lt;/p&gt;

&lt;p&gt;Read on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding Value to Your Life
&lt;/h2&gt;

&lt;p&gt;A big config is definitely harder to read and maintain, and we don't like that. That's why we decided to solve this problem in the first place.&lt;/p&gt;

&lt;p&gt;However, we waited to get right into it.&lt;/p&gt;

&lt;p&gt;Instead, we started to think about our market positioning and value proposition. What is DevStream anyway? What problems does it solve? Why would other engineers want to use it instead of building stuff manually or purchasing one-stop DevOps platforms?&lt;/p&gt;

&lt;p&gt;If we could precisely answer these questions, we would know how to improve it to the next level.&lt;/p&gt;

&lt;p&gt;The discussion went on and on. The whole team, including our CEO, discussed multiple days. We've put tens of hours of thought and debate into it, until we figured out where exactly DevStream can add value to your life:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installing DevOps tools (but that's not the point at all)&lt;/li&gt;
&lt;li&gt;integrating DevOps tools and building engineering platforms (now we're talking)&lt;/li&gt;
&lt;li&gt;application lifecycle/software development lifecycle management: 

&lt;ul&gt;
&lt;li&gt;repository bootstrapping&lt;/li&gt;
&lt;li&gt;continuous integration pipelines, integrating best practices, typical stages, and steps into it&lt;/li&gt;
&lt;li&gt;continuous deployment pipelines, integrating helm chart/Dockerfile best practices&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;&lt;em&gt;Note: read more on &lt;a href="https://www.redhat.com/en/topics/devops/what-is-application-lifecycle-management-alm"&gt;Application Lifecycle Management (ALM) here&lt;/a&gt; and &lt;a href="https://medium.com/gitguardian/how-adding-security-into-devops-accelerates-the-sdlc-pt-1-459134252ee7"&gt;Software Development Lifecycle (SDLC) here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Lightbulb: Apps? Apps!
&lt;/h2&gt;

&lt;p&gt;Since many values DevStream could bring are around applications/microservices and their lifecycle management, why not simply build configurations based on that?&lt;/p&gt;

&lt;p&gt;We call this new thought "app-centric" configuration, and let's get right into it:&lt;/p&gt;




&lt;h2&gt;
  
  
  Backward Compatibility
&lt;/h2&gt;

&lt;p&gt;First things first, the previous "tools" config still works. We wouldn't want to break that. That is to say, if you copy-paste the first code snippet from the beginning of this article, it's supposed to work without any problem, just like previous versions. &lt;/p&gt;




&lt;h2&gt;
  
  
  (Much) Shorter, Simpler, Easier, While Achieving the Same
&lt;/h2&gt;

&lt;p&gt;You'd be surprised that the config below does exactly the same thing as the code snippet at the very beginning of this article.&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;apps&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;myapp&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;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
    &lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;django&lt;/span&gt;
  &lt;span class="na"&gt;repo&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="s"&gt;github.com/devstream-io/myapp&lt;/span&gt;
  &lt;span class="na"&gt;repoTemplate&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="s"&gt;github.com/devstream-io/dtm-scaffolding-python&lt;/span&gt;
  &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;githubactions&lt;/span&gt;
  &lt;span class="na"&gt;cd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocdapp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is this magic? How'd we do that?&lt;/p&gt;

&lt;p&gt;First, we created a new abstraction that is called "apps". Each app corresponds to a real-world application or microservice that you manage.&lt;/p&gt;

&lt;p&gt;Secondly, we simplified the configurations as much as possible with five small sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;spec: language, framework related to the application, which is shared information with other sections&lt;/li&gt;
&lt;li&gt;repo: where to create/bootstrap the repository for the app&lt;/li&gt;
&lt;li&gt;repoTemplate: the template used to bootstrap the app's repo&lt;/li&gt;
&lt;li&gt;ci: setting up continuous integration for this app&lt;/li&gt;
&lt;li&gt;cd: setting up continuous deployment for this app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last but not least, more "defaults" and "best practices" are integrated, by default, into DevStream, so that you don't have to override most of the configs.&lt;/p&gt;

&lt;p&gt;Neat, right? I know.&lt;/p&gt;




&lt;h2&gt;
  
  
  One File to Rule Them All
&lt;/h2&gt;

&lt;p&gt;Putting It All Together:&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;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;stateFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;devstream.state&lt;/span&gt;

&lt;span class="na"&gt;tools&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;helm-installer&lt;/span&gt;
  &lt;span class="na"&gt;instanceID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;

&lt;span class="na"&gt;apps&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;myapp1&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;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
    &lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;django&lt;/span&gt;
  &lt;span class="na"&gt;repo&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="s"&gt;github.com/devstream-io/myapp1&lt;/span&gt;
  &lt;span class="na"&gt;repoTemplate&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="s"&gt;github.com/devstream-io/dtm-scaffolding-python&lt;/span&gt;
  &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;githubactions&lt;/span&gt;
  &lt;span class="na"&gt;cd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocdapp&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;myapp2&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;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;golang&lt;/span&gt;
    &lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gin&lt;/span&gt;
  &lt;span class="na"&gt;repo&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="s"&gt;github.com/devstream-io/myapp2&lt;/span&gt;
  &lt;span class="na"&gt;repoTemplate&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="s"&gt;github.com/devstream-io/dtm-scaffolding-golang&lt;/span&gt;
  &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;githubactions&lt;/span&gt;
  &lt;span class="na"&gt;cd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocdapp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope you enjoy this new feature. Have fun experimenting tools together with apps!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>devstream</category>
      <category>platformengineering</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
