<?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>Forem: Patrick Smyth</title>
    <description>The latest articles on Forem by Patrick Smyth (@smythp).</description>
    <link>https://forem.com/smythp</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%2F1393712%2F7f8cf204-c693-4aa0-9526-d7655a87695c.jpeg</url>
      <title>Forem: Patrick Smyth</title>
      <link>https://forem.com/smythp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/smythp"/>
    <language>en</language>
    <item>
      <title>Deep Dive 🤿: Where Does Grype Data Come From?</title>
      <dc:creator>Patrick Smyth</dc:creator>
      <pubDate>Tue, 12 Nov 2024 16:16:18 +0000</pubDate>
      <link>https://forem.com/chainguard/deep-dive-where-does-grype-data-come-from-n9e</link>
      <guid>https://forem.com/chainguard/deep-dive-where-does-grype-data-come-from-n9e</guid>
      <description>&lt;p&gt;Grype is a vulnerability scanner for container images and filesystems. It's  developed by &lt;a href="https://anchore.com/" rel="noopener noreferrer"&gt;Anchore&lt;/a&gt; and written in Golang. When you point Grype at a container image, it will scan the files and folders on that image, compare what it finds to a database of CVEs (known vulnerabilities), and spit out a report telling you what CVEs have been detected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/anchore/grype" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2fym4d9sjjkf3f6q2yt.png" alt="Grype logo, it's like a creepy monster guy" width="234" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We like Grype at &lt;a href="https://www.chainguard.dev/" rel="noopener noreferrer"&gt;Chainguard&lt;/a&gt; because it's open source, customizable, and reliable enough to integrate into our CI and CVE remediation workflows. (You can read more about &lt;a href="https://www.chainguard.dev/unchained/why-chainguard-uses-grype-as-its-first-line-of-defense-for-cves" rel="noopener noreferrer"&gt;why we like Grype in this post&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0u8p5jtf43wxql1zfozr.png" class="article-body-image-wrapper"&gt;&lt;img alt="Expertly photoshopped I Like Ike pin edited to read I Like Grype" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0u8p5jtf43wxql1zfozr.png" width="794" height="596"&gt;&lt;/a&gt;&lt;br&gt;I know, my photoshop skills scare even me sometimes.
  &lt;/p&gt;

&lt;p&gt;In this article, we'll answer a question that comes up frequently: where does Grype's vulnerability data come from? In the process, we'll take a look at Grype's open data pipe line and do some light analysis of the vulnerability data that Grype uses to scan containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Grype Works
&lt;/h2&gt;

&lt;p&gt;If you haven't used Grype before, here's a brief overview of how it works.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You point Grype at a container image (or filesystem).&lt;/li&gt;
&lt;li&gt;Grype downloads a fresh instance of its &lt;code&gt;vulnerability.db&lt;/code&gt; database, then scans the image for specific packages, files, configurations, and so on, building a manifest in the form of a Software Bill of Materials (SBOM) itemizing the software contained in the image. (Under the hood, Grype uses a sister tool, &lt;a href="https://github.com/anchore/syft" rel="noopener noreferrer"&gt;Syft&lt;/a&gt;, for this step.) &lt;/li&gt;
&lt;li&gt;Grype then compares the specific versions of each package against the vulnerability data in its database. &lt;/li&gt;
&lt;li&gt;Finally, a list of CVEs detected in the image is returned to the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a comprehensive overview of Grype's functionality, check out &lt;a href="https://edu.chainguard.dev/chainguard/chainguard-images/working-with-images/scanners/grype-tutorial/" rel="noopener noreferrer"&gt;Using Grype to Scan Container Images for Vulnerabilities&lt;/a&gt; on Chainguard Academy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grype's Data Sources
&lt;/h2&gt;

