If your goal is to leverage Red Hat Universal Base Image micro-variants to package your software with dependencies available via Red Hat RPMs and you need to leverage Red Hat's OpenSSL to support FIPS-140, you can use the following approach to install the correct OpenSSL module and keep your RPM database accurate so your image gets graded correctly by vulnerability scanners (which depend on this database):
- Use a multi-stage
Dockerfile/Containerfile - In the first stage, build your software with any RPM-based build time dependencies using regular UBI or builder images
- In the second stage, copy your software artifacts into a UBI micro image using
COPY --from=... - In the third stage, to install any RPM-based runtime dependencies using a regular UBI image, copy the root filesystem of the second stage into a directory like
/mnt/rootfsand then install any needed RPMs usingdnf --installroot=/mnt/rootfs - In the last stage, copy all content from
/mnt/rootfsfrom the third stage into a UBI micro image usingCOPY --from=... /mnt/rootfs/ /
This way, you obtain the FIPS-140 certified crypto modules from Red Hat's OpenSSL implementation (or any other library containing FIPS-compliant ciphers, like nss), from the official source while maintaining the RPM database, which is also present in UBI micro (in /var/lib/rpm/rpmdb.sqlite).
This is important, since many static vulnerability analyzers like Trivy, Grype, Clair and others rely on the RPM database to indentify the source of installed software for correct matching and rating of vulnerabilities.
Below is a demonstration of the concept explained above that also purposefully installs an (>1yr) older OpenSSL version to later compare the vulnerability analysis of the resulting image with a more basic approach that does not maintain RPM database integrity.
# Multi-stage Dockerfile using dnf --installroot
# Stage 0: Create hello world script
FROM registry.access.redhat.com/ubi9/ubi-micro:latest AS base
RUN echo '#!/bin/bash' > /app.sh && \
echo 'OPENSSL_VERSION="unknown"' >> /app.sh && \
echo 'echo "Hello, World from UBI9 Micro!"' >> /app.sh && \
echo 'echo ""' >> /app.sh && \
echo 'echo "OpenSSL Library Version:"' >> /app.sh && \
echo 'if command -v openssl &> /dev/null 2>&1; then' >> /app.sh && \
echo ' openssl version' >> /app.sh && \
echo 'elif [ -n "$OPENSSL_VERSION" ] && [ "$OPENSSL_VERSION" != "unknown" ]; then' >> /app.sh && \
echo ' echo "$OPENSSL_VERSION"' >> /app.sh && \
echo 'else' >> /app.sh && \
echo ' # Try to identify library files' >> /app.sh && \
echo ' for lib in /usr/lib64/libssl.so.* /usr/lib64/libcrypto.so.*; do' >> /app.sh && \
echo ' if [ -f "$lib" ]; then' >> /app.sh && \
echo ' echo "Library: $(basename "$lib")"' >> /app.sh && \
echo ' fi' >> /app.sh && \
echo ' done | head -2' >> /app.sh && \
echo 'fi' >> /app.sh && \
chmod +x /app.sh
# Stage 1: Use an old UBI9 (9.4-1214.1729773476) to install vulnerable OpenSSL via RPM into the base filesystem using dnf --installroot
FROM registry.access.redhat.com/ubi9/ubi@sha256:ee0b908e958a1822afc57e5d386d1ea128eebe492cb2e01b6903ee19c133ea75 AS installer
# Install dnf tools
RUN dnf install -y dnf-utils && \
dnf clean all && \
rm -rf /var/cache/dnf
# Copy the base filesystem (UBI9 micro) from stage 0 to /rootfs
COPY --from=base / /mnt/rootfs/
# Use dnf --installroot to install OpenSSL RPMs into /mnt/rootfs
# This will maintain the RPM database in /mnt/rootfs/var/lib/rpm
RUN dnf --installroot=/mnt/rootfs \
--setopt=reposdir=/etc/yum.repos.d \
--setopt=cachedir=/var/cache/dnf \
install -y openssl openssl-libs && \
dnf clean all --installroot=/mnt/rootfs && \
rm -rf /var/cache/dnf
# Stage 2: Copy the complete filesystem from installer stage into UBI9 micro
FROM registry.access.redhat.com/ubi9/ubi-micro:latest AS final
# Copy the entire rootfs from the installer stage
# This includes the RPM database with OpenSSL records at /mnt/rootfs/var/lib/rpm
COPY --from=installer /mnt/rootfs/ /
# Set the entrypoint to run the hello world application
ENTRYPOINT ["/app.sh"]If we scan the resulting image, we can see that the OpenSSL-related CVE-2025-9230 is correctly identified and their rating confirms with Red Hat's severity rating of "Medium" of this particular vulnerability:
Trivy (0.67.2) output:
quay.io/dmesser/ubi-micro-openssl:rpms (redhat 9.6)
Total: 14 (UNKNOWN: 0, LOW: 11, MEDIUM: 3, HIGH: 0, CRITICAL: 0)
┌──────────────────┬────────────────┬──────────┬──────────────┬─────────────────────────┬───────────────┬──────────────────────────────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
├──────────────────┼────────────────┼──────────┼──────────────┼─────────────────────────┼───────────────┼──────────────────────────────────────────────────────────────┤
│ coreutils-single │ CVE-2025-5278 │ MEDIUM │ affected │ 8.32-39.el9 │ │ coreutils: Heap Buffer Under-Read in GNU Coreutils sort via │
│ │ │ │ │ │ │ Key Specification │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-5278 │
├──────────────────┼────────────────┼──────────┤ ├─────────────────────────┼───────────────┼──────────────────────────────────────────────────────────────┤
│ libgcc │ CVE-2022-27943 │ LOW │ │ 11.5.0-5.el9_5 │ │ binutils: libiberty/rust-demangle.c in GNU GCC 11.2 allows │
│ │ │ │ │ │ │ stack exhaustion in demangle_const │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-27943 │
├──────────────────┼────────────────┤ │ ├─────────────────────────┼───────────────┼──────────────────────────────────────────────────────────────┤
│ ncurses-base │ CVE-2023-50495 │ │ │ 6.2-10.20210508.el9_6.2 │ │ ncurses: segmentation fault via _nc_wrap_entry() │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-50495 │
├──────────────────┤ │ │ │ ├───────────────┤ │
│ ncurses-libs │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
├──────────────────┼────────────────┼──────────┤ ├─────────────────────────┼───────────────┼──────────────────────────────────────────────────────────────┤
│ openssl │ CVE-2025-9230 │ MEDIUM │ │ 1:3.2.2-6.el9_5.1 │ │ openssl: Out-of-bounds read & write in RFC 3211 KEK Unwrap │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-9230 │
│ ├────────────────┼──────────┤ │ ├───────────────┼──────────────────────────────────────────────────────────────┤
│ │ CVE-2024-13176 │ LOW │ │ │ │ openssl: Timing side-channel in ECDSA signature computation │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-13176 │
│ ├────────────────┤ ├──────────────┤ ├───────────────┼──────────────────────────────────────────────────────────────┤
│ │ CVE-2024-41996 │ │ will_not_fix │ │ │ openssl: remote attackers (from the client side) to trigger │
│ │ │ │ │ │ │ unnecessarily expensive server-side... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-41996 │
│ ├────────────────┤ ├──────────────┤ ├───────────────┼──────────────────────────────────────────────────────────────┤
│ │ CVE-2025-9232 │ │ affected │ │ │ openssl: Out-of-bounds read in HTTP client no_proxy handling │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-9232 │
├──────────────────┼────────────────┼──────────┤ │ ├───────────────┼──────────────────────────────────────────────────────────────┤
│ openssl-libs │ CVE-2025-9230 │ MEDIUM │ │ │ │ openssl: Out-of-bounds read & write in RFC 3211 KEK Unwrap │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-9230 │
│ ├────────────────┼──────────┤ │ ├───────────────┼──────────────────────────────────────────────────────────────┤
│ │ CVE-2024-13176 │ LOW │ │ │ │ openssl: Timing side-channel in ECDSA signature computation │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-13176 │
│ ├────────────────┤ ├──────────────┤ ├───────────────┼──────────────────────────────────────────────────────────────┤
│ │ CVE-2024-41996 │ │ will_not_fix │ │ │ openssl: remote attackers (from the client side) to trigger │
│ │ │ │ │ │ │ unnecessarily expensive server-side... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-41996 │
│ ├────────────────┤ ├──────────────┤ ├───────────────┼──────────────────────────────────────────────────────────────┤
│ │ CVE-2025-9232 │ │ affected │ │ │ openssl: Out-of-bounds read in HTTP client no_proxy handling │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-9232 │
├──────────────────┼────────────────┤ │ ├─────────────────────────┼───────────────┼──────────────────────────────────────────────────────────────┤
│ pcre2 │ CVE-2022-41409 │ │ │ 10.40-6.el9 │ │ pcre2: negative repeat value in a pcre2test subject line │
│ │ │ │ │ │ │ leads to inifinite... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-41409 │
├──────────────────┤ │ │ │ ├───────────────┤ │
│ pcre2-syntax │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
└──────────────────┴────────────────┴──────────┴──────────────┴─────────────────────────┴───────────────┴──────────────────────────────────────────────────────────────┘
Grype (0.102.0) output:
NAME INSTALLED FIXED IN TYPE VULNERABILITY SEVERITY EPSS RISK
openssl 1:3.2.2-6.el9_5.1 (won't fix) rpm CVE-2024-41996 Low 0.4% (62nd) 0.2
openssl-libs 1:3.2.2-6.el9_5.1 (won't fix) rpm CVE-2024-41996 Low 0.4% (62nd) 0.2
openssl 1:3.2.2-6.el9_5.1 rpm CVE-2024-13176 Low 0.1% (30th) < 0.1
openssl-libs 1:3.2.2-6.el9_5.1 rpm CVE-2024-13176 Low 0.1% (30th) < 0.1
pcre2 10.40-6.el9 rpm CVE-2022-41409 Low < 0.1% (19th) < 0.1
pcre2-syntax 10.40-6.el9 rpm CVE-2022-41409 Low < 0.1% (19th) < 0.1
ncurses-base 6.2-10.20210508.el9_6.2 rpm CVE-2023-50495 Low < 0.1% (16th) < 0.1
ncurses-libs 6.2-10.20210508.el9_6.2 rpm CVE-2023-50495 Low < 0.1% (16th) < 0.1
libgcc 11.5.0-5.el9_5 rpm CVE-2022-27943 Low < 0.1% (15th) < 0.1
openssl 1:3.2.2-6.el9_5.1 rpm CVE-2025-9230 Medium < 0.1% (9th) < 0.1
openssl-libs 1:3.2.2-6.el9_5.1 rpm CVE-2025-9230 Medium < 0.1% (9th) < 0.1
openssl 1:3.2.2-6.el9_5.1 rpm CVE-2025-9232 Low < 0.1% (5th) < 0.1
openssl-libs 1:3.2.2-6.el9_5.1 rpm CVE-2025-9232 Low < 0.1% (5th) < 0.1
coreutils-single 8.32-39.el9 rpm CVE-2025-5278 Medium < 0.1% (2nd) < 0.1
Clair (on Quay.io, be sure to use the new UI) output: https://quay.io/repository/dmesser/ubi-micro-openssl/tag/rpms?tab=securityreport&digest=sha256:adbb25c54722f6a0208a59266c9dc6ad1d5be71cce99d6445c9ba941656cf431
Let's compare this with a more direct approach, that uses a simpler Dockerfile / Containerfile which simply copies the runtime dependencies into a final UBI9 micro build stage, leaving the RPM database untouched:
# Multi-stage Dockerfile to create a hello world container with vulnerable OpenSSL using COPY
# Build Stage: Use an old UBI9 (9.4-1214.1729773476) to install vulnerable OpenSSL via RPM
FROM registry.access.redhat.com/ubi9/ubi:9.4@sha256:ee0b908e958a1822afc57e5d386d1ea128eebe492cb2e01b6903ee19c133ea75 AS build
# Install OpenSSL and OpenSSL libraries with https://access.redhat.com/security/cve/cve-2025-9230
RUN dnf install -y openssl openssl-libs && \
dnf clean all && \
rm -rf /var/cache/dnf
RUN OPENSSL_VERSION=$(openssl version 2>/dev/null || echo "unknown") && \
echo '#!/bin/bash' > /app.sh && \
echo "OPENSSL_VERSION=\"$OPENSSL_VERSION\"" >> /app.sh && \
echo 'echo "Hello, World from UBI9 Micro!"' >> /app.sh && \
echo 'echo ""' >> /app.sh && \
echo 'echo "OpenSSL Library Version:"' >> /app.sh && \
echo 'if command -v openssl &> /dev/null 2>&1; then' >> /app.sh && \
echo ' openssl version' >> /app.sh && \
echo 'elif [ -n "$OPENSSL_VERSION" ] && [ "$OPENSSL_VERSION" != "unknown" ]; then' >> /app.sh && \
echo ' echo "$OPENSSL_VERSION"' >> /app.sh && \
echo 'else' >> /app.sh && \
echo ' # Try to identify library files' >> /app.sh && \
echo ' for lib in /usr/lib64/libssl.so.* /usr/lib64/libcrypto.so.*; do' >> /app.sh && \
echo ' if [ -f "$lib" ]; then' >> /app.sh && \
echo ' echo "Library: $(basename "$lib")"' >> /app.sh && \
echo ' fi' >> /app.sh && \
echo ' done | head -2' >> /app.sh && \
echo 'fi' >> /app.sh && \
chmod +x /app.sh
# Create symlinks to required shared libraries for openssl binary
# Use ldd to find all dependencies and create symlinks in a single directory
RUN mkdir -p /tmp/openssl-deps && \
ldd /usr/bin/openssl 2>/dev/null | \
awk '/=>/ {print $3}' | \
grep -v "^$" | \
grep -v "not found" | \
sort -u | \
while read lib ; do \
ln -v -t /tmp/openssl-deps/ -s "$lib" ; \
done
# Final Stage: Use UBI9 Micro
FROM registry.access.redhat.com/ubi9/ubi-micro:latest
# Create necessary directories
RUN mkdir -p /usr/bin /usr/lib64 /lib64
# Copy OpenSSL binary
COPY --from=build /usr/bin/openssl /usr/bin/openssl
# Copy OpenSSL libraries
COPY --from=build /usr/lib64/libssl.so.* /usr/lib64/
COPY --from=build /usr/lib64/libcrypto.so.* /usr/lib64/
# Copy all required shared library dependencies via symlinks
COPY --from=build /tmp/openssl-deps/* /lib64/
COPY --from=build /app.sh /app.sh
# Set the entrypoint to run the hello world application
ENTRYPOINT ["/app.sh"]If you scan this image, you will see that the OpenSSL libraries are either not correctly identified or not identified at all, leading to incorrect rating or false negatives.
Trivy (0.67.2) output:
quay.io/dmesser/ubi-micro-openssl:files (redhat 9.6)
Total: 6 (UNKNOWN: 0, LOW: 5, MEDIUM: 1, HIGH: 0, CRITICAL: 0)
┌──────────────────┬────────────────┬──────────┬──────────┬─────────────────────────┬───────────────┬─────────────────────────────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
├──────────────────┼────────────────┼──────────┼──────────┼─────────────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤
│ coreutils-single │ CVE-2025-5278 │ MEDIUM │ affected │ 8.32-39.el9 │ │ coreutils: Heap Buffer Under-Read in GNU Coreutils sort via │
│ │ │ │ │ │ │ Key Specification │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-5278 │
├──────────────────┼────────────────┼──────────┤ ├─────────────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤
│ libgcc │ CVE-2022-27943 │ LOW │ │ 11.5.0-5.el9_5 │ │ binutils: libiberty/rust-demangle.c in GNU GCC 11.2 allows │
│ │ │ │ │ │ │ stack exhaustion in demangle_const │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-27943 │
├──────────────────┼────────────────┤ │ ├─────────────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤
│ ncurses-base │ CVE-2023-50495 │ │ │ 6.2-10.20210508.el9_6.2 │ │ ncurses: segmentation fault via _nc_wrap_entry() │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-50495 │
├──────────────────┤ │ │ │ ├───────────────┤ │
│ ncurses-libs │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
├──────────────────┼────────────────┤ │ ├─────────────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤
│ pcre2 │ CVE-2022-41409 │ │ │ 10.40-6.el9 │ │ pcre2: negative repeat value in a pcre2test subject line │
│ │ │ │ │ │ │ leads to inifinite... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2022-41409 │
├──────────────────┤ │ │ │ ├───────────────┤ │
│ pcre2-syntax │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
└──────────────────┴────────────────┴──────────┴──────────┴─────────────────────────┴───────────────┴─────────────────────────────────────────────────────────────┘
OpenSSL is completely absent because Trivy queries the RPM database to identify content in the container image. This constitutes a false negative.
Grype (0.102.0) output:
NAME INSTALLED FIXED IN TYPE VULNERABILITY SEVERITY EPSS RISK
openssl 3.2.2 3.0.15, 3.1.7, 3.2.3, 3.3.2 binary CVE-2024-6119 High 15.2% (94th) 11.4
openssl 3.2.2 1.0.2zk, 1.1.1za, 3.0.15, 3.1.7, 3.2.3, ... binary CVE-2024-5535 Critical 5.8% (90th) 5.3
openssl 3.2.2 1.0.2zl, 1.1.1zb, 3.0.16, 3.1.8, 3.2.4, ... binary CVE-2024-9143 Medium 0.7% (70th) 0.3
openssl 3.2.2 3.2.4, 3.3.3, 3.4.1 binary CVE-2024-12797 Medium 0.3% (55th) 0.2
openssl 3.2.2 1.0.2zl, 1.1.1zb, 3.0.16, 3.1.8, 3.2.4, ... binary CVE-2024-13176 Medium 0.1% (30th) < 0.1
openssl 3.2.2 1.0.2zm, 1.1.1zd, 3.0.18, 3.2.6, 3.3.5, ... binary CVE-2025-9230 High < 0.1% (9th) < 0.1
pcre2 10.40-6.el9 rpm CVE-2022-41409 Low < 0.1% (19th) < 0.1
pcre2-syntax 10.40-6.el9 rpm CVE-2022-41409 Low < 0.1% (19th) < 0.1
ncurses-base 6.2-10.20210508.el9_6.2 rpm CVE-2023-50495 Low < 0.1% (16th) < 0.1
ncurses-libs 6.2-10.20210508.el9_6.2 rpm CVE-2023-50495 Low < 0.1% (16th) < 0.1
libgcc 11.5.0-5.el9_5 rpm CVE-2022-27943 Low < 0.1% (15th) < 0.1
openssl 3.2.2 3.2.6, 3.3.5, 3.4.3, 3.5.4 binary CVE-2025-9231 Medium < 0.1% (2nd) < 0.1
coreutils-single 8.32-39.el9 rpm CVE-2025-5278 Medium < 0.1% (2nd) < 0.1
Grype identifies the OpenSSL files as a binary but reports several false positives, because it does not identify this version of OpenSSL from Red Hat and therefore does not use Red Hat's vulnerability source data. The known CVE-2024-9230 is reported incorrectly as "High" and matches like CVE-2024-6119 (High) or CVE-2024-5535 (Critical) are incorrect, because they have been fixed via backports in the OpenSSL 3.2.2 version from Red Hat and also only received a Medium and Low rating respectively by Red Hat Product Security.
Clair (on Quay.io, be sure to use the new UI) output: https://quay.io/repository/dmesser/ubi-micro-openssl/tag/files?tab=securityreport&digest=sha256:f04e175a6a45b449276fa52fcbf50e9490d09745fae8dcf6e8b6f0f55027cb19
The OpenSSL libraries are not identified because Clair also requires an accurate RPM database to detect RHEL base OS components.
Instead of doing as suggested for the final stage:
I'd propose to start from
scratchthere and do the copy. Like this:There's no issue that this container won't be identified as Red Hat OS. It will be because the detection relies on files present in the image and these will appear where needed after the
COPYcommand.The reason to start from
scratchis because thednfcommands (evendnf install) may remove some files. These will appear again if we doFROM [...]ubi-microbecause the subsequentCOPYcommand essentially produces the union of two filesystems (adds what's missing, overwrites what's already present, and keeps everything else).Such a thing is a tiny risk and I haven't seen it to materialize, but my suggestion follows the spirit of what's demoed here: make no leftovers so that all system files are be registered through RPM packages in RPMDB.
A disadvantage of this approach is that
LABEL-s won't be inherited fromubi-micro. Some may be required by Konflux Conforma and so as a developer you'd need to re-add them.