<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Container on My personal blog</title>
        <link>https://www.matteobianchi.eu/tags/container/</link>
        <description>Recent content in Container on My personal blog</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <lastBuildDate>Tue, 30 Jun 2026 09:00:00 +0200</lastBuildDate><atom:link href="https://www.matteobianchi.eu/tags/container/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Sicurezza delle immagini container</title>
        <link>https://www.matteobianchi.eu/p/container-image-security/</link>
        <pubDate>Tue, 30 Jun 2026 09:00:00 +0200</pubDate>
        
        <guid>https://www.matteobianchi.eu/p/container-image-security/</guid>
        <description>&lt;img src="https://www.matteobianchi.eu/p/container-image-security/cover.png" alt="Featured image of post Sicurezza delle immagini container" /&gt;&lt;h2 id=&#34;perché-conta&#34;&gt;Perché conta
&lt;/h2&gt;&lt;p&gt;Gli artefatti verificabili dei capitoli precedenti, nel mondo cloud-native, sono quasi sempre
&lt;strong&gt;immagini container&lt;/strong&gt;. E un&amp;rsquo;immagine non è solo la tua app: è la tua app &lt;em&gt;più&lt;/em&gt; un sistema operativo,
librerie, utility. Ogni pacchetto incluso è codice che può avere vulnerabilità e che un attaccante può
sfruttare una volta dentro. La regola guida è semplice: &lt;strong&gt;meno c&amp;rsquo;è nell&amp;rsquo;immagine, meno c&amp;rsquo;è da
attaccare&lt;/strong&gt;. Questo poggia sul modello di &lt;a class=&#34;link&#34; href=&#34;https://www.matteobianchi.eu/p/container-networking/&#34; &gt;networking dei container&lt;/a&gt;
già visto; qui ci concentriamo sull&amp;rsquo;immagine come superficie.&lt;/p&gt;
&lt;h2 id=&#34;il-peso-dellimmagine-di-base&#34;&gt;Il peso dell&amp;rsquo;immagine di base
&lt;/h2&gt;&lt;pre class=&#34;mermaid&#34;&gt;
  flowchart TD
    A[&amp;#34;FROM ubuntu&amp;lt;br/&amp;gt;~400 pacchetti, shell,&amp;lt;br/&amp;gt;package manager&amp;#34;] --&amp;gt; A1[Grande superficie:&amp;lt;br/&amp;gt;molti CVE, strumenti&amp;lt;br/&amp;gt;utili all&amp;#39;attaccante]
    B[&amp;#34;FROM distroless/static&amp;lt;br/&amp;gt;solo libc + la tua app&amp;#34;] --&amp;gt; B1[Superficie minima:&amp;lt;br/&amp;gt;niente shell,&amp;lt;br/&amp;gt;pochi/zero CVE OS]
    style A1 fill:#fde2e4,stroke:#e63946
    style B1 fill:#e8f0fe,stroke:#4361ee
&lt;/pre&gt;

&lt;p&gt;Un&amp;rsquo;immagine basata su una distribuzione completa porta centinaia di pacchetti che l&amp;rsquo;app non usa — ma
che l&amp;rsquo;attaccante sì: una shell per muoversi, &lt;code&gt;curl&lt;/code&gt; per scaricare payload, un package manager per
installare strumenti. Un&amp;rsquo;immagine &lt;strong&gt;distroless&lt;/strong&gt; (o &lt;code&gt;scratch&lt;/code&gt; per i binari statici) contiene solo
l&amp;rsquo;app e lo stretto indispensabile: niente shell significa niente &lt;code&gt;kubectl exec&lt;/code&gt; in una shell, molto
meno con cui lavorare dopo una compromissione.&lt;/p&gt;
&lt;h2 id=&#34;il-multi-stage-build&#34;&gt;Il multi-stage build
&lt;/h2&gt;&lt;p&gt;Lo strumento che rende tutto questo pratico: compilare in un&amp;rsquo;immagine ricca, spedire in una minimale.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-dockerfile&#34; data-lang=&#34;dockerfile&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;# stage di build: ha compilatore, strumenti, dipendenze di sviluppo&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; golang:1.23 AS build&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;WORKDIR&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; /src&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;COPY&lt;/span&gt; . .&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;RUN&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;CGO_ENABLED&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt; go build -o /app ./cmd/server&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# stage finale: SOLO il binario, su base minimale, utente non-root&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; gcr.io/distroless/static:nonroot&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;COPY&lt;/span&gt; --from&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;build /app /app&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;USER&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; nonroot&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;ENTRYPOINT&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/app&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le righe evidenziate sono il punto: gli strumenti di build restano nello stage &lt;code&gt;build&lt;/code&gt; e non arrivano
in produzione; l&amp;rsquo;immagine finale ha solo il binario, gira come utente &lt;strong&gt;non-root&lt;/strong&gt;, e non ha né shell
né package manager da sfruttare.&lt;/p&gt;
&lt;h2 id=&#34;scansione-delle-vulnerabilità-dellimmagine&#34;&gt;Scansione delle vulnerabilità dell&amp;rsquo;immagine
&lt;/h2&gt;&lt;p&gt;Oltre a minimizzare, si scansiona. Uno scanner (Trivy, Grype) ispeziona i layer e trova i CVE nei
pacchetti OS e nelle dipendenze applicative — è la &lt;a class=&#34;link&#34; href=&#34;https://www.matteobianchi.eu/p/sca-e-dipendenze/&#34; &gt;SCA&lt;/a&gt;
applicata all&amp;rsquo;immagine assemblata, non solo al manifest. Si colloca:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;In pipeline&lt;/strong&gt;, come gate dopo la build: blocca il nuovo rischio critico.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nel registry&lt;/strong&gt;, in continuo: un CVE nuovo può colpire un&amp;rsquo;immagine già pubblicata, come per la SBOM.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;le-altre-regole-doro&#34;&gt;Le altre regole d&amp;rsquo;oro
&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;USER non-root        → mai processi come root nel container
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tag immutabili       → riferire per digest (sha256), non per :latest mutabile
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;niente segreti       → mai chiavi nei layer (restano nella history dell&amp;#39;immagine!)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;filesystem read-only → l&amp;#39;app non deve scrivere sulla propria immagine
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.dockerignore        → non copiare .git, .env, chiavi nel contesto di build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Nota il parallelo con i segreti: un segreto copiato in un layer resta nell&amp;rsquo;immagine &lt;em&gt;per sempre&lt;/em&gt;, come
nella history di Git. L&amp;rsquo;immagine va trattata con la stessa cautela del repository.&lt;/p&gt;


&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Domanda:&lt;/strong&gt; le immagini distroless senza shell non rendono un incubo il debug in produzione? Quando qualcosa va storto non posso nemmeno aprire una shell nel container.&lt;/summary&gt;
&lt;p&gt;È lo scambio reale, e la risposta è che la cosa che rende il debug scomodo per te è esattamente ciò che
rende la vita difficile all&#39;attaccante — ma oggi non devi più scegliere tra le due. Partiamo dal perché
l&#39;assenza di shell è un bene: la stragrande maggioranza delle tecniche post-exploitation presuppone di
avere, dentro il container, una shell per esplorare, `curl`/`wget` per scaricare lo stadio successivo, un
package manager per installare strumenti. Toglierli non ferma un attaccante determinato, ma alza
nettamente il costo e il rumore di ogni suo movimento, e neutralizza del tutto gli automatismi che
contano sulla presenza di `/bin/sh`. Rinunciarci per comodità di debug significa lasciare armi in casa.
Detto questo, il debug non è compromesso: i runtime moderni offrono i &lt;em&gt;debug container effimeri&lt;/em&gt;
(in Kubernetes &lt;code&gt;kubectl debug&lt;/code&gt; con &lt;code&gt;ephemeral containers&lt;/code&gt;), che attaccano un
container temporaneo pieno di strumenti al pod in esecuzione, condividendone namespace e filesystem, senza
che quegli strumenti vivano mai nell&#39;immagine di produzione. Hai la shell quando ti serve, dove ti serve,
e sparisce quando hai finito — l&#39;immagine resta minimale. In aggiunta, la disciplina distroless spinge
verso un debug migliore a monte: logging strutturato, metriche ed endpoint di health invece dell&#39;ispezione
manuale via shell, che è comunque il modo in cui si opera un sistema a scala. Quindi: sì, cambi abitudine,
ma non perdi capacità — sposti gli strumenti di debug fuori dall&#39;immagine e li porti dentro solo quando
servono, ottenendo insieme operabilità e una superficie d&#39;attacco minima.&lt;/p&gt;
&lt;/details&gt;


&lt;h2 id=&#34;conclusione&#34;&gt;Conclusione
&lt;/h2&gt;&lt;p&gt;Un&amp;rsquo;immagine sicura è piccola per principio: distroless o scratch via multi-stage build, utente
non-root, niente segreti nei layer, riferita per digest, scansionata in pipeline e nel registry. Meno
contiene, meno offre all&amp;rsquo;attaccante. Ma l&amp;rsquo;immagine è solo metà della storia: una volta in esecuzione
nel cluster, conta &lt;em&gt;come&lt;/em&gt; gira. L&amp;rsquo;hardening di Kubernetes, prossimo capitolo.&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