&lt;p&gt;Grype relies on a set of upstream providers for its vulnerability data. As of November 2024, the list of providers includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://secdb.alpinelinux.org" rel="noopener noreferrer"&gt;Alpine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://alas.aws.amazon.com/AL2/alas.rss%20&amp;amp;%20https://alas.aws.amazon.com/AL2022/alas.rss" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://security-tracker.debian.org/tracker/data/json%20&amp;amp;%20https://salsa.debian.org/security-tracker-team/security-tracker/raw/master/data/DSA/list" rel="noopener noreferrer"&gt;Debian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.github.com/graphql" rel="noopener noreferrer"&gt;GitHub Security Advisories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://services.nvd.nist.gov/rest/json/cves/2.0" rel="noopener noreferrer"&gt;NVD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://linux.oracle.com/security/oval" rel="noopener noreferrer"&gt;Oracle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.redhat.com/security/data/oval" rel="noopener noreferrer"&gt;RedHat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ftp.suse.com/pub/projects/security/oval" rel="noopener noreferrer"&gt;SLES&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://launchpad.net/ubuntu-cve-tracker" rel="noopener noreferrer"&gt;Ubuntu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://packages.wolfi.dev" rel="noopener noreferrer"&gt;Wolfi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that the above links are to endpoints where data is provided. Chainguard is one of the upstream providers, and updating scanners like Grype on the fixed status of packages in our upstream OS, &lt;a href="https://github.com/wolfi-dev" rel="noopener noreferrer"&gt;Wolfi&lt;/a&gt;, is a key element in maintaining the low-to-no CVE status of &lt;a href="https://www.chainguard.dev/chainguard-images?utm_source=cg-devrel" rel="noopener noreferrer"&gt;Chainguard Images&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Grype's &lt;code&gt;vulnerability.db&lt;/code&gt; gets rebuilt daily from data sourced from these upstream providers. To build this database, Grype uses two open source tools, &lt;a href="https://github.com/anchore/vunnel" rel="noopener noreferrer"&gt;&lt;code&gt;vunnel&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/anchore/grype-db" rel="noopener noreferrer"&gt;&lt;code&gt;grype-db&lt;/code&gt;&lt;/a&gt;. The &lt;code&gt;vunnel&lt;/code&gt; tool downloads, standardizes, and stores vulnerability data from the above upstream providers. Basically, it accesses the various provider endpoints and stores a local vulnerability database and metadata for each provider locally. The &lt;code&gt;grype-db&lt;/code&gt; utility collates this vulnerability data, building a much smaller &lt;code&gt;vulnerability.db&lt;/code&gt; usable by Grype.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Grype Database with &lt;code&gt;vunnel&lt;/code&gt; and &lt;code&gt;grype-db&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In this section, we'll try out the &lt;code&gt;vunnel&lt;/code&gt; and &lt;code&gt;grype-db&lt;/code&gt; utilities, building a local vulnerability cache and database.&lt;/p&gt;

&lt;p&gt;Since a built-daily &lt;code&gt;vulnerability.db&lt;/code&gt; file gets downloaded every time you run a Grype scan, why would you want to build Grype's &lt;code&gt;vulnerability.db&lt;/code&gt; manually? Building manually is useful if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want to use a subset of upstream sources&lt;/li&gt;
&lt;li&gt;You'd like to integrate other sources to create a custom &lt;code&gt;vulnerability.db&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You require older Grype schemas&lt;/li&gt;
&lt;li&gt;You'd like to contribute to Grype&lt;/li&gt;
&lt;li&gt;You want to understand more about Grype's upstream providers and data structure&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;vunnel&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The&lt;code&gt;grype-db&lt;/code&gt; utility uses &lt;code&gt;vunnel&lt;/code&gt; under the hood, but let's first try out &lt;code&gt;vunnel&lt;/code&gt; explicitly to see how it works. You'll need Python 3 installed for this section, and I'll assume it's accessible on your system using the &lt;code&gt;python&lt;/code&gt; command. (&lt;code&gt;vunnel&lt;/code&gt; is written in Python.)&lt;/p&gt;

&lt;p&gt;First, let's create a project folder:&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/vulnerability-data &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$_&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a virtual environment and activate it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now install &lt;code&gt;vunnel&lt;/code&gt; to the activated virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;vunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;vunnel&lt;/code&gt; is installed, we can use the &lt;code&gt;vunnel list&lt;/code&gt; command to show the current list of providers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vunnel list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alpine
amazon
chainguard
...
sles
ubuntu
wolfi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can download a local cache of provider data for any of these providers with the following (using &lt;code&gt;chainguard&lt;/code&gt; as an example provider):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vunnel run chainguard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;data&lt;/code&gt; folder where all provider data used as input and the standardized output database are contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data
└── chainguard
    ├── checksums
    ├── input
    │   └── secdb
    │       └── security.json
    ├── metadata.json
    └── results
        └── results.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;grype-db&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;grype-db&lt;/code&gt; utility can pull provider data with &lt;code&gt;vunnel&lt;/code&gt; under the hood, and can also collect and package up this data into the &lt;code&gt;vulnerability.db&lt;/code&gt; file used by Grype.&lt;/p&gt;

&lt;p&gt;In the following, we'll use &lt;code&gt;grype-db&lt;/code&gt; to download all provider data using &lt;code&gt;vunnel&lt;/code&gt; under the hood, then build the &lt;code&gt;vulnerability.db&lt;/code&gt; file. The process of downloading all provider data can take some time and uses about 8 GB of disk space.&lt;/p&gt;

&lt;p&gt;First, download the &lt;code&gt;grype-db&lt;/code&gt; script to the project folder we created previously:&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;-sSfL&lt;/span&gt; https://raw.githubusercontent.com/anchore/grype-db/main/install.sh | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd like to build from all available data, you'll need a GitHub token capable of authenticating as a user. This is because GitHub rate limits API access for non-authenticated users. You can follow these &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens" rel="noopener noreferrer"&gt;instructions provided by GitHub&lt;/a&gt;, but in short head to &lt;a href="https://github.com/settings/tokens" rel="noopener noreferrer"&gt;this token settings page on GitHub&lt;/a&gt;. Remember to safeguard your token as you would a password, and I recommend creating a scoped and short-lived (i.e. 7 days) token.&lt;/p&gt;

&lt;p&gt;Once you have your token, create a configuration file for &lt;code&gt;grype-db&lt;/code&gt; in our &lt;code&gt;~/vulnerability-data&lt;/code&gt; project folder. First, set your generated GitHub token to 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="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, generate the config in our &lt;code&gt;~/vulnerability-data&lt;/code&gt; project folder:&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; ~/vulnerability-data/.grype-db.yaml
provider:
  vunnel:
    executor: local
    generate-configs: true
    env:
      GITHUB_TOKEN: &lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have the &lt;code&gt;grype-db&lt;/code&gt; script and the &lt;code&gt;.grype-db.yaml&lt;/code&gt; configuration file in our project folder. Let's run a command that will pull all provider data, create a database file, and package it up for inclusion in a CI or other workflow. (For this step, you'll need &lt;code&gt;vunnel&lt;/code&gt; available, so the virtual environment created in the previous section on vunnel will need to still be activated, and you should run this command from the project folder where we downloaded the &lt;code&gt;grype-db&lt;/code&gt; script.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./grype-db &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Downloading and processing all provider data can take a long time, possibly hours, so go watch &lt;a href="https://www.youtube.com/watch?v=jG3iNpo3RnQ" rel="noopener noreferrer"&gt;Master of the Flying Guillotine&lt;/a&gt; or get some work done, I guess. ☕&lt;/p&gt;

&lt;p&gt;Once the process completes, a &lt;code&gt;build&lt;/code&gt; folder will have been created in our &lt;code&gt;~/vulnerability-data/&lt;/code&gt; project folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;build
├── listing.json
├── metadata.json
├── provider-metadata.json
├── vulnerability.db
└── vulnerability-db_v5_2024-11-05T19:09:08Z_1730848219.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;vulnerability.db&lt;/code&gt; database should be the same as the database built daily for use by Grype.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grype's &lt;code&gt;vulnerability.db&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I plan on following up this post with an analysis of the data in Grype's &lt;code&gt;vulnerability.db&lt;/code&gt; database, but here are some quick notes on the structure of this SQLite3 file:&lt;/p&gt;

&lt;p&gt;When Grype runs, it checks against the last time the database was updated. If it's been longer than a day (Grype rebuilds the database daily) a new &lt;code&gt;vulnerability.db&lt;/code&gt; is downloaded to a cache. - On Linux, it's stored in &lt;code&gt;~/.cache/grype/db/5/vulnerability.db&lt;/code&gt;, where the numbered folder (&lt;code&gt;5&lt;/code&gt;) corresponds to the current Grype schema version number.) On Mac OS, it's stored in &lt;code&gt;~/Library/Caches/grype/db&lt;/code&gt; by default.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;vulnerability.db&lt;/code&gt; database has five tables, but only two have significant data. The &lt;code&gt;vulnerability_metadata&lt;/code&gt; table stores information on CVEs as they apply on a per-platform basis. The entities in the &lt;code&gt;vulnerability&lt;/code&gt; table represent vulnerabilities as they apply to specific package versions.&lt;/p&gt;

&lt;p&gt;The platforms with the most vulnerability metadata entries are Ubuntu, NVD (NIST's National Vulnerability Database,, and Susa. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcpz0a1l3xc7u91rqj9p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcpz0a1l3xc7u91rqj9p.png" alt="Chart showing the upstream providers, Ubuntu is the biggest, Chainguard is pretty new" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While these results are somewhat interesting when considering where Grype data comes from, the number here reflects many factors, mainly the date the provider started recording vulnerabilities and, for platform-specific providers, the attack surface of the platform. Other details such as duplication of distros lower the signal here and would require more analysis to parse out.&lt;/p&gt;

&lt;p&gt;We can also check the number of vulnerability metadata entries by year:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwus00tu84kwuwpdv09k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwus00tu84kwuwpdv09k.png" alt="Chart showing number of metadata entries ramping up from the late 90s andstabilizing around 2016-2017" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This chart mainly shows a movement toward maturity in the ecosystem, leading to some stability after 2016-2017. (The early twenty-teens were a time of rapid development in cloud technologies in particular.)&lt;/p&gt;

&lt;p&gt;Digging into the data in Grype's &lt;code&gt;vulnerability.db&lt;/code&gt; can also help to answer much more specific questions about how CVE affect different platforms. For example, imagine we host a mailserver and we wish to know the fixed status of &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-37383" rel="noopener noreferrer"&gt;CVE-2024-37383&lt;/a&gt;, a  known-exploited vulnerability which allows cross-site scripting in RoundCube, a webmail client. We can narrow the data in the vulnerability table to answer this question:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;fix_state&lt;/th&gt;
      &lt;th&gt;namespace&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;fixed&lt;/td&gt;
      &lt;td&gt;nvd:cpe&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;fixed&lt;/td&gt;
      &lt;td&gt;debian:distro:debian:11&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;fixed&lt;/td&gt;
      &lt;td&gt;debian:distro:debian:12&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;fixed&lt;/td&gt;
      &lt;td&gt;debian:distro:debian:13&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;fixed&lt;/td&gt;
      &lt;td&gt;debian:distro:debian:unstable&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;not-fixed&lt;/td&gt;
      &lt;td&gt;ubuntu:distro:ubuntu:20.04&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;not-fixed&lt;/td&gt;
      &lt;td&gt;ubuntu:distro:ubuntu:22.04&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;fixed&lt;/td&gt;
      &lt;td&gt;ubuntu:distro:ubuntu:23.10&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In a follow-up post, I'll show you how to load Grype's &lt;code&gt;vulnerability.db&lt;/code&gt; database into &lt;a href="https://pandas.pydata.org/docs/" rel="noopener noreferrer"&gt;Pandas&lt;/a&gt; to get a better sense of Grype's data schema and how it can be used to answer specific questions in platform security and broader CVE trends.&lt;/p&gt;

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

&lt;p&gt;One of the most remarkable aspects of the Grype image scanner is the openness of its data pipeline. This is great for transparency and makes Grype's &lt;code&gt;vulnerability.db&lt;/code&gt; into a more flexible and useful tool. If you've found this post useful, let us know and maybe you'll see more security deep dives like this. 🛡️🤿 You can follow Chainguard on &lt;a href="https://dev.to/t/chainguard"&gt;dev.to&lt;/a&gt; or &lt;a href="https://www.linkedin.com/company/chainguard-dev/posts/?feedView=all" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or &lt;a href="https://www.chainguard.dev/newsletter?utm_source=cg-devrel" rel="noopener noreferrer"&gt;sign up for our newsletter&lt;/a&gt; to keep in touch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://anchore.com/blog/build-your-own-grype-database/" rel="noopener noreferrer"&gt;Build Your Own Grype Database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.chainguard.dev/unchained/why-chainguard-uses-grype-as-its-first-line-of-defense-for-cves" rel="noopener noreferrer"&gt;Why Chainguard uses Grype as its first line of defense for CVEs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://anchore.com/software-supply-chain-security/open-source-container-vulnerability-scanning-tools" rel="noopener noreferrer"&gt;A Guide to Vulnerability Scanning with Open Source Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/anchore/grype-db" rel="noopener noreferrer"&gt;Utility: &lt;code&gt;grype-db&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/anchore/vunnel" rel="noopener noreferrer"&gt;Utility: &lt;code&gt;vunnel&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://anchorecommunity.discourse.group/t/does-grype-fetches-data-from-different-sources-or-only-from-nvd/136" rel="noopener noreferrer"&gt;Forum Post: Where Does Grype Data Come From?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>opensource</category>
      <category>security</category>
      <category>docker</category>
    </item>
    <item>
      <title>Securely Containerize a Python Application with Chainguard Images</title>
      <dc:creator>Patrick Smyth</dc:creator>
      <pubDate>Fri, 12 Apr 2024 19:09:57 +0000</pubDate>
      <link>https://forem.com/chainguard/securely-containerize-a-python-application-with-chainguard-images-bn8</link>
      <guid>https://forem.com/chainguard/securely-containerize-a-python-application-with-chainguard-images-bn8</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/2D0JULd4E5A"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Containerization technologies such as Docker have revolutionized the development to production pipeline, making it easier than ever to reproduce environments and set up and maintain infrastructure. Unfortunately, containers come with their own risks and overhead. While standard images can be convenient for development, putting large images into production increases the attack surface and can demand additional and unnecessary resources in deployment.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll build on a &lt;a href="https://www.chainguard.dev/chainguard-images?utm_source=cg-devrel"&gt;Chainguard Image&lt;/a&gt; to containerize a Python application in a multi-stage build process. By using a Chainguard Image, our containerized application benefits from a minimalist design that reduces attack surface and from a set of features focused on security and ease of development. In addition, the use of a multistage build process will allow us to develop our application with access to tools such as package managers while removing these potential vulnerabilities later in the build process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3q8ly36r21wbx75uhd8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3q8ly36r21wbx75uhd8.gif" alt="A GIF of a little octpus with the caption Hello, I am small image" width="512" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Application
&lt;/h2&gt;

&lt;p&gt;The Python application we will containerize in this tutorial, Chainguard Timeteller, will provide the user with the local time in ten randomly chosen international timezones. It also displays the time in UTC and in the user's local timezone. The application depends on &lt;a href="https://pypi.org/project/pytz/"&gt;pytz&lt;/a&gt;, allowing us to draw from the &lt;a href="https://en.wikipedia.org/wiki/Tz_database"&gt;Olson tz database&lt;/a&gt; in selecting our random timezones.&lt;/p&gt;

&lt;p&gt;Let's start by creating our main application script. &lt;br&gt;
 First, open your terminal and run the below line to create a new folder called &lt;code&gt;timeteller&lt;/code&gt; in your home directory:&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;mkdir&lt;/span&gt; ~/timeteller &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/timeteller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a new file called &lt;code&gt;main.py&lt;/code&gt; in your preferred text editor. We'll use the widely available &lt;a href="https://en.wikipedia.org/wiki/GNU_nano"&gt;Nano&lt;/a&gt; editor 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;nano main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the following Python code into &lt;code&gt;main.py&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tzname&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;common_timezones&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tz_aware_now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utc&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;Create a timezone-aware datetime for the current time and a provided timezone. Defaults to UTC.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tz&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;now&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pretty_print_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Given a datetime object, return a human-readable and nicely-formatted time string..&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Generate an ordinal suffix, i.e. "th" or "st" based on the day
&lt;/span&gt;    &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;
    &lt;span class="n"&gt;day_ordinal_suffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&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;th&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;st&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dayd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;th&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;date_pretty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%-I:%M %p on %B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;day_ordinal_suffix&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;date_pretty&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_timezone_message&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Create the message to display to a user. The message includes a greeting, the current time in UTC, the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s local timezone and time, and ten local times from randomly selected timezones.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;local_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tz_aware_now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tz_aware_now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;random_timezones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;common_timezones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;timezones_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pretty_print_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tz_aware_now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zone&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;zone&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;random_timezones&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;printable_timezones&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; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    &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;➤&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezones_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;zone&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;zone&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;timezones_map&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Welcome to Chainguard Timeteller! &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🌎 The current time in UTC is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;pretty_print_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tz_aware_now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❓ Your current timezone is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tzname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;⏰ Your local time is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;pretty_print_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="se"&gt;\n&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;Local times from ten randomly chosen timezones around the world:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;printable_timezones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;generate_timezone_message&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using Nano, you can save the file by pressing &lt;code&gt;Control-x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, and &lt;code&gt;Enter&lt;/code&gt; in sequence.&lt;/p&gt;

&lt;p&gt;In this script, we define functions to return the current time in a specific timezone, generate nicely-formatted lines for each region, and pull together a message to the user. When run directly, the script prints the generated message, including the time in ten randomly selected timezones, to the console. The code depends on a library, pytz, not in the standard library, and we'll have to install it during our build process.&lt;/p&gt;

&lt;p&gt;Because our code depends on &lt;code&gt;pytz&lt;/code&gt;, a package not in Python's standard library, we'll also need to specify our dependencies. Open a &lt;code&gt;requirements.txt&lt;/code&gt; file using your text editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the below into the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pytz==2024.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we specify the version of &lt;code&gt;pytz&lt;/code&gt; we want to use. Once you're done, save the file.&lt;/p&gt;

&lt;p&gt;Before we build our container, let's test that our script works. First, install our dependency using the pip package manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on your system, you may need to use the &lt;code&gt;pip3&lt;/code&gt; command instead of the &lt;code&gt;pip&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Once &lt;code&gt;pytz&lt;/code&gt; has installed, run the script with the below command:&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;python&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on your system, you may need to use the &lt;code&gt;python3&lt;/code&gt; command instead of the &lt;code&gt;python&lt;/code&gt; command. You should receive output similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Welcome to Chainguard Timeteller! 

🌎 The current time in UTC is 6:07 PM on February 18th.
❓ Your current timezone is EST.
⏰ Your local time is 1:07 PM on February 18th.

Local times from ten randomly chosen timezones around the world:

     ➤ Europe/Dublin 6:07 PM on February 18th
     ➤ Africa/Lagos 7:07 PM on February 18th
     ➤ America/Tortola 2:07 PM on February 18th
     [...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you see the above output with local times in ten randomly-selected timezones, you'll know the application is working. You're ready to containerize Timeteller using a base image from Chainguard Images.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Stage Build Using Chainguard Images
&lt;/h2&gt;

&lt;p&gt;Now that we have our application in place, we're ready to containerize it using a multi-stage build process. This build process works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We begin the build by pulling a development version of the &lt;code&gt;python-latest&lt;/code&gt; Chainguard Image as our base image.&lt;/li&gt;
&lt;li&gt;We copy the &lt;code&gt;requirements.txt&lt;/code&gt; file to the image, activate our virtual environment, and install our dependency using &lt;code&gt;pip&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We now pull the minimal runtime version of the &lt;code&gt;python-latest&lt;/code&gt; image. This image does not contain pip or an interactive shell.&lt;/li&gt;
&lt;li&gt;We copy our virtual environment (now with access to our dependency) from the dev image to the minimal runtime image.&lt;/li&gt;
&lt;li&gt;We activate our virtual environment on the minimal runtime image and run the application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create a file named &lt;code&gt;Dockerfile&lt;/code&gt; in your &lt;code&gt;timeteller&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the following build instructions to the Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;cgr.dev/chainguard/python:latest-dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; LANG=C.UTF-8&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PYTHONDONTWRITEBYTECODE=1&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PYTHONUNBUFFERED=1&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/timeteller/venv/bin:$PATH"&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /timeteller&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv /timeteller/venv
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; cgr.dev/chainguard/python:latest&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; TZ="America/Chicago"&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /timeteller&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PYTHONUNBUFFERED=1&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/venv/bin:$PATH"&lt;/span&gt;


&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; main.py ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /timeteller/venv /venv&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; [ "python", "/timeteller/main.py" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the timezone in the line &lt;code&gt;ENV TZ="America/Chicago"&lt;/code&gt;to your own local timezone. (Without providing this information, the detected timezone in the container would be UTC.)&lt;/p&gt;

&lt;p&gt;Save the file. We should now be ready to perform the build. 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;docker build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; timeteller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will build the image from the instructions in our Dockerfile and tag it with the name &lt;code&gt;timeteller&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the image build is successful, you're ready to run it with 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;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; timeteller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the output from our application as above, including the ten randomly selected local times. Congratulations! You've successfully containerized and run an application using Chainguard Images in a multi-stage build process.&lt;/p&gt;

&lt;p&gt;The final image based on python:latest does not contain either pip or interactive shells such as &lt;code&gt;sh&lt;/code&gt; or &lt;code&gt;bash&lt;/code&gt;. Since package managers and shells are common vectors for attackers, having neither as part of our runtime image decreases the attack surface of our application in production. The multi-stage build process allows us to use tools such as shells and package managers during development while allowing us to keep our production image securely minimal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages of Chainguard Images
&lt;/h2&gt;

&lt;p&gt;Chainguard Images provide a happy medium between superminimal images such as &lt;a href="https://hub.docker.com/_/scratch/"&gt;scratch&lt;/a&gt; and more complex distribution-based images such as Alpine or Debian. Chainguard Images aim specifically to reduce complexity, intentionally including only those software components necessary for runtime. Further, Chainguard Images are based on a distroless philosophy, meaning that they strip out additional software components traditionally associated with a distribution. Typically, a Chainguard Image contains only an application runtime, root certificates, a minimal file structure, and a small number of core system libraries.&lt;/p&gt;

&lt;p&gt;Each Chainguard Image comes with a comprehensive SBOM (Software Bill of Materials). This allows users of Chainguard Images to check against known vulnerabilities, adhere to the legal terms of software licenses, and ensure software integrity.&lt;/p&gt;

&lt;p&gt;Finally, the focus on minimal builds results in significantly fewer CVEs on your runtime images. Before we end this tutorial, let's scan for CVEs in our Timeteller image using an industry-standard tool, &lt;a href="https://docs.docker.com/scout/"&gt;Docker Scout&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To use Docker Scout, you'll first have to have a &lt;a href="https://hub.docker.com"&gt;Docker Hub&lt;/a&gt; account. Follow the &lt;a href="https://github.com/docker/scout-cli?tab=readme-ov-file#cli-plugin-installation"&gt;installation instructions for Docker Scout on GitHub&lt;/a&gt;. Once Docker Scout is installed, you can sign in to Docker Hub on the command line with the &lt;code&gt;docker login&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Once we have Docker Scout installed, we can use it to scan for vulnerabilities with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker scout cves timeteller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running Docker Scout on our Timeteller image (built from a Chainguard Image) on February 18th, 2024 produced the following report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;    ✓ Image stored for indexing
    ✓ Indexed 32 packages
    ✓ No vulnerable package detected&lt;span class="sb"&gt;


&lt;/span&gt;&lt;span class="gu"&gt;## Overview&lt;/span&gt;&lt;span class="sb"&gt;

                    │       Analyzed Image         
&lt;/span&gt;────────────────────┼──────────────────────────────
  Target            │  timeteller:latest           
    digest          │  0cca410be7e4                
    platform        │ linux/amd64                  
    vulnerabilities │    0C     0H     0M     0L   
    size            │ 28 MB                        
    packages        │ 32                           &lt;span class="sb"&gt;


&lt;/span&gt;&lt;span class="gu"&gt;## Packages and Vulnerabilities&lt;/span&gt;

  No vulnerable packages detected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, no CVEs were detected in the Timeteller image at time of writing, a relatively rare outcome in the fast-paced world of container vulnerabilities and exposures. While your results with Chainguard Images won't always be this free of vulnerabilities, using Chainguard Images as your base will reduce your CVE incidence rate by 80% compared to comparable industry alternatives.&lt;/p&gt;

&lt;p&gt;In this tutorial, you containerized a Python application with Chainguard Images in a multi-stage build process. This resulted in a runtime image with only the software components required to run our application. This focus on reducing software complexity resulted in a runtime image with a demonstrably low number of CVEs—zero in this case. Now that you understand the advantages of building your production infrastructure on &lt;a href="https://www.chainguard.dev/chainguard-images?utm_source=cg-devrel"&gt;Chainguard Images&lt;/a&gt;, you're ready to go forth and secure your own production environment.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>security</category>
      <category>python</category>
      <category>wolfi</category>
    </item>
  </channel>
</rss>
