miércoles, abril 20, 2022

Docker: Lista de Materiales de Software o Software Bill Of Materials (SBOM)

Software Bill Of Materials (SBOM), es un inventario de los componentes de software y dependencias de un sistema, información de dichos componentes y la relación entre ellos. Este inventario se mantiene en un formato que es fácilmente legible por máquinas. Últimamente se han visto muchas vulnerabilidades relacionadas con la cadena de suministro de dependencias, una de las más sonadas el famoso log4shell entre otras. Cuando aparece una vulnerabilidad de este tipo, la pregunta es ¿Estamos afectado por dicha vulnerabilidad? ¿Alguno de nuestros sistemas usa la librería X? Y Docker nos puede ayudar.

Figura 1: Docker. Lista de Materiales de Software
o Software Bill Of Materials (SBOM)

La única forma de poder responder a este tipo de preguntas con cierta confianza es manteniendo el inventario, actualizado, de los componentes y dependencias que nuestros sistemas usan. Hay que tener en cuenta también que dicho inventario no sólo contenga los componentes y dependencias usadas directamente en nuestros sistemas, sino también las dependencias heredadas por dichos componentes y dependencias, también conocidas como dependencias transitivas.

Aunque SBOM no es algo nuevo, se puso muy de moda el año pasado cuando la Casa Blanca lanzó una orden ejecutiva en la requería que cualquier empresa que hiciera negocios con el gobierno norteamericano (venta de software, desarrollo o venta de licencias de uso) fuera capaz de proveer de esta lista de materiales con todos los componentes del software. De esta forma han sido muchas de las empresas que han incluido la posibilidad de la creación de SBOMs en sus herramientas, entre ellas Docker, dentro de su producto Docker Desktop

En la versión 4.7.0, justo después de la publicación de la segunda la edición de nuestro libro Docker: SecDevOps, Docker añadió un plugin por defecto que permite la generación de ficheros SBOM y de los que vamos a ver algunos ejemplos, que ya hemos visto un poco qué es SBOM y por qué es importante. 

Una cosa para recalcar, como se comentó antes, esto no es algo nuevo, y además tampoco es un estándar, por lo que existen varias iniciativas y formatos. Aunque no son los únicos que existen, los siguientes son los formatos que más fuerza o repercusión tienen actualmente:
Sin entrar en más detalles sobre dichos formatos, vamos a ver cómo se puede generar la lista de componentes y dependencias de una imagen de Docker.

Como comenté anteriormente, en la versión 4.7.0, Docker añadió una opción nueva (realmente un plugin) al comando *docker* llamada *sbom*. Este plugin usa por detrás un proyecto de Anchore llamado [syft](https://github.com/anchore/syft). Éste analiza las capas de la imagen y extrae la información sobre que comoponentes hay en cada capa. Si ejecutamos dicho comando obtendremos la siguiente salida:
    $ docker sbom

    Usage:  docker sbom [OPTIONS] COMMAND

    View the packaged-based Software Bill Of Materials (SBOM) for an image.

    EXPERIMENTAL: The flags and outputs of this command may change.
    Leave feedback on https://github.com/docker/sbom-cli-plugin.
    Examples:

    docker sbom alpine:latest                                    a summary of discovered packages
    docker sbom alpine:latest --format syft-json                 show all possible cataloging details
    docker sbom alpine:latest --output sbom.txt                  write report output to a file
    docker sbom alpine:latest --exclude /lib --exclude '**/*.db' ignore one or more paths/globs in the image

    Options:
    -D, --debug                 show debug logging
        --exclude stringArray   exclude paths from being scanned using a glob expression
        --format string         report output format, options=[syft-json cyclonedx-xml cyclonedx-json
                                github-0-json spdx-tag-value spdx-json table text] (default "table")
        --layers string         [experimental] selection of layers to catalog, options=[squashed all] (default
                                "squashed")
    -o, --output string         file to write the default report output to (default is STDOUT)
        --platform string       an optional platform specifier for container image sources (e.g. 'linux/arm64',
                                'linux/arm64/v8', 'arm64', 'linux')
        --quiet                 suppress all non-report output
    -v, --version               version for sbom

    Commands:
    version     Show Docker sbom version information

    Run 'docker sbom COMMAND --help' for more information on a command.
    an image argument is required
Dicho plugin permite la generación de ficheros SBOM en formato SPDX y CycloneDX. Veamos a continuación la salida por defecto que se produciría sobre una imagen Alpine:
    $ docker sbom alpine
    Syft v0.43.0
    ✔ Loaded image
    ✔ Parsed image
    ✔ Cataloged packages      [14 packages]

    NAME                    VERSION      TYPE
    alpine-baselayout       3.2.0-r16    apk
    alpine-keys             2.3-r1       apk
    apk-tools               2.12.7-r0    apk
    busybox                 1.33.1-r3    apk
    ca-certificates-bundle  20191127-r5  apk
    libc-utils              0.7.2-r3     apk
    libcrypto1.1            1.1.1l-r0    apk
    libretls                3.3.3p1-r2   apk
    libssl1.1               1.1.1l-r0    apk
    musl                    1.2.2-r3     apk
    musl-utils              1.2.2-r3     apk
    scanelf                 1.3.2-r0     apk
    ssl_client              1.33.1-r3    apk
    zlib                    1.2.11-r3    apk
En este caso se puede ver la lista de los componentes de la imagen *alpine:latest*. Ahora veamos el mismo ejemplo, pero en formato SPDX:
    $ docker sbom --format spdx alpine
    Syft v0.43.0
    ✔ Loaded image
    ✔ Parsed image
    ✔ Cataloged packages      [14 packages]

    SPDXVersion: SPDX-2.2
    DataLicense: CC0-1.0
    SPDXID: SPDXRef-DOCUMENT
    DocumentName: alpine-latest
    DocumentNamespace: https://anchore.com/syft/image/alpine-latest-b478dc4d-bced-406a-adf9-6f7b7ea6d231
    LicenseListVersion: 3.16
    Creator: Organization: Anchore, Inc
    Creator: Tool: syft-[not provided]
    Created: 2022-04-15T20:19:57Z

    ##### Package: alpine-baselayout

    PackageName: alpine-baselayout
    SPDXID: SPDXRef-Package-apk-alpine-baselayout-ed18f2a986e77aab
    PackageVersion: 3.2.0-r16
    PackageDownloadLocation: NOASSERTION
    FilesAnalyzed: false
    PackageLicenseConcluded: GPL-2.0-only
    PackageLicenseDeclared: GPL-2.0-only
    PackageCopyrightText: NOASSERTION
    ExternalRef: SECURITY cpe23Type cpe:2.3:a:alpine-baselayout:alpine-baselayout:3.2.0-r16:*:*:*:*:*:*:*
    ExternalRef: SECURITY cpe23Type cpe:2.3:a:alpine-baselayout:alpine_baselayout:3.2.0-r16:*:*:*:*:*:*:*
    ExternalRef: SECURITY cpe23Type cpe:2.3:a:alpine_baselayout:alpine-baselayout:3.2.0-r16:*:*:*:*:*:*:*
    ExternalRef: SECURITY cpe23Type cpe:2.3:a:alpine_baselayout:alpine_baselayout:3.2.0-r16:*:*:*:*:*:*:*
    ExternalRef: SECURITY cpe23Type cpe:2.3:a:alpine:alpine-baselayout:3.2.0-r16:*:*:*:*:*:*:*
    ExternalRef: SECURITY cpe23Type cpe:2.3:a:alpine:alpine_baselayout:3.2.0-r16:*:*:*:*:*:*:*
    ExternalRef: PACKAGE_MANAGER purl pkg:alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2

    ##### Package: alpine-keys
    ...
La salida es mucho más larga, pero a modo de ejemplo se puede ver uno de los componentes encontrados, con nombre, licencia, referencias externas, etcétera. También es posible generar la salida en formato *json*:
    $ docker sbom --format spdx-json alpine
    Syft v0.43.0
    ✔ Loaded image
    ✔ Parsed image
    ✔ Cataloged packages      [14 packages]

    {
        "SPDXID": "SPDXRef-DOCUMENT",
        "name": "alpine-latest",
        "spdxVersion": "SPDX-2.2",
        "creationInfo": {
        "created": "2022-04-15T21:15:23.057754Z",
        "creators": [
        "Organization: Anchore, Inc",
        "Tool: syft-[not provided]"
    ],
        "licenseListVersion": "3.16"
    },
        "dataLicense": "CC0-1.0",
        "documentNamespace": "https://anchore.com/syft/image/alpine-latest-2ea1fa9e-099e-4939-bdd8-d41fab29f1b0",
        "packages": [
        {
        "SPDXID": "SPDXRef-ed18f2a986e77aab",
    ...
Para ver la salida en formato CycloneDX:
    $ docker sbom --format cyclonedx alpine
    Syft v0.43.0
    ✔ Loaded image
    ✔ Parsed image
    ✔ Cataloged packages      [14 packages]

    [0000]  WARN unable to convert relationship from CycloneDX 1.3 JSON, dropping:
    {From:Pkg(name="musl" version="1.2.2-r3" type="apk" id="304c2239a7d1d6b7")
     To:Location<RealPath="/lib/ld-musl-x86_64.so.1" 

    ...

    <?xml version="1.0" encoding="UTF-8"?>
    <bom xmlns="http://cyclonedx.org/schema/bom/1.4"
           serialNumber="urn:uuid:faaf4f40-e332-4f0e-8acc-3fadcae62d25" version="1">
    <metadata>
        <timestamp>2022-04-15T17:17:00-04:00</timestamp>
        <tools>
        <tool>
            <vendor>anchore</vendor>
            <name>syft</name>
            <version>[not provided]</version>
        </tool>
        </tools>
        <component bom-ref="e33e1cadf7cfbd1e" type="container">
        <name>alpine:latest</name>
        <version>sha256:a2f19fdc3f78ddc201c5bb37e302fed8b3487695056215b5577113d3938196bf</version>
        </component>
    </metadata>
    <components>
        <component type="library">
        <publisher>Natanael Copa <ncopa@alpinelinux.org></publisher>
        <name>alpine-baselayout</name>
        <version>3.2.0-r16</version>
        <description>Alpine base dir structure and init scripts</description>
        <licenses>
            <license>
            <id>GPL-2.0-only</id>
            </license>
        </licenses>
        <cpe>cpe:2.3:a:alpine-baselayout:alpine-baselayout:3.2.0-r16:*:*:*:*:*:*:*</<pe>
        <purl>pkg:alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2</purl>
        <externalReferences>
            <reference type="distribution">
            <url>https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout</url>
            </reference>
        </externalReferences>
        <properties>
            <property name="syft:package:foundBy">apkdb-cataloger</property>
            <property name="syft:package:metadataType">ApkMetadata</property>
            <property name="syft:package:type">apk</property>
            <property name="syft:cpe23">cpe:2.3:a:alpine-baselayout:alpine_baselayout:3.2.0-r16:*:*:*:*:*:*:*</property>
            <property name="syft:cpe23">cpe:2.3:a:alpine_baselayout:alpine-baselayout:3.2.0-r16:*:*:*:*:*:*:*</property>
            <property name="syft:cpe23">cpe:2.3:a:alpine_baselayout:alpine_baselayout:3.2.0-r16:*:*:*:*:*:*:*</property>
            <property name="syft:cpe23">cpe:2.3:a:alpine:alpine-baselayout:3.2.0-r16:*:*:*:*:*:*:*</property>
            <property name="syft:cpe23">cpe:2.3:a:alpine:alpine_baselayout:3.2.0-r16:*:*:*:*:*:*:*</property>
            <property name="syft:location:0:layerID">sha256:e2eb06d8af8218cfec8210147357a68b7e13f7c485b991c288c2d01dc228bb68</property>
            <property name="syft:location:0:path">/lib/apk/db/installed</property>
            <property name="syft:metadata:gitCommitOfApkPort">8a8c96a0ea2fcd824c361aa4438763fa33ee8ca0</property>
            <property name="syft:metadata:installedSize">413696</property>
            <property name="syft:metadata:originPackage">alpine-baselayout</property>
            <property name="syft:metadata:pullChecksum">Q1UJtB9cNV4r+/VbxySkEei++qbho=</property>
            <property name="syft:metadata:pullDependencies">/bin/sh so:libc.musl-x86_64.so.1</property>
            <property name="syft:metadata:size">20716</property>
        </properties>
        </component>
        ...
    ...
Como se pueden ver los campos generados son distintos que SPDX y el formato, por defecto, es XML, pero al final la información que se recolecta es prácticamente la misma. También es posible la generación en formato *json*:
    $ docker sbom --format cyclonedx-json alpine
    Syft v0.43.0
    ✔ Loaded image
    ✔ Parsed image
    ✔ Cataloged packages      [14 packages]

    [0000]  WARN unable to convert relationship from CycloneDX 1.3 JSON, dropping: {From:Pkg(name="musl" version="1.2.2-r3" type="apk" id="304c2239a7d1d6b7") To:Location<RealPath="/lib/ld-musl-x86_6
    ...

    {
    "bomFormat": "CycloneDX",
    "specVersion": "1.4",
    "serialNumber": "urn:uuid:95cef6a2-edcb-4b82-91a4-3ed026f1670f",
    "version": 1,
    "metadata": {
        "timestamp": "2022-04-15T17:21:34-04:00",
        "tools": [
            {
                "vendor": "anchore",
                "name": "syft",
                "version": "[not provided]"
            }
            ],
                "component": {
            "bom-ref": "e33e1cadf7cfbd1e",
            "type": "container",
            "name": "alpine:latest",
            "version": "sha256:a2f19fdc3f78ddc201c5bb37e302fed8b3487695056215b5577113d3938196bf"
        }
    },
    "components": [
        {
            "type": "library",
            "publisher": "Natanael Copa \u003cncopa@alpinelinux.org\u003e",
            "name": "alpine-baselayout",
            "version": "3.2.0-r16",
            "description": "Alpine base dir structure and init scripts",
            "licenses": [
                {
                    "license": {
                        "id": "GPL-2.0-only"
                }
                }
        ],
            "cpe": "cpe:2.3:a:alpine-baselayout:alpine-baselayout:3.2.0-r16:*:*:*:*:*:*:*",
            "purl": "pkg:alpine/alpine-baselayout@3.2.0-r16?arch=x86_64\u0026upstream=alpine-baselayout\u0026distro=alpine-3.14.2",
            "externalReferences": [
                {
                    "url": "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
                    "type": "distribution"
            }
        ],
        "properties": [
            {
                "name": "syft:package:foundBy",
                "value": "apkdb-cataloger"
            },
            {
                "name": "syft:package:metadataType",
    ...
La lista de los formatos soportados actualmente son:
* syft-json
* cyclonedx-xml
* cyclonedx-json
* github-0-json 
* spdx-tag-value 
* spdx-json 
* table (formato por defecto)
* text
Si en vez mandar el contenido del fichero a la consola o salida estándar, queréis que se guarde en un fichero, se puede usar la opción -o o --output:
    $ docker sbom --format spdx-json -o alpine.json alpine
    Syft v0.43.0
    ✔ Loaded image
    ✔ Parsed image
    ✔ Cataloged packages      [14 packages]
La inclusión de este plugin en Docker está en modo experimental y puede que en futuras versiones éste cambie. Si tienes que generar SBOMs en entornos de producción, se podría usar la herramienta syft directamente. Esta herramienta es mucho más completa que lo que ofrece actualmente el plugin de Docker.

Happy Hacking!

No hay comentarios:

Publicar un comentario