Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 82 additions & 1 deletion mantle/kola/tests/fips/fips.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
package fips

import (
"github.com/coreos/coreos-assembler/mantle/kola"
"github.com/coreos/coreos-assembler/mantle/kola/cluster"
"github.com/coreos/coreos-assembler/mantle/kola/register"
"github.com/coreos/coreos-assembler/mantle/platform/conf"
)

func init() {
// Minimal test case to test FIPS enabling at first boot
// Minimal test case to test FIPS enabling at first boot.
// Also tests that using TLS works in FIPS mode by having Ignition
// fetch a remote resource to make sure [1] doesn't happen again.
// [1] https://issues.redhat.com/browse/OCPBUGS-65684
register.RegisterTest(&register.Test{
Run: fipsEnableTest,
ClusterSize: 1,
Name: `fips.enable`,
Description: "Verify that fips enabled works.",
Flags: []register.Flag{},
Tags: []string{kola.NeedsInternetTag},
Distros: []string{"rhcos"},
UserData: conf.Ignition(`{
"ignition": {
Expand Down Expand Up @@ -46,6 +51,12 @@ func init() {
"verification": {}
},
"mode": 420
},
{
"path": "/var/resource/https",
"contents": {
"source": "https://ignition-test-fixtures.s3.amazonaws.com/resources/anonymous"
}
}
]
}
Expand Down Expand Up @@ -129,6 +140,70 @@ func init() {
}
}`),
})
// Test that using TLS works in FIPS mode by having Ignition fetch
// a remote resource over HTTPS with FIPS compatible algorithms.
// See https://issues.redhat.com/browse/COS-3487
// Note that 34.172.244.189, running RHCOS 9.6 (build 20260312-0) on
// Google Cloud Platform, provides HTTPS services using nginx-126:10.1.
register.RegisterTest(&register.Test{
Run: fipsEnableTestTLS,
ClusterSize: 1,
Name: `fips.enable.tls`,
Description: "Verify that fips enabled works if fetching a remote resource over HTTPS with FIPS compatible algorithms.",
Flags: []register.Flag{},
Tags: []string{kola.NeedsInternetTag},
Distros: []string{"rhcos"},
Platforms: []string{"qemu"},
ExcludeArchitectures: []string{"s390x", "ppc64le", "aarch64"}, // only test on x86_64
UserData: conf.Ignition(`{
"ignition": {
"config": {
"replace": {
"source": null,
"verification": {}
}
},
"security": {
"tls": {
"certificateAuthorities": [
{
"compression": "gzip",
"source": "data:;base64,H4sIAAAAAAAC/2SUTdN7yhbF5z7FnaduIUEY/AfdNFoQ2luYeUkkSLwk0fj0t57nnsk5Zw9/u2rV3lVrrf/+DEQGdv+jIhJiHasgRL+UcTBGVqOqoJdqQDEENY4q7G3jjqz64bkgmSjY2p8qKY3Gm0w1P7VOfYbvc+kCH9kM9AE1N1Q58G0APkKA0oi0nR+tMAp1p46NeKtUGGYXa59dMDXvpes0KXU2tHc0Z2PcBgjJD9x+4fYDf1kDbg7hqEFTLfZ9TYPECGIYYp04P+LZxeWw7nZM+cq68ueFHP/zPAh9oNW14QFNVUHaq3VtQHBWT5t93FhwWUnrFkyamb0KudwI1bgZ+jV86+TQaPrgrd/RvQRx+Xqx6lfffdnAi9FQTRw82PZ8HHM7Gt6IeaTV10vepwcr0iU/QLV250dMAquRsMi/rIJevXRV71auDPfnyVnHiD9UX4ukfUm5cGJkdr29FkGZ/DU58SQxrnjd9p/yAu+cgNL7JXq3j0oxd16NyiU3z+5bYyH5HK6NhTg5Y9J7labtJrhbee+zXBdD+7LGb42PLOOCbqoyfBp/93jvZBIBj6gGXwvCEsjE6q5o2iSGNe/EjLESWAdPw71i2EmRnCO22Y1k0i41HjGZaQLzaiM8r4kbDH3xewJdNBjhfh0ZGpTmt67ZfuLOYdjxwPJt4dh9RN/3Ba9LV78JzkjK0+rrV97xHamGieTUaRcqNVNxYdgPp5tRKtgt0PSg+wglmZ7obbN+cfNuR1JkJPLMJSx2tO1Pb+jtxnCJCpR3W8EOiGWaC8LlXZ9nT0nEJyeJm2iwTzRW/Km6Yrn189YdH5Y3Fy2K1OJT+VgDPoA939NEA2cG1m5s+jIENxlB4KgwB1SvUy0mXAd8k4WAUrVO8YmmEPqRCSiiGv3dEwhqysC+RHjcuDkE1f/FBKTXfpSRh97WU19wD/tV3W6NMRK2z0HSvf/mWOYnUX9ZFgI7MvEKKldoWtHGxSRxfLc9bbQrzSORUfLm2t2J3Bw42QVnTblbl4ww4DnmpaGN6OOuxKL79dTj7LqlPtcfH3vqK2XBC93VVzwavjTN+hnskvauGroj3Z4z442peDVjPXB4urDVFX8qsv924b7x7fuXaytdrbqyy53RL0r5my2udheyLWPJcHh6t5yZi5zjnEAxTRgeh+oRDpwkhnOunL/pQRjchO3LaPwaSuLkDs/HhqodOno6JA9BZsPYZ9BgdPPgKB9l1z/H9X3L8uU11Hv/6gSTZXpsGxm4Ts/24SbbT1pehxgCjh7XYY1Dc5KYqyh5tBYH/bk/vtbDHLgW/+yt83gTr1McTwczY08UWFIvfchVldSHYixka6XAN4lyEZinOkjjINfRNRpa3iuUs77t94NcgU8U7j6FtV0TCWTp7Qym5jn1jvupgkCKYuqEyhlhpig1gy0fs7mEuCi8ERNxt1R5+hHP5SZPX1AH1evyXCuzfZeP5uRVbNLbXbZ8UBvIw5tZtt1wqIKDRXA81H/+ML9ljVzt3wX+vwAAAP//bBEEnd0FAAA="
}
]
}
},
"timeouts": {},
"version": "3.4.0"
},
"passwd": {},
"storage": {
"files": [
{
"group": {
"name": "root"
},
"overwrite": true,
"path": "/etc/ignition-machine-config-encapsulated.json",
"user": {
"name": "root"
},
"contents": {
"source": "data:,%7B%22metadata%22%3A%7B%22name%22%3A%22rendered-worker-1cc576110e0cf8396831ce4016f63900%22%2C%22selfLink%22%3A%22%2Fapis%2Fmachineconfiguration.openshift.io%2Fv1%2Fmachineconfigs%2Frendered-worker-1cc576110e0cf8396831ce4016f63900%22%2C%22uid%22%3A%2248871c03-899d-4332-a5f5-bef94e54b23f%22%2C%22resourceVersion%22%3A%224168%22%2C%22generation%22%3A1%2C%22creationTimestamp%22%3A%222019-11-04T15%3A54%3A08Z%22%2C%22annotations%22%3A%7B%22machineconfiguration.openshift.io%2Fgenerated-by-controller-version%22%3A%22bd846958bc95d049547164046a962054fca093df%22%7D%2C%22ownerReferences%22%3A%5B%7B%22apiVersion%22%3A%22machineconfiguration.openshift.io%2Fv1%22%2C%22kind%22%3A%22MachineConfigPool%22%2C%22name%22%3A%22worker%22%2C%22uid%22%3A%223d0dee9e-c9d6-4656-a4a9-81785b9ab01a%22%2C%22controller%22%3Atrue%2C%22blockOwnerDeletion%22%3Atrue%7D%5D%7D%2C%22spec%22%3A%7B%22osImageURL%22%3A%22registry.svc.ci.openshift.org%2Focp%2F4.3-2019-11-04-125204%40sha256%3A8a344c5b157bd01c3ca1abfcef0004fc39f5d69cac1cdaad0fd8dd332ad8e272%22%2C%22config%22%3A%7B%22ignition%22%3A%7B%22config%22%3A%7B%7D%2C%22security%22%3A%7B%22tls%22%3A%7B%7D%7D%2C%22timeouts%22%3A%7B%7D%2C%22version%22%3A%223.0.0%22%7D%2C%22networkd%22%3A%7B%7D%2C%22passwd%22%3A%7B%7D%2C%22storage%22%3A%7B%7D%2C%22systemd%22%3A%7B%7D%7D%2C%22kernelArguments%22%3A%5B%5D%2C%22fips%22%3Atrue%7D%7D",
"verification": {}
},
"mode": 420
},
{
"path": "/var/resource/https-fips",
"contents": {
"source": "https://34.172.244.189:8443/index.html"
}
Comment on lines +199 to +201
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The test uses a hardcoded IP address 34.172.244.189 to fetch a resource. This makes the test fragile and dependent on an external service that might not be available or could change, leading to flaky tests and maintenance overhead.

It would be better to either:

  1. Dynamically provision the nginx server as part of the test setup and retrieve its IP address.
  2. If the server must be pre-existing, its address should be passed as a configuration parameter to the test suite, not hardcoded in the test itself.

}
]
}
}`),
})
}

