From 59d7713b4a77de64fec5acdebfaf66823c09a21a Mon Sep 17 00:00:00 2001 From: Vadim Yalovets Date: Fri, 22 May 2026 15:52:36 +0300 Subject: [PATCH 1/4] PKG-1294 Remove perl-DBD-mysql dependency from xtrabackup packages --- storage/innobase/xtrabackup/utils/debian/control | 3 ++- storage/innobase/xtrabackup/utils/percona-xtrabackup.spec | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/storage/innobase/xtrabackup/utils/debian/control b/storage/innobase/xtrabackup/utils/debian/control index 29c87b215d4..a07a5e469e3 100644 --- a/storage/innobase/xtrabackup/utils/debian/control +++ b/storage/innobase/xtrabackup/utils/debian/control @@ -28,8 +28,9 @@ Homepage: http://www.percona.com/software/percona-xtrabackup Package: percona-xtrabackup-97 Architecture: any Depends: libcurl4-openssl-dev, rsync, zstd, lz4, ${misc:Depends}, ${shlibs:Depends} +Suggests: libdbd-mysql-perl Provides: xtrabackup -Conflicts: percona-xtrabackup-21, percona-xtrabackup-22, percona-xtrabackup, percona-xtrabackup-24, percona-xtrabackup-80, percona-xtrabackup-81, percona-xtrabackup-82, percona-xtrabackup-83 +Conflicts: percona-xtrabackup-21, percona-xtrabackup-22, percona-xtrabackup, percona-xtrabackup-24, percona-xtrabackup-80, percona-xtrabackup-81, percona-xtrabackup-82, percona-xtrabackup-83, percona-xtrabackup-84 Breaks: xtrabackup (<< 2.0.0~) Replaces: xtrabackup (<< 2.0.0~) Enhances: mysql-server diff --git a/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec b/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec index 92e18823fd4..071051d4881 100644 --- a/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec +++ b/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec @@ -36,7 +36,8 @@ BuildRequires: %{cmake_bin}, libaio-devel, libgcrypt-devel, ncurses-devel, read BuildRequires: libcurl-devel Conflicts: percona-xtrabackup-21, percona-xtrabackup-22, percona-xtrabackup, percona-xtrabackup-24, percona-xtrabackup-80, percona-xtrabackup-81, percona-xtrabackup-82, percona-xtrabackup-84 Requires: rsync, zstd -Requires: perl(Digest::MD5), lz4 +Requires: lz4 +Recommends: perl(Digest::MD5) BuildRoot: %{_tmppath}/%{name}-%{version}%{xb_version_extra}-root From 1d4025c5637e5a84345ea0cf67dc54991e211c82 Mon Sep 17 00:00:00 2001 From: EvgeniyPatlan Date: Fri, 7 Aug 2026 11:14:44 +0300 Subject: [PATCH 2/4] Merge pull request #1773 from percona/sbom-packaging feat: generate SBOMs for shipped packages --- .../innobase/xtrabackup/utils/build-binary.sh | 28 ++ .../innobase/xtrabackup/utils/debian/rules | 28 +- .../utils/percona-xtrabackup-8.0_builder.sh | 30 +- .../xtrabackup/utils/percona-xtrabackup.spec | 15 + .../xtrabackup/utils/sbom/check-components.sh | 94 +++++++ .../xtrabackup/utils/sbom/components.txt | 59 ++++ .../xtrabackup/utils/sbom/gen-sbom.sh | 264 ++++++++++++++++++ .../xtrabackup/utils/sbom/validate-sbom.sh | 97 +++++++ 8 files changed, 607 insertions(+), 8 deletions(-) create mode 100755 storage/innobase/xtrabackup/utils/sbom/check-components.sh create mode 100644 storage/innobase/xtrabackup/utils/sbom/components.txt create mode 100755 storage/innobase/xtrabackup/utils/sbom/gen-sbom.sh create mode 100755 storage/innobase/xtrabackup/utils/sbom/validate-sbom.sh diff --git a/storage/innobase/xtrabackup/utils/build-binary.sh b/storage/innobase/xtrabackup/utils/build-binary.sh index 9f3b44815bd..d8b2765c54b 100755 --- a/storage/innobase/xtrabackup/utils/build-binary.sh +++ b/storage/innobase/xtrabackup/utils/build-binary.sh @@ -234,14 +234,42 @@ mkdir "$INSTALLDIR" mkdir "$WORKDIR_ABS/$BASEINSTALLDIR/minimal" cp -r "$WORKDIR_ABS/$BASEINSTALLDIR/$PRODUCT_FULL" "$WORKDIR_ABS/$BASEINSTALLDIR/minimal/$PRODUCT_FULL-minimal" + SBOM_UTILS="${SOURCEDIR}/storage/innobase/xtrabackup/utils/sbom" + gen_tarball_sbom() { + local dest=$1 + local label=$2 + sh "${SBOM_UTILS}/gen-sbom.sh" \ + --pkg "percona-xtrabackup" \ + --version "$XTRABACKUP_VERSION" \ + --root "$SOURCEDIR" \ + --dest "${dest}/sbom" \ + --scan-libs "${dest}/lib/private" + mkdir -p "${WORKDIR_ABS}/sbom" + for _sf in "${dest}"/sbom/*; do + [ -f "$_sf" ] || continue + cp "$_sf" "${WORKDIR_ABS}/sbom/${label}.$(basename "$_sf")" + done + } + + if [ "${SBOM:-0}" = "1" ]; then + sh "${SBOM_UTILS}/check-components.sh" --root "$SOURCEDIR" + fi + # NORMAL TARBALL cd "$INSTALLDIR" link + if [ "${SBOM:-0}" = "1" ]; then + gen_tarball_sbom "$INSTALLDIR" "$PRODUCT_FULL" + fi cd "$WORKDIR_ABS/$BASEINSTALLDIR/minimal/$PRODUCT_FULL-minimal" rm -rf percona-xtrabackup-8.0-test 2> /dev/null find . -type f -exec file '{}' \; | grep ': ELF ' | cut -d':' -f1 | xargs strip --strip-unneeded link + if [ "${SBOM:-0}" = "1" ]; then + gen_tarball_sbom "$WORKDIR_ABS/$BASEINSTALLDIR/minimal/$PRODUCT_FULL-minimal" \ + "${PRODUCT_FULL}-minimal" + fi cd "$WORKDIR" $TAR czf "percona-xtrabackup-$XTRABACKUP_VERSION-$(uname -s)-$(uname -m)$GLIBC_VER.tar.gz" \ diff --git a/storage/innobase/xtrabackup/utils/debian/rules b/storage/innobase/xtrabackup/utils/debian/rules index c0ca7055980..e78cd566a8c 100755 --- a/storage/innobase/xtrabackup/utils/debian/rules +++ b/storage/innobase/xtrabackup/utils/debian/rules @@ -20,6 +20,11 @@ builddirdebug = debug rpath='$$ORIGIN/../lib/private' originrpath='$$ORIGIN' +DEB_SOURCE := $(shell dpkg-parsechangelog -S Source 2>/dev/null) +DEB_VERSION := $(shell dpkg-parsechangelog -S Version 2>/dev/null) +SBOM_DIR = debian/$(DEB_SOURCE)/usr/share/$(DEB_SOURCE)/sbom +SBOM_UTILS = storage/innobase/xtrabackup/utils/sbom + override_dh_auto_configure: ifeq "$(DEB_DUMMY)" "" @@ -62,12 +67,23 @@ override_dh_auto_install: override_dh_install: dh_install - mkdir -p debian/percona-xtrabackup-97/usr/lib/private/ - mkdir -p debian/percona-xtrabackup-97/usr/bin/ - cp ./$(builddir)/library_output_directory/libprotobuf-lite.so* debian/percona-xtrabackup-97/usr/lib/private/ - cp ./$(builddirdebug)/runtime_output_directory/xtrabackup debian/percona-xtrabackup-97/usr/bin/xtrabackup-debug - patchelf --debug --set-rpath $(originrpath) debian/percona-xtrabackup-97/usr/lib/private/libprotobuf-lite.so* - patchelf --debug --set-rpath $(rpath) debian/percona-xtrabackup-97/usr/bin/xtrabackup-debug + mkdir -p debian/percona-xtrabackup-96/usr/lib/private/ + mkdir -p debian/percona-xtrabackup-96/usr/bin/ + cp ./$(builddir)/library_output_directory/libprotobuf-lite.so* debian/percona-xtrabackup-96/usr/lib/private/ + cp ./$(builddirdebug)/runtime_output_directory/xtrabackup debian/percona-xtrabackup-96/usr/bin/xtrabackup-debug + patchelf --debug --set-rpath $(originrpath) debian/percona-xtrabackup-96/usr/lib/private/libprotobuf-lite.so* + patchelf --debug --set-rpath $(rpath) debian/percona-xtrabackup-96/usr/bin/xtrabackup-debug +ifneq (,$(filter pkg.pxb.sbom,$(DEB_BUILD_PROFILES))) + @test -n "$(DEB_SOURCE)" -a -n "$(DEB_VERSION)" || \ + { echo "SBOM: dpkg-parsechangelog produced no Source/Version" >&2; exit 1; } + @test -d debian/$(DEB_SOURCE) || \ + { echo "SBOM: debian/$(DEB_SOURCE) is not a binary package staging dir; \ + point SBOM_DIR at the main package" >&2; exit 1; } + sh $(SBOM_UTILS)/check-components.sh --root . + mkdir -p $(SBOM_DIR) + sh $(SBOM_UTILS)/gen-sbom.sh --pkg $(DEB_SOURCE) --version $(DEB_VERSION) \ + --root . --dest $(SBOM_DIR) +endif @echo "RULES.$@" override_dh_strip: diff --git a/storage/innobase/xtrabackup/utils/percona-xtrabackup-8.0_builder.sh b/storage/innobase/xtrabackup/utils/percona-xtrabackup-8.0_builder.sh index 189a0b989c0..82f1e09300a 100644 --- a/storage/innobase/xtrabackup/utils/percona-xtrabackup-8.0_builder.sh +++ b/storage/innobase/xtrabackup/utils/percona-xtrabackup-8.0_builder.sh @@ -20,6 +20,7 @@ Usage: $0 [OPTIONS] --repo Repo for build --rpm_release RPM version( default = 1) --deb_release DEB version( default = 1) + --sbom If it is 1 SBOMs are generated and embedded( default = 0) --help) usage ;; Example $0 --builddir=/tmp/PXB --get_sources=1 --build_src_rpm=1 --build_rpm=1 EOF @@ -59,6 +60,7 @@ parse_arguments() { --repo=*) REPO="$val" ;; --rpm_release=*) RPM_RELEASE="$val" ;; --deb_release=*) DEB_RELEASE="$val" ;; + --sbom=*) SBOM="$val" ;; --help) usage ;; *) if test -n "$pick_args" @@ -553,7 +555,12 @@ build_rpm(){ enable_venv - rpmbuild --define "_topdir ${WORKDIR}/rpmbuild" --define "dist .${OS_NAME}" --rebuild rpmbuild/SRPMS/${SRCRPM} + SBOM_DEFINE=() + if [ ${SBOM} = 1 ]; then + SBOM_DEFINE=(--define "with_sbom 1") + fi + + rpmbuild --define "_topdir ${WORKDIR}/rpmbuild" --define "dist .${OS_NAME}" "${SBOM_DEFINE[@]}" --rebuild rpmbuild/SRPMS/${SRCRPM} return_code=$? if [ $return_code != 0 ]; then exit $return_code @@ -679,7 +686,12 @@ build_deb(){ rm -f call-home.sh cd ../ - dpkg-buildpackage -rfakeroot -uc -us -b + if [ ${SBOM} = 1 ]; then + DEB_BUILD_PROFILES="${DEB_BUILD_PROFILES:+${DEB_BUILD_PROFILES} }pkg.pxb.sbom" \ + dpkg-buildpackage -rfakeroot -uc -us -b + else + dpkg-buildpackage -rfakeroot -uc -us -b + fi cd ${WORKDIR} mkdir -p $CURDIR/deb @@ -717,6 +729,18 @@ build_tarball(){ mkdir -p ${CURDIR}/tarball cp $WORKDIR/TARGET/*.tar.gz ${WORKDIR}/tarball/ cp $WORKDIR/TARGET/*.tar.gz ${CURDIR}/tarball/ + + if [ ${SBOM} = 1 ]; then + mkdir -p ${WORKDIR}/sbom + mkdir -p ${CURDIR}/sbom + if [ -n "$(ls -A ${WORKDIR}/TARGET/sbom 2>/dev/null)" ]; then + cp ${WORKDIR}/TARGET/sbom/* ${WORKDIR}/sbom/ + cp ${WORKDIR}/TARGET/sbom/* ${CURDIR}/sbom/ + else + echo "ERROR: --sbom=1 but build-binary.sh produced no SBOMs" + exit 1 + fi + fi } CURDIR=$(pwd) @@ -739,7 +763,9 @@ RPM_RELEASE=1 DEB_RELEASE=1 REPO="https://github.com/percona/percona-xtrabackup.git" CMAKE_BIN="cmake" +SBOM=0 parse_arguments PICK-ARGS-FROM-ARGV "$@" +export SBOM check_workdir get_system diff --git a/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec b/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec index 071051d4881..a37d4d7988b 100644 --- a/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec +++ b/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec @@ -119,6 +119,16 @@ rm -rf $RPM_BUILD_ROOT/%{_mandir}/man1/l* rm -rf $RPM_BUILD_ROOT/%{_mandir}/man1/p* rm -rf $RPM_BUILD_ROOT/%{_mandir}/man1/z* +%if 0%{?with_sbom} +sh storage/innobase/xtrabackup/utils/sbom/check-components.sh --root . +install -d -m 755 $RPM_BUILD_ROOT%{_datadir}/%{name}/sbom +sh storage/innobase/xtrabackup/utils/sbom/gen-sbom.sh \ + --pkg %{name} \ + --version %{version}%{xb_version_extra} \ + --root . \ + --dest $RPM_BUILD_ROOT%{_datadir}/%{name}/sbom +%endif + %post tfn=$(/usr/bin/mktemp -p "$(/usr/bin/mktemp -d /tmp/XXXXXXXX)" call-home.XXXXXX.sh) cp %SOURCE999 /tmp/ 2>/dev/null || @@ -145,6 +155,7 @@ rm -rf $RPM_BUILD_ROOT %{_libdir}/xtrabackup/plugin/component_keyring_vault.so %{_libdir}/xtrabackup/plugin/component_keyring_file.so %{_libdir}/xtrabackup/plugin/component_keyring_kms.so +%{_libdir}/xtrabackup/plugin/component_telemetry.so %{_includedir}/kmip.h %{_includedir}/kmippp.h /usr/lib/libkmip.a @@ -152,6 +163,10 @@ rm -rf $RPM_BUILD_ROOT /usr/lib/libkmipclient.a /usr/lib/libkmipcore.a %{_libdir}/xtrabackup/plugin/component_keyring_kmip.so +%if 0%{?with_sbom} +%dir %{_datadir}/%{name} +%{_datadir}/%{name}/sbom +%endif %doc LICENSE %doc %{_mandir}/man1/*.1.gz diff --git a/storage/innobase/xtrabackup/utils/sbom/check-components.sh b/storage/innobase/xtrabackup/utils/sbom/check-components.sh new file mode 100755 index 00000000000..1385743da70 --- /dev/null +++ b/storage/innobase/xtrabackup/utils/sbom/check-components.sh @@ -0,0 +1,94 @@ +#!/bin/sh + +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT="." +REGISTRY="${SCRIPT_DIR}/components.txt" + +KNOWN_UNCERTAIN="" + +while [ $# -gt 0 ]; do + case "$1" in + --root) ROOT=${2:?}; shift 2 ;; + --registry) REGISTRY=${2:?}; shift 2 ;; + *) echo "check-components: unknown argument: $1" >&2; exit 2 ;; + esac +done + +[ -f "$REGISTRY" ] || { echo "check-components: registry not found: $REGISTRY" >&2; exit 2; } +[ -d "$ROOT/extra" ] || { echo "check-components: no extra/ under root: $ROOT" >&2; exit 2; } + +ERRORS=0 +err() { echo "check-components: ERROR: $*" >&2; ERRORS=$((ERRORS + 1)); } +warn() { echo "check-components: WARNING: $*" >&2; } + +ROWS=$(mktemp "${TMPDIR:-/tmp}/pxb-reg.XXXXXX") +trap 'rm -f "$ROWS"' EXIT + +awk -F'|' ' + { sub(/#.*/, "") } + /^[[:space:]]*$/ { next } + { + for (i = 1; i <= NF; i++) gsub(/^[ \t]+|[ \t]+$/, "", $i) + if ($1 == "") next + print $1 "|" $2 "|" $4 "|" $6 + } +' "$REGISTRY" > "$ROWS" + +norm() { printf '%s' "$1" | tr 'A-Z' 'a-z' | tr -cd 'a-z0-9'; } + +while IFS='|' read -r name version ships path; do + [ -n "$name" ] || continue + + case " $KNOWN_UNCERTAIN " in + *" $name "*) [ "$ships" = "uncertain" ] && warn "$name: ships=uncertain (allowlisted; resolve and set yes/no)" ;; + *) [ "$ships" = "uncertain" ] && err "$name: ships=uncertain and not allowlisted" ;; + esac + + if [ "$ships" = "yes" ] && [ -z "$version" ]; then + err "$name: ships=yes with an empty version (use 'unknown' if genuinely unversioned)" + fi + + [ "$path" = "-" ] && continue + + if [ ! -e "$ROOT/$path" ]; then + if [ "$name" = "libkmip" ]; then + err "$name: $path missing — run 'git submodule update --init extra/libkmip'" + else + err "$name: path does not exist: $path" + fi + continue + fi + + [ "$version" = "unknown" ] && continue + + base=$(basename "$path") + case "$base" in + *[0-9]*) ;; + *) continue ;; + esac + + nv=$(norm "$version") + nb=$(norm "$base") + case "$nb" in + *"$nv"*) ;; + *) err "$name: registry version '$version' not found in tree path '$path'" ;; + esac +done < "$ROWS" + +for d in "$ROOT"/extra/*/; do + [ -d "$d" ] || continue + dname=$(basename "$d") + if ! awk -F'|' -v d="extra/${dname}" ' + $4 == d || index($4, d "/") == 1 { found = 1 } + END { exit found ? 0 : 1 }' "$ROWS"; then + err "extra/${dname} has no registry entry (add it, with ships=yes or ships=no)" + fi +done + +if [ "$ERRORS" -gt 0 ]; then + echo "check-components: FAILED with ${ERRORS} error(s)" >&2 + exit 1 +fi +echo "check-components: registry consistent with tree" diff --git a/storage/innobase/xtrabackup/utils/sbom/components.txt b/storage/innobase/xtrabackup/utils/sbom/components.txt new file mode 100644 index 00000000000..ed0ace68ed6 --- /dev/null +++ b/storage/innobase/xtrabackup/utils/sbom/components.txt @@ -0,0 +1,59 @@ +# Third-party component registry for Percona XtraBackup SBOM generation. +# +# Syft and comparable scanners detect none of these: they are vendored C/C++ +# source trees with no package manifest. This file is the authoritative +# inventory. check-components.sh validates it against the source tree on every +# build and fails on drift, so a dependency bump cannot silently desync it. +# +# Format (pipe-separated, leading/trailing space ignored, '#' starts a comment): +# +# name | version | license | ships | linkage | path | notes +# +# ships yes code is present in a shipped artifact +# no present in the source tree but not built or not shipped +# uncertain needs a decision; treated as an error by check-components.sh +# unless listed in the KNOWN_UNCERTAIN allowlist +# linkage shared-lib | static | header-only | data | source +# path tree path used for drift validation; '-' if not applicable +# license SPDX identifier or expression +# +# Only ships=yes rows are emitted into the SBOM. + +# --- shipped: bundled libraries installed as files --------------------------- +protobuf | 24.4 | BSD-3-Clause | yes | shared-lib | extra/protobuf/protobuf-24.4 | -DWITH_PROTOBUF=bundled; ships /usr/lib/private/libprotobuf* +abseil-cpp | 20250814.1 | Apache-2.0 | yes | shared-lib | extra/abseil/abseil-cpp-20250814.1 | dep of bundled protobuf; ships /usr/lib/private/libabsl_* +icu | 77.1 | Unicode-DFS-2016 | yes | shared-lib | extra/icu/icu-release-77-1 | ships libicu* and icudt77l data; no LICENSE file in tree +libkmip | 5eeea918e7213f32df466be58cf9a68347ae2df2 | Apache-2.0 OR BSD-3-Clause | yes | static | extra/libkmip | git submodule; version is the pinned commit; ships libkmip.a/libkmippp.a + headers + +# --- shipped: bundled libraries linked into the binaries --------------------- +zlib | 1.3.2 | Zlib | yes | static | extra/zlib/zlib-1.3.2 | -DWITH_ZLIB=bundled; via mysys into all 4 binaries +zstd | 1.5.7 | BSD-3-Clause | yes | static | extra/zstd/zstd-1.5.7 | -DWITH_ZSTD=bundled; library is BSD-3; COPYING GPL-2.0 covers CLI which is not built +lz4 | 1.10.0 | BSD-2-Clause | yes | static | extra/lz4/lz4-1.10.0 | only lib/ is compiled; programs/ GPL-2.0 not built +xxhash | 0.8.3 | BSD-2-Clause | yes | static | extra/xxhash/xxHash-0.8.3 | via sql_main; no LICENSE file in tree +xxhash-lz4 | 1.10.0 | BSD-2-Clause | yes | static | extra/lz4/lz4-1.10.0/lib/xxhash.c | second, distinct xxHash copy compiled into xbstream +libbacktrace | 793921876c981 | BSD-3-Clause | yes | static | extra/libbacktrace/sha793921876c981 | via mysys; version is the pinned commit; no LICENSE file in tree + +# --- shipped: header-only, compiled into the binaries ------------------------ +boost | 1.87.0 | BSL-1.0 | yes | header-only | extra/boost/boost_1_87_0 | DOWNLOAD_BOOST/WITH_BOOST are no-ops; boost.cmake always uses the bundled tree +rapidjson | unknown | MIT | yes | header-only | extra/rapidjson | no version marker upstream in tree +unordered_dense | 4.4.0 | MIT | yes | header-only | extra/unordered_dense/unordered_dense-4.4.0 | via sql_main + +# --- shipped: vendored inside xtrabackup itself ------------------------------ +quicklz | 1.5.0 | GPL-2.0-only | yes | source | storage/innobase/xtrabackup/src/quicklz | version from QLZ_VERSION_* macros; dual GPL-1/2/3-or-commercial, GPL-2.0-only asserted to match PXB +jsmn | unknown | MIT | yes | source | storage/innobase/xtrabackup/src/jsmn | no version marker upstream + +# --- not shipped ------------------------------------------------------------- +curl | 8.14.1 | curl | no | source | extra/curl/curl-8.14.1 | WITH_CURL forced to "system"; the SYSTEM libcurl is a runtime dep, this bundled tree is not built +googletest | 1.17.0 | BSD-3-Clause | no | source | extra/googletest/googletest-1.17.0 | unit tests gated off when WITH_XTRABACKUP=ON +gperftools | 2.15 | BSD-3-Clause | no | source | extra/gperftools/gperftools-2.15 | WITH_TCMALLOC defaults OFF +libcbor | 0.11.0 | MIT | no | source | extra/libcbor/libcbor-0.11.0 | WITH_FIDO forced to "none" +libfido2 | 1.15.0 | BSD-2-Clause | no | source | extra/libfido2/libfido2-1.15.0 | WITH_FIDO forced to "none" +libcno | 208939f540957a35b337dacdd5c5e34d51821bd2 | MIT | no | source | extra/libcno/libcno-208939f540957a35b337dacdd5c5e34d51821bd2 | built but only linked by mysql router; WITH_ROUTER OFF +libedit | 20240808-3.1 | BSD-3-Clause | no | source | extra/libedit/libedit-20240808-3.1 | built but only consumed by client/, which is never added as a subdirectory +tirpc | 1.3.5 | BSD-3-Clause | no | source | extra/tirpc/libtirpc-1.3.5 | bundled path requires RHEL8/9 + non-system SSL; only consumer is group_replication +doxygen-awesome | 2.2.0 | MIT | no | data | extra/doxygen-awesome/2.2.0 | documentation CSS only; no CMake reference + +# --- shipped: telemetry component (component_telemetry.so) ------------------- +opentelemetry-cpp | 1.23.0 | Apache-2.0 | yes | static | extra/opentelemetry-cpp/opentelemetry-cpp-1.23.0 | linked into component_telemetry.so +opentelemetry-proto | 1.7.0 | Apache-2.0 | yes | static | extra/opentelemetry-proto/opentelemetry-proto-1.7.0 | linked into component_telemetry.so +json | 3.12.0 | MIT | yes | header-only | extra/json/json-3.12.0 | nlohmann json; header-only in opentelemetry-cpp diff --git a/storage/innobase/xtrabackup/utils/sbom/gen-sbom.sh b/storage/innobase/xtrabackup/utils/sbom/gen-sbom.sh new file mode 100755 index 00000000000..fdabdf1ad7d --- /dev/null +++ b/storage/innobase/xtrabackup/utils/sbom/gen-sbom.sh @@ -0,0 +1,264 @@ +#!/bin/sh + +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) + +PKG="" +VER="" +DEST="" +REGISTRY="${SCRIPT_DIR}/components.txt" +ROOT="." +SCAN_LIBS="" + +usage() { + cat <<'EOF' +Usage: gen-sbom.sh --pkg NAME --version VER --dest DIR + [--registry FILE] [--root DIR] [--scan-libs DIR] +EOF + exit 1 +} + +die() { echo "gen-sbom: ERROR: $*" >&2; exit 1; } + +while [ $# -gt 0 ]; do + case "$1" in + --pkg) PKG=${2:?}; shift 2 ;; + --version) VER=${2:?}; shift 2 ;; + --dest) DEST=${2:?}; shift 2 ;; + --registry) REGISTRY=${2:?}; shift 2 ;; + --root) ROOT=${2:?}; shift 2 ;; + --scan-libs) SCAN_LIBS=${2:?}; shift 2 ;; + --help) usage ;; + *) die "unknown argument: $1" ;; + esac +done + +[ -n "$PKG" ] || die "--pkg is required" +[ -n "$VER" ] || die "--version is required" +[ -n "$DEST" ] || die "--dest is required" +[ -f "$REGISTRY" ] || die "registry not found: $REGISTRY" + +mkdir -p "$DEST" + +TS=$(date -u -d "@${SOURCE_DATE_EPOCH:-}" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \ + || date -u +%Y-%m-%dT%H:%M:%SZ) + +WORK=$(mktemp -d "${TMPDIR:-/tmp}/pxb-sbom.XXXXXX") +trap 'rm -rf "$WORK"' EXIT +COMPONENTS="${WORK}/components" +: > "$COMPONENTS" + +awk -F'|' ' + { sub(/#.*/, "") } + /^[[:space:]]*$/ { next } + { + for (i = 1; i <= NF; i++) { gsub(/^[ \t]+|[ \t]+$/, "", $i) } + if ($4 != "yes") next + if ($1 == "" || $2 == "" || $3 == "") { + printf "gen-sbom: ERROR: ships=yes row missing name/version/license: [%s]\n", $0 > "/dev/stderr" + bad = 1 + next + } + print $1 "|" $2 "|" $3 "|" $5 "|vendored" + } + END { if (bad) exit 1 } +' "$REGISTRY" >> "$COMPONENTS" || die "registry contains malformed ships=yes rows: $REGISTRY" + +[ -s "$COMPONENTS" ] || die "registry produced no shipped components: $REGISTRY" + +resolve_owner() { + _p=$1 + if command -v rpm >/dev/null 2>&1; then + _r=$(rpm -qf --qf '%{NAME}|%{VERSION}-%{RELEASE}|%{LICENSE}\n' "$_p" 2>/dev/null | head -1) || _r="" + case $_r in + *'|'*'|'*) printf '%s' "$_r"; return 0 ;; + esac + fi + if command -v dpkg-query >/dev/null 2>&1; then + _pk=$(dpkg -S "$_p" 2>/dev/null \ + | grep -v '^diversion by ' \ + | head -1 | cut -d: -f1) || _pk="" + if [ -n "$_pk" ]; then + _v=$(dpkg-query -W -f='${Version}\n' "$_pk" 2>/dev/null | head -1) || _v="" + [ -n "$_v" ] || _v="unknown" + printf '%s|%s|NOASSERTION' "$_pk" "$_v" + return 0 + fi + fi + return 1 +} + +row_is_sane() { + [ "$(printf '%s' "$1" | awk -F'|' 'END { print NF }')" = "3" ] || return 1 + case $1 in + *' '*) return 1 ;; + esac + printf '%s' "$1" | awk -F'|' '{ exit ($1 == "" || $2 == "") ? 1 : 0 }' +} + +if [ -n "$SCAN_LIBS" ]; then + [ -d "$SCAN_LIBS" ] || die "--scan-libs directory not found: $SCAN_LIBS" + + HOSTMAP="${WORK}/hostmap" + ldconfig -p 2>/dev/null | awk '/=>/ { print $NF }' | while IFS= read -r _p; do + _rp=$(readlink -f "$_p" 2>/dev/null) || continue + [ -f "$_rp" ] || continue + printf '%s|%s\n' "$(basename "$_rp")" "$_rp" + done | sort -u > "$HOSTMAP" || true + + for f in "$SCAN_LIBS"/*; do + [ -L "$f" ] && continue + [ -f "$f" ] || continue + base=$(basename "$f") + origin=$(awk -F'|' -v b="$base" '$1 == b { print $2; exit }' "$HOSTMAP") || true + if [ -z "$origin" ]; then + origin=$(ldconfig -p 2>/dev/null \ + | awk -v b="$base" '$1 == b { print $NF; exit }') || true + [ -n "$origin" ] && origin=$(readlink -f "$origin" 2>/dev/null) || true + fi + if [ -z "$origin" ] || [ ! -e "$origin" ]; then + echo "gen-sbom: WARNING: no host origin for ${base}; recorded as unknown" >&2 + printf '%s|unknown|NOASSERTION|shared-lib|host\n' "$base" >> "$COMPONENTS" + continue + fi + if row=$(resolve_owner "$origin") && row_is_sane "$row"; then + printf '%s|shared-lib|host\n' "$row" >> "$COMPONENTS" + else + echo "gen-sbom: WARNING: ${base} owned by no host package (or the" \ + "package database returned an unusable record); recorded as unknown" >&2 + printf '%s|unknown|NOASSERTION|shared-lib|host\n' "$base" >> "$COMPONENTS" + fi + done +fi + +tr '\t' ' ' < "$COMPONENTS" | tr -d '\000-\010\013\014\016-\037\177' > "${COMPONENTS}.clean" +mv "${COMPONENTS}.clean" "$COMPONENTS" + +sort -u -o "$COMPONENTS" "$COMPONENTS" +COUNT=$(wc -l < "$COMPONENTS" | tr -d ' ') + +DUPES=$(awk -F'|' '{ id = $1; gsub(/[^A-Za-z0-9.-]/, "-", id); print id }' "$COMPONENTS" \ + | sort | uniq -d) +[ -z "$DUPES" ] || die "SPDXID collision between components: $(echo "$DUPES" | tr '\n' ' ')" + +uuid_from_content() { + _seed="${PKG}|${VER}|$(cat "$COMPONENTS")" + _hex="" + if command -v sha256sum >/dev/null 2>&1; then + _hex=$(printf '%s' "$_seed" | sha256sum | cut -c1-32) + elif command -v shasum >/dev/null 2>&1; then + _hex=$(printf '%s' "$_seed" | shasum -a 256 | cut -c1-32) + fi + if [ -z "$_hex" ]; then + _hex=$(printf '%s' "$_seed" | awk '{ + h = 0 + for (i = 1; i <= length($0); i++) h = (h * 31 + index($0, substr($0, i, 1))) % 4294967296 + printf "%08x%08x%08x%08x", h, (h*7)%4294967296, (h*13)%4294967296, (h*17)%4294967296 + }') + fi + printf '%s-%s-%s-%s-%s' \ + "$(printf '%s' "$_hex" | cut -c1-8)" "$(printf '%s' "$_hex" | cut -c9-12)" \ + "$(printf '%s' "$_hex" | cut -c13-16)" "$(printf '%s' "$_hex" | cut -c17-20)" \ + "$(printf '%s' "$_hex" | cut -c21-32)" +} +UUID=$(uuid_from_content) + +emit() { + awk -F'|' -v pkg="$PKG" -v ver="$VER" -v ts="$TS" -v uuid="$UUID" \ + -v mode="$1" ' + function esc(s) { gsub(/\\/, "\\\\", s); gsub(/"/, "\\\"", s); return s } + function pct(s, i, c, o) { + o = "" + for (i = 1; i <= length(s); i++) { + c = substr(s, i, 1) + if (c ~ /[A-Za-z0-9._~:-]/) o = o c + else o = o sprintf("%%%02X", ORD[c]) + } + return o + } + function purl(n, v) { return "pkg:generic/" pct(n) (v == "unknown" ? "" : "@" pct(v)) } + function spdxid(n) { gsub(/[^A-Za-z0-9.-]/, "-", n); return n } + function is_expr(l) { return (l ~ / OR / || l ~ / AND /) } + BEGIN { for (i = 0; i < 256; i++) ORD[sprintf("%c", i)] = i } + { + n[NR] = $1; v[NR] = $2; l[NR] = $3; k[NR] = $4; o[NR] = $5 + } + END { + if (mode == "spdx") { + print "{" + print " \"spdxVersion\": \"SPDX-2.3\"," + print " \"dataLicense\": \"CC0-1.0\"," + print " \"SPDXID\": \"SPDXRef-DOCUMENT\"," + printf " \"name\": \"%s-%s\",\n", esc(pkg), esc(ver) + printf " \"documentNamespace\": \"https://percona.com/spdx/%s-%s-%s\",\n", esc(pkg), esc(ver), uuid + print " \"creationInfo\": {" + printf " \"created\": \"%s\",\n", ts + print " \"creators\": [ \"Tool: pxb-gen-sbom\", \"Organization: Percona LLC\" ]" + print " }," + print " \"packages\": [" + printf " { \"SPDXID\": \"SPDXRef-Package-ROOT\", \"name\": \"%s\", \"versionInfo\": \"%s\",", esc(pkg), esc(ver) + print " \"downloadLocation\": \"NOASSERTION\", \"filesAnalyzed\": false," + print " \"licenseConcluded\": \"GPL-2.0-only\", \"licenseDeclared\": \"GPL-2.0-only\", \"copyrightText\": \"NOASSERTION\" }" + for (i = 1; i <= NR; i++) { + printf " ,{ \"SPDXID\": \"SPDXRef-Package-%s\", \"name\": \"%s\", \"versionInfo\": \"%s\",\n", spdxid(n[i]), esc(n[i]), esc(v[i]) + print " \"downloadLocation\": \"NOASSERTION\", \"filesAnalyzed\": false," + printf " \"licenseConcluded\": \"%s\", \"licenseDeclared\": \"%s\", \"copyrightText\": \"NOASSERTION\",\n", esc(l[i]), esc(l[i]) + printf " \"comment\": \"linkage=%s origin=%s\",\n", esc(k[i]), esc(o[i]) + printf " \"externalRefs\": [ { \"referenceCategory\": \"PACKAGE-MANAGER\", \"referenceType\": \"purl\", \"referenceLocator\": \"%s\" } ] }\n", esc(purl(n[i], v[i])) + } + print " ]," + print " \"relationships\": [" + print " { \"spdxElementId\": \"SPDXRef-DOCUMENT\", \"relatedSpdxElement\": \"SPDXRef-Package-ROOT\", \"relationshipType\": \"DESCRIBES\" }" + for (i = 1; i <= NR; i++) { + printf " ,{ \"spdxElementId\": \"SPDXRef-Package-ROOT\", \"relatedSpdxElement\": \"SPDXRef-Package-%s\", \"relationshipType\": \"CONTAINS\" }\n", spdxid(n[i]) + } + print " ]" + print "}" + } else if (mode == "cdx") { + print "{" + print " \"bomFormat\": \"CycloneDX\"," + print " \"specVersion\": \"1.5\"," + printf " \"serialNumber\": \"urn:uuid:%s\",\n", uuid + print " \"version\": 1," + print " \"metadata\": {" + printf " \"timestamp\": \"%s\",\n", ts + print " \"tools\": [ { \"vendor\": \"Percona\", \"name\": \"pxb-gen-sbom\", \"version\": \"1.0\" } ]," + printf " \"component\": { \"type\": \"application\", \"bom-ref\": \"%s\", \"name\": \"%s\", \"version\": \"%s\",", esc(purl(pkg, ver)), esc(pkg), esc(ver) + print " \"licenses\": [ { \"license\": { \"id\": \"GPL-2.0-only\" } } ] }" + print " }," + print " \"components\": [" + for (i = 1; i <= NR; i++) { + printf "%s", (i == 1 ? " " : " ,") + printf "{ \"type\": \"library\", \"bom-ref\": \"%s\", \"name\": \"%s\", \"version\": \"%s\",\n", esc(purl(n[i], v[i])), esc(n[i]), esc(v[i]) + printf " \"purl\": \"%s\",\n", esc(purl(n[i], v[i])) + if (l[i] == "NOASSERTION") + print " \"licenses\": []," + else if (is_expr(l[i])) + printf " \"licenses\": [ { \"expression\": \"%s\" } ],\n", esc(l[i]) + else + printf " \"licenses\": [ { \"license\": { \"id\": \"%s\" } } ],\n", esc(l[i]) + printf " \"properties\": [ { \"name\": \"pxb:linkage\", \"value\": \"%s\" }, { \"name\": \"pxb:origin\", \"value\": \"%s\" } ] }\n", esc(k[i]), esc(o[i]) + } + print " ]" + print "}" + } else if (mode == "table") { + printf "%-22s %-42s %-30s %-12s %s\n", "NAME", "VERSION", "LICENSE", "LINKAGE", "ORIGIN" + for (i = 1; i <= NR; i++) + printf "%-22s %-42s %-30s %-12s %s\n", n[i], v[i], l[i], k[i], o[i] + } else if (mode == "licenses") { + for (i = 1; i <= NR; i++) printf "%s %s %s\n", n[i], v[i], l[i] + } + }' "$COMPONENTS" +} + +emit spdx > "${DEST}/${PKG}.spdx.json" +emit cdx > "${DEST}/${PKG}.cdx.json" +emit table > "${DEST}/${PKG}.sbom.txt" +emit licenses > "${DEST}/${PKG}.licenses.txt" + +chmod 0755 "${DEST}" +chmod 0644 "${DEST}/${PKG}.spdx.json" "${DEST}/${PKG}.cdx.json" \ + "${DEST}/${PKG}.sbom.txt" "${DEST}/${PKG}.licenses.txt" + +echo "gen-sbom: wrote ${COUNT} components for ${PKG} ${VER} to ${DEST}" diff --git a/storage/innobase/xtrabackup/utils/sbom/validate-sbom.sh b/storage/innobase/xtrabackup/utils/sbom/validate-sbom.sh new file mode 100755 index 00000000000..c86c1161de0 --- /dev/null +++ b/storage/innobase/xtrabackup/utils/sbom/validate-sbom.sh @@ -0,0 +1,97 @@ +#!/bin/bash + +set -uo pipefail + +REQUIRED_COMPONENTS="protobuf zlib zstd lz4 icu abseil-cpp libkmip quicklz boost" +MIN_COMPONENTS=10 + +ERRORS=0 +err() { echo "validate-sbom: ERROR: $*" >&2; ERRORS=$((ERRORS + 1)); } +ok() { echo "validate-sbom: OK: $*"; } + +[ $# -gt 0 ] || { echo "usage: $0 ARTIFACT [ARTIFACT...]" >&2; exit 2; } + +TMP=$(mktemp -d "${TMPDIR:-/tmp}/pxb-vsbom.XXXXXX") +trap 'rm -rf "$TMP"' EXIT + +extract() { + local art=$1 dest=$2 + mkdir -p "$dest" + case "$art" in + *.rpm) (cd "$dest" && rpm2cpio "$art" | cpio -idmu --quiet) ;; + *.deb) dpkg-deb -x "$art" "$dest" ;; + *.tar.gz) tar xzf "$art" -C "$dest" ;; + *) [ -d "$art" ] && cp -r "$art/." "$dest/" ;; + esac +} + +check_json() { + local f=$1 + if command -v python3 >/dev/null 2>&1; then + python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$f" 2>/dev/null + return $? + fi + grep -q '^}' "$f" 2>/dev/null +} + +idx=0 +for art in "$@"; do + idx=$((idx + 1)) + [ -e "$art" ] || { err "artifact not found: $art"; continue; } + name=$(basename "$art") + dest="${TMP}/$(printf '%03d' "$idx")_$(echo "$name" | tr -c 'A-Za-z0-9._-' '_')" + rm -rf "$dest" + extract "$art" "$dest" || { err "$name: extraction failed"; continue; } + + mapfile -t sbom_dirs < <(find "$dest" -type d -name sbom 2>/dev/null) + if [ ${#sbom_dirs[@]} -eq 0 ]; then + err "$name: no sbom/ directory found" + continue + fi + + for d in "${sbom_dirs[@]}"; do + rel=${d#"$dest"} + errors_before=$ERRORS + + spdx=$(find "$d" -name '*.spdx.json' | head -1) + cdx=$(find "$d" -name '*.cdx.json' | head -1) + table=$(find "$d" -name '*.sbom.txt' | head -1) + lic=$(find "$d" -name '*.licenses.txt' | head -1) + + for pair in "spdx.json:$spdx" "cdx.json:$cdx" "sbom.txt:$table" "licenses.txt:$lic"; do + label=${pair%%:*}; path=${pair#*:} + [ -n "$path" ] || err "$name:$rel: missing ${label}" + done + [ -n "$spdx" ] && [ -n "$cdx" ] || continue + + check_json "$spdx" || err "$name:$rel: $(basename "$spdx") is not valid JSON" + check_json "$cdx" || err "$name:$rel: $(basename "$cdx") is not valid JSON" + + count=$(grep -o '"versionInfo"' "$spdx" 2>/dev/null | wc -l | tr -d ' ') + if [ "${count:-0}" -lt "$MIN_COMPONENTS" ]; then + err "$name:$rel: only ${count} components in SPDX (expected >= ${MIN_COMPONENTS})" + fi + + missing="" + for c in $REQUIRED_COMPONENTS; do + grep -q "\"name\": \"${c}\"" "$spdx" || missing="${missing} ${c}" + done + [ -n "$missing" ] && err "$name:$rel: SPDX missing expected components:${missing}" + + if [ -n "$lic" ]; then + total=$(grep -c . "$lic" 2>/dev/null || true) + noass=$(grep -c 'NOASSERTION' "$lic" 2>/dev/null || true) + if [ "${total:-0}" -gt 0 ] && [ "${noass:-0}" -eq "${total:-0}" ]; then + err "$name:$rel: every licence in $(basename "$lic") is NOASSERTION" + fi + fi + + [ $ERRORS -eq $errors_before ] && ok "$name:$rel: ${count} components, all checks passed" + done +done + +if [ $ERRORS -gt 0 ]; then + echo "validate-sbom: FAILED with ${ERRORS} error(s)" >&2 + exit 1 +fi +echo "validate-sbom: all artifacts passed" From fce761902d93421cffe5b3683dec74c8fb06546d Mon Sep 17 00:00:00 2001 From: Vadim Yalovets Date: Tue, 1 Sep 2026 13:48:49 +0300 Subject: [PATCH 3/4] Test sbom --- storage/innobase/xtrabackup/utils/percona-xtrabackup.spec | 1 - storage/innobase/xtrabackup/utils/sbom/components.txt | 4 ---- 2 files changed, 5 deletions(-) diff --git a/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec b/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec index a37d4d7988b..423fef6e49d 100644 --- a/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec +++ b/storage/innobase/xtrabackup/utils/percona-xtrabackup.spec @@ -155,7 +155,6 @@ rm -rf $RPM_BUILD_ROOT %{_libdir}/xtrabackup/plugin/component_keyring_vault.so %{_libdir}/xtrabackup/plugin/component_keyring_file.so %{_libdir}/xtrabackup/plugin/component_keyring_kms.so -%{_libdir}/xtrabackup/plugin/component_telemetry.so %{_includedir}/kmip.h %{_includedir}/kmippp.h /usr/lib/libkmip.a diff --git a/storage/innobase/xtrabackup/utils/sbom/components.txt b/storage/innobase/xtrabackup/utils/sbom/components.txt index ed0ace68ed6..25e7fb0e1d9 100644 --- a/storage/innobase/xtrabackup/utils/sbom/components.txt +++ b/storage/innobase/xtrabackup/utils/sbom/components.txt @@ -53,7 +53,3 @@ libedit | 20240808-3.1 | BSD-3-Clause | no | source tirpc | 1.3.5 | BSD-3-Clause | no | source | extra/tirpc/libtirpc-1.3.5 | bundled path requires RHEL8/9 + non-system SSL; only consumer is group_replication doxygen-awesome | 2.2.0 | MIT | no | data | extra/doxygen-awesome/2.2.0 | documentation CSS only; no CMake reference -# --- shipped: telemetry component (component_telemetry.so) ------------------- -opentelemetry-cpp | 1.23.0 | Apache-2.0 | yes | static | extra/opentelemetry-cpp/opentelemetry-cpp-1.23.0 | linked into component_telemetry.so -opentelemetry-proto | 1.7.0 | Apache-2.0 | yes | static | extra/opentelemetry-proto/opentelemetry-proto-1.7.0 | linked into component_telemetry.so -json | 3.12.0 | MIT | yes | header-only | extra/json/json-3.12.0 | nlohmann json; header-only in opentelemetry-cpp From 4f5effe51405bf27b68c640ce486f98e672135dd Mon Sep 17 00:00:00 2001 From: Vadim Yalovets Date: Tue, 1 Sep 2026 18:17:17 +0300 Subject: [PATCH 4/4] PXC-5223 Packaging tasks for release - PXC 9.7.1 --- storage/innobase/xtrabackup/utils/sbom/components.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/storage/innobase/xtrabackup/utils/sbom/components.txt b/storage/innobase/xtrabackup/utils/sbom/components.txt index 25e7fb0e1d9..ed0ace68ed6 100644 --- a/storage/innobase/xtrabackup/utils/sbom/components.txt +++ b/storage/innobase/xtrabackup/utils/sbom/components.txt @@ -53,3 +53,7 @@ libedit | 20240808-3.1 | BSD-3-Clause | no | source tirpc | 1.3.5 | BSD-3-Clause | no | source | extra/tirpc/libtirpc-1.3.5 | bundled path requires RHEL8/9 + non-system SSL; only consumer is group_replication doxygen-awesome | 2.2.0 | MIT | no | data | extra/doxygen-awesome/2.2.0 | documentation CSS only; no CMake reference +# --- shipped: telemetry component (component_telemetry.so) ------------------- +opentelemetry-cpp | 1.23.0 | Apache-2.0 | yes | static | extra/opentelemetry-cpp/opentelemetry-cpp-1.23.0 | linked into component_telemetry.so +opentelemetry-proto | 1.7.0 | Apache-2.0 | yes | static | extra/opentelemetry-proto/opentelemetry-proto-1.7.0 | linked into component_telemetry.so +json | 3.12.0 | MIT | yes | header-only | extra/json/json-3.12.0 | nlohmann json; header-only in opentelemetry-cpp