|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +set -euo pipefail |
| 4 | + |
| 5 | +WORKSPACE="${PWD}" |
| 6 | +SYFT_COMMAND="syft" |
| 7 | + |
| 8 | +if [[ ! -t 0 || ! -t 1 || ! -t 2 ]]; then |
| 9 | + export NO_COLOR=1 |
| 10 | +fi |
| 11 | + |
| 12 | +if [[ "${NO_COLOR:-0}" =~ ^(n|no|0|f|false)$ ]]; then |
| 13 | + _BOLD="$(echo -ne '\e[1m')" |
| 14 | + _RED="$(echo -ne '\e[31m')" |
| 15 | + _BROWN="$(echo -ne '\e[33m')" |
| 16 | + _VIOLET="$(echo -ne '\e[35m')" |
| 17 | + _AZURE="$(echo -ne '\e[36m')" |
| 18 | + _YELLOW="$(echo -ne '\e[93m')" |
| 19 | + _RESET="$(echo -ne '\e[0m')" |
| 20 | + _Q1="${_VIOLET}" |
| 21 | + _Q0="${_RESET}" |
| 22 | +else |
| 23 | + _BOLD="" |
| 24 | + _RED="" |
| 25 | + _BROWN="" |
| 26 | + _VIOLET="" |
| 27 | + _AZURE="" |
| 28 | + _YELLOW="" |
| 29 | + _RESET="" |
| 30 | + _Q1="\`" |
| 31 | + _Q0="\`" |
| 32 | +fi |
| 33 | + |
| 34 | +function inform() { |
| 35 | + echo "$0: $*" |
| 36 | +} |
| 37 | + |
| 38 | +function warning() { |
| 39 | + echo "${_YELLOW}WARNING[$0]: $*${_RESET}" >&2 |
| 40 | +} |
| 41 | + |
| 42 | +function error() { |
| 43 | + echo "${_RED}ERROR[$0]: $*${_RESET}" >&2 |
| 44 | + exit 1 |
| 45 | +} |
| 46 | + |
| 47 | +function usage() { |
| 48 | + cat <<-__EOF__ |
| 49 | + Usage: ${_AZURE}$0 <FILE>${_RESET} |
| 50 | +
|
| 51 | + or as the container: |
| 52 | +
|
| 53 | + ${_BROWN}# Read a list of images from the <FILE>${_RESET} |
| 54 | + ${_AZURE}podman run --rm -v "\${PWD}":/gensbom:Z -e 'TPA_*' gensbom:latest <FILE>${_RESET} |
| 55 | +
|
| 56 | + In the current working directory: |
| 57 | +
|
| 58 | + 1. Read the list of images (one image per line) from ${_VIOLET}<FILE>${_RESET}. |
| 59 | + 2. For every image from the list: |
| 60 | + * generate an SBOM in CycloneDX 1.6 format using Syft |
| 61 | + * ingest the SBOM to the Trusted Profile Analyzer (TPA) |
| 62 | + 3. The ${_Q1}sboms${_Q0} directory and the ${_Q1}sboms.zip${_Q0} archive contain the |
| 63 | + generated SBOMs. |
| 64 | +
|
| 65 | + ${_YELLOW}WARNING:${_RESET} The ${_Q1}sboms${_Q0} directory and the ${_Q1}sboms.zip${_Q0} archive from the |
| 66 | + previous run will be removed! |
| 67 | +
|
| 68 | + ${_BOLD}Authentication${_RESET} |
| 69 | +
|
| 70 | + ${_VIOLET}config.json${_RESET} |
| 71 | + A file with the valid container registry credentials following the |
| 72 | + Docker format. This file must be in the current working directory |
| 73 | +
|
| 74 | + ${_VIOLET}TPA_AUTH_TOKEN${_RESET} |
| 75 | + Authorization token for TPA |
| 76 | +
|
| 77 | + ${_BOLD}Environment variables${_RESET} |
| 78 | +
|
| 79 | + ${_VIOLET}TPA_SERVICE_URL${_RESET} |
| 80 | + URL with running TPA instance |
| 81 | +
|
| 82 | + ${_VIOLET}TPA_AUTH_TOKEN${_RESET} |
| 83 | + Valid authorization token for TPA. Required if the TPA instance |
| 84 | + requires authorization |
| 85 | +
|
| 86 | + ${_BOLD}See also${_RESET} |
| 87 | +
|
| 88 | + ${_VIOLET}config.json${_RESET} format |
| 89 | + https://github.com/google/go-containerregistry/tree/main/pkg/authn#docker-config-auth |
| 90 | + https://github.com/anchore/syft/wiki/private-registry-authentication |
| 91 | +
|
| 92 | + __EOF__ |
| 93 | +} |
| 94 | + |
| 95 | +if [[ -z "${1:-}" ]]; then |
| 96 | + usage |
| 97 | + exit 0 |
| 98 | +fi |
| 99 | + |
| 100 | +_INPUT="$1" |
| 101 | + |
| 102 | +if [[ ! -f "${_INPUT}" ]]; then |
| 103 | + error "\`${_INPUT}\` is not a file" |
| 104 | +fi |
| 105 | + |
| 106 | +if [[ -z "${TPA_SERVICE_URL:-}" ]]; then |
| 107 | + error "TPA_SERVICE_URL environment variable is not set or it is empty" |
| 108 | +fi |
| 109 | + |
| 110 | +_DOCKER_CONFIG="${WORKSPACE}/config.json" |
| 111 | + |
| 112 | +if [[ ! -f "${_DOCKER_CONFIG}" ]]; then |
| 113 | + warning "Docker configuration with credentials (\`${_DOCKER_CONFIG}\`) not present" |
| 114 | + warning "Syft will not be able to access private container registries" |
| 115 | +else |
| 116 | + export DOCKER_CONFIG="${WORKSPACE}" |
| 117 | +fi |
| 118 | + |
| 119 | +_AUTH_HEADER="Authorization: ${TPA_AUTH_TOKEN:-}" |
| 120 | + |
| 121 | +if [[ -z "${TPA_AUTH_TOKEN:-}" ]]; then |
| 122 | + warning "TPA_AUTH_TOKEN environment variable is not set or it is empty" |
| 123 | + warning "Authorized communication with ${TPA_SERVICE_URL} will not be possible" |
| 124 | + _AUTH_HEADER="" |
| 125 | +fi |
| 126 | + |
| 127 | +_SBOMS_DIR="${WORKSPACE}/sboms" |
| 128 | +_SBOMS_ARCHIVE="sboms.zip" |
| 129 | + |
| 130 | +rm -rf "${_SBOMS_DIR}" "${WORKSPACE}/${_SBOMS_ARCHIVE}" |
| 131 | +mkdir -p "${_SBOMS_DIR}" |
| 132 | + |
| 133 | +_SBOM_COUNTER=0 |
| 134 | + |
| 135 | +while IFS="" read -r _IMAGE || [[ -n "${_IMAGE}" ]]; do |
| 136 | + if [[ -z "${_IMAGE}" ]]; then |
| 137 | + continue |
| 138 | + fi |
| 139 | + |
| 140 | + _SBOM="${_SBOMS_DIR}/$(printf 'sbom%010d' "${_SBOM_COUNTER}")-$(echo "${_IMAGE}" | tr '/:' '--').json" |
| 141 | + _SBOM_COUNTER=$(( _SBOM_COUNTER + 1 )) |
| 142 | + |
| 143 | + if ! "${SYFT_COMMAND}" -v scan "${_IMAGE}" -o "cyclonedx-json=${_SBOM}"; then |
| 144 | + # Remove empty/corrupted file so it will not be included in the final archive |
| 145 | + rm -f "${_SBOM}" |
| 146 | + continue |
| 147 | + fi |
| 148 | + |
| 149 | + if [[ ! -s "${_SBOM}" ]]; then |
| 150 | + warning "File \`${_SBOM}\` was not created" |
| 151 | + # Remove empty file so it will not be included in the final archive |
| 152 | + rm -f "${_SBOM}" |
| 153 | + continue |
| 154 | + fi |
| 155 | + |
| 156 | + _SHA512="sha512:$(sha512sum "${_SBOM}" | awk '{print $1}')" |
| 157 | + inform "File ${_Q1}${_SBOM}${_Q0} with ${_Q1}${_SHA512}${_Q0} has been created" |
| 158 | + |
| 159 | + if curl -sSfL \ |
| 160 | + ${_AUTH_HEADER:+-H "${_AUTH_HEADER}"} \ |
| 161 | + "${TPA_SERVICE_URL}/api/v2/sbom/${_SHA512}/download" \ |
| 162 | + >/dev/null 2>&1; \ |
| 163 | + then |
| 164 | + # Do not ingest already ingested SBOM |
| 165 | + inform "File ${_Q1}${_SBOM}${_Q0} is already ingested" |
| 166 | + continue |
| 167 | + fi |
| 168 | + |
| 169 | + if ! curl -sSfL \ |
| 170 | + -X POST \ |
| 171 | + ${_AUTH_HEADER:+-H "${_AUTH_HEADER}"} \ |
| 172 | + -H "Content-Type: application/json" \ |
| 173 | + -d "@${_SBOM}" \ |
| 174 | + "${TPA_SERVICE_URL}/api/v2/sbom"; \ |
| 175 | + then |
| 176 | + warning "Failed to ingest \`${_SBOM}\`" |
| 177 | + else |
| 178 | + # Put few newlines after the `curl` output |
| 179 | + echo -ne '\n\n' |
| 180 | + fi |
| 181 | +done < "${_INPUT}" |
| 182 | + |
| 183 | +pushd "${_SBOMS_DIR}" >/dev/null |
| 184 | + |
| 185 | +if find -name '*.json' -type f | grep -q .; then |
| 186 | + find -name '*.json' -type f -print | zip "${_SBOMS_ARCHIVE}" -@ |
| 187 | + mv "${_SBOMS_ARCHIVE}" "../${_SBOMS_ARCHIVE}" |
| 188 | +fi |
| 189 | + |
| 190 | +popd >/dev/null |
0 commit comments