// Test: Run basic FIPS test
Expand All @@ -137,3 +212,9 @@ func fipsEnableTest(c cluster.TestCluster) {
c.AssertCmdOutputContains(m, `cat /proc/sys/crypto/fips_enabled`, "1")
c.AssertCmdOutputContains(m, `update-crypto-policies --show`, "FIPS")
}

func fipsEnableTestTLS(c cluster.TestCluster) {
fipsEnableTest(c)
m := c.Machines()[0]
c.AssertCmdOutputContains(m, `cat /var/resource/https-fips`, "This file was served from an RHCOS FIPS-hardened server.")
}
13 changes: 13 additions & 0 deletions tests/containers/fips-nginx/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM registry.redhat.io/rhel10/nginx-126:10.1

ADD nginx.conf "${NGINX_CONF_PATH}"

COPY index.html /usr/share/nginx/html/index.html

# TLS material
USER 0
COPY tls/ /etc/nginx/tls/
RUN chown -R 1001:0 /etc/nginx/tls
USER 1001

CMD ["nginx", "-g", "daemon off;"]
28 changes: 28 additions & 0 deletions tests/containers/fips-nginx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# fips-nginx Container

This is used by the `fips.enable.https` test to verify that using
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test name appears to be fips.enable.tls, not fips.enable.https. Please update the documentation for consistency.

Suggested change
This is used by the `fips.enable.https` test to verify that using
This is used by the `fips.enable.tls` test to verify that using

TLS works in FIPS mode by having Ignition fetch a remote resource
over HTTPS with FIPS compatible algorithms.

See https://catalog.redhat.com/en/software/containers/rhel10/nginx-126/677d3718e58b5a1ae5598058#overview

To build the container using command:
`./build.sh <IP>`

To run the container image using command:
`podman run -d -p 8443:8443 --name fips-nginx fips-nginx`

Remember to create firewall-rules to allow port 8443:
```
gcloud compute firewall-rules create allow-nginx-fips-8443 \
--action ALLOW \
--direction INGRESS \
--rules tcp:8443 \
--source-ranges 0.0.0.0/0 \
--target-tags nginx-fips-server \
--description "Allow FIPS test access to nginx on port 8443"

gcloud compute instances add-tags rhcos-fips-test \
--zone us-central1-a \
--tags nginx-fips-server
```
107 changes: 107 additions & 0 deletions tests/containers/fips-nginx/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env bash

# Run the image using command:
# podman run -d --name fips-nginx -p 8443:8443 fips-nginx
set -euo pipefail

# Check if argument is provided
if [ $# -eq 0 ]; then
echo "Error: Missing IP address argument"
echo "Usage: $0 <ip-address>"
exit 1
fi

ip="$1"

tmpdir="$(mktemp -d)"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The script creates a temporary directory but doesn't clean it up on exit. This can leave garbage files on the system. It's a good practice to use trap to ensure cleanup, even if the script fails.

Suggested change
tmpdir="$(mktemp -d)"
tmpdir="$(mktemp -d)"
trap 'rm -rf -- "$tmpdir"' EXIT

cp Containerfile ${tmpdir}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The script assumes it is run from the directory where Containerfile is located. This can be brittle. To make the script more robust and runnable from any location, you should reference files relative to the script's own path.

Suggested change
cp Containerfile ${tmpdir}
cp "$(dirname "$0")/Containerfile" "${tmpdir}"

cd ${tmpdir}

# Prepare index.html
cat <<EOF > index.html
This file was served from an RHCOS FIPS-hardened server.
EOF

# Prepare nginx.conf
cat <<EOF > nginx.conf
events {}

http {
server {
listen 8443 ssl;
server_name _;

# ---- FIPS-only TLS ----
ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;

ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256;

ssl_certificate /etc/nginx/tls/fips-server.crt;
ssl_certificate_key /etc/nginx/tls/fips-server.key;

location / {
root /usr/share/nginx/html;
index index.html;
}
}
}
EOF

mkdir -p tls
pushd tls/
# Prepare openssl.cnf
# The IP must point to an nginx server configured with FIPS-compliant ciphers
cat <<SSLEOF > openssl.cnf
[ req ]
default_bits = 3072
distinguished_name = dn
prompt = no
string_mask = utf8only
req_extensions = req_ext

[ dn ]
CN = FIPS TLS Test Server

[ req_ext ]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = critical, serverAuth
subjectAltName = @alt_names

[ alt_names ]
IP.1 = ${ip}
SSLEOF

# Prepare key and crt
## Generate the private key (FIPS-approved)
openssl genpkey \
-algorithm RSA \
-pkeyopt rsa_keygen_bits:3072 \
-out fips-server.key

## Generate CSR (still FIPS-only)
openssl req -new -key fips-server.key -out fips-server.csr -config openssl.cnf

## Self-sign the certificate (TLS-compatible + FIPS)
openssl x509 -req \
-in fips-server.csr \
-signkey fips-server.key \
-out fips-server.crt \
-days 3650 \
-sha256 \
-extfile openssl.cnf \
-extensions req_ext

# Verify SAN present
openssl x509 -in fips-server.crt -noout -text | grep -A2 "Subject Alternative Name"

openssl verify \
-provider fips \
-CAfile fips-server.crt \
fips-server.crt

rm fips-server.csr openssl.cnf

popd

podman build -t fips-nginx .
Loading