From 536811229c0d635720401303e9966d4004263bda Mon Sep 17 00:00:00 2001 From: Thomas Bechtold Date: Thu, 28 May 2026 22:02:00 +0200 Subject: [PATCH 1/3] add test/tw/gen-tls-cert pipeline A reusable pipeline for fabricating self-signed RSA X.509 cert/key pairs during melange test setup. Replaces the ad-hoc `openssl req -x509 ...` boilerplate that ~340 stereo packages currently inline inside `test/daemon-check-output` setup blocks (webhook serving certs, dummy TLS for exporters, etc.). Inputs: - cn (required), san, key-out, cert-out, days, key-bits Includes a manual test under tests/manual/gen-tls-cert.yaml exercising the minimal, SAN+custom-paths, and key-bits=4096 cases. Co-Authored-By: Claude Opus 4.7 (1M context) --- pipelines/test/tw/INDEX.md | 26 +++++++ pipelines/test/tw/gen-tls-cert.yaml | 109 ++++++++++++++++++++++++++++ tests/manual/gen-tls-cert.yaml | 102 ++++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 pipelines/test/tw/gen-tls-cert.yaml create mode 100644 tests/manual/gen-tls-cert.yaml diff --git a/pipelines/test/tw/INDEX.md b/pipelines/test/tw/INDEX.md index 47e38d53..5bd5786c 100644 --- a/pipelines/test/tw/INDEX.md +++ b/pipelines/test/tw/INDEX.md @@ -14,6 +14,7 @@ This directory contains test pipelines for validating Wolfi packages. Use this i | `emptypackage` | Validate empty packages | none | | `gem-check` | Verify Ruby gems load correctly | none | | `gem-installed` | Verify gem is installed and has content | none | +| `gen-tls-cert` | Generate a self-signed TLS cert for test setup | `cn` | | `header-check` | Verify C/C++ headers compile | none | | `help-check` | Verify binaries respond to --help | `bins` | | `ldd-check` | Check for missing shared libraries | none | @@ -321,6 +322,31 @@ Validates systemd service/unit files for proper formatting and best practices. --- +## Test Setup Helpers + +Pipelines that fabricate test fixtures (not validators — they never assert on +package contents). + +### `gen-tls-cert` +Generates a self-signed RSA X.509 certificate and private key on disk. Use it +to replace inline `openssl req -x509 ...` boilerplate in `test/daemon-check-output` +`setup:` blocks (webhook serving certs, dummy TLS for exporters, etc.). + +**When to use:** Whenever a test needs a throwaway cert/key on disk before the +daemon under test starts. + +**Inputs:** +- `cn` (required) - Subject Common Name +- `san` (optional) - subjectAltName value, e.g. `DNS:foo.svc,IP:127.0.0.1` +- `key-out` (optional, default: `/tmp/tls.key`) - Private key output path +- `cert-out` (optional, default: `/tmp/tls.crt`) - Certificate output path +- `days` (optional, default: `365`) - Validity in days +- `key-bits` (optional, default: `2048`) - RSA key size + +**Dependencies:** openssl + +--- + ## Usage Examples ### Basic package type validation diff --git a/pipelines/test/tw/gen-tls-cert.yaml b/pipelines/test/tw/gen-tls-cert.yaml new file mode 100644 index 00000000..3cebc4e6 --- /dev/null +++ b/pipelines/test/tw/gen-tls-cert.yaml @@ -0,0 +1,109 @@ +name: TLS test certificate generator + +description: | + Generates a self-signed RSA X.509 certificate and private key suitable for + test setups (e.g. webhook/admission-controller serving certs in + `test/daemon-check-output` blocks). This is a setup helper, not a validator + — it never fails the test on content; it only fails if openssl cannot + produce the requested key/cert. + +needs: + packages: + - openssl + +inputs: + cn: + description: | + Subject Common Name to embed in the certificate. + Example: "webhook.azure-workload-identity-test.svc" + required: true + san: + description: | + Optional subjectAltName extension value, passed verbatim to + `openssl req -addext "subjectAltName=..."`. + Example: "DNS:webhook.svc,DNS:webhook.svc.cluster.local,IP:127.0.0.1" + If empty, no SAN extension is added. + required: false + default: "" + key-out: + description: | + Output path for the generated private key (PEM). + Parent directories are created if missing. + required: false + default: "/tmp/tls.key" + cert-out: + description: | + Output path for the generated certificate (PEM). + Parent directories are created if missing. + required: false + default: "/tmp/tls.crt" + days: + description: | + Certificate validity in days. + required: false + default: "365" + key-bits: + description: | + RSA key size in bits. + required: false + default: "2048" + +# USAGE EXAMPLES: +# +# Minimal serving cert at the default /tmp/tls.{key,crt} paths: +# - uses: test/tw/gen-tls-cert +# with: +# cn: webhook.svc +# +# Cert with SAN, custom output paths, short validity: +# - uses: test/tw/gen-tls-cert +# with: +# cn: actions-runner-controller-webhook-service.default.svc +# san: "DNS:actions-runner-controller-webhook-service.default.svc" +# key-out: /tmp/k8s-webhook-server/serving-certs/tls.key +# cert-out: /tmp/k8s-webhook-server/serving-certs/tls.crt +# days: "1" +# +pipeline: + - name: "generate self-signed TLS certificate" + runs: | + set -e + + cn="${{inputs.cn}}" + san="${{inputs.san}}" + key_out="${{inputs.key-out}}" + cert_out="${{inputs.cert-out}}" + days="${{inputs.days}}" + key_bits="${{inputs.key-bits}}" + + if [ -z "${cn}" ] ; then + echo "FAIL: 'cn' input is required" >&2 + exit 1 + fi + + mkdir -p "$(dirname "${key_out}")" "$(dirname "${cert_out}")" + + addext_args="" + if [ -n "${san}" ] ; then + addext_args="-addext subjectAltName=${san}" + fi + + # shellcheck disable=SC2086 # we want addext_args to split. + openssl req -x509 \ + -newkey "rsa:${key_bits}" \ + -nodes \ + -days "${days}" \ + -keyout "${key_out}" \ + -out "${cert_out}" \ + -subj "/CN=${cn}" \ + ${addext_args} + + # Sanity check: the cert must be parseable and carry the requested CN. + subject=$(openssl x509 -in "${cert_out}" -noout -subject) + echo "${subject}" + if ! echo "${subject}" | grep -qF "CN=${cn}" ; then + echo "FAIL: generated cert subject [${subject}] does not contain CN=${cn}" >&2 + exit 1 + fi + + echo "PASS: TLS test cert generated at ${cert_out} (key: ${key_out})" diff --git a/tests/manual/gen-tls-cert.yaml b/tests/manual/gen-tls-cert.yaml new file mode 100644 index 00000000..d5b3b8cf --- /dev/null +++ b/tests/manual/gen-tls-cert.yaml @@ -0,0 +1,102 @@ +package: + name: gen-tls-cert-test + version: "0.0.0" + epoch: 0 + description: Manual tests for the test/tw/gen-tls-cert pipeline + +environment: + contents: + packages: + - busybox + - wolfi-base + +pipeline: + - runs: | + echo "Manual tests for gen-tls-cert pipeline" + +subpackages: + # Positive case 1: minimal invocation, default paths. + - name: gen-tls-cert-test-minimal + description: "Positive: cn only, default /tmp/tls.{key,crt} paths" + pipeline: + - runs: | + mkdir -p ${{targets.contextdir}}/usr/share/doc/${{context.name}} + echo "placeholder" > ${{targets.contextdir}}/usr/share/doc/${{context.name}}/README + test: + environment: + contents: + packages: + - openssl + pipeline: + - uses: test/tw/gen-tls-cert + with: + cn: test.example.com + - name: Verify default key and cert exist and match + runs: | + set -e + test -s /tmp/tls.key + test -s /tmp/tls.crt + openssl x509 -in /tmp/tls.crt -noout -subject | grep -qF "CN=test.example.com" + # Ensure the cert and key actually correspond. + cert_mod=$(openssl x509 -noout -modulus -in /tmp/tls.crt | openssl md5) + key_mod=$(openssl rsa -noout -modulus -in /tmp/tls.key | openssl md5) + [ "${cert_mod}" = "${key_mod}" ] + echo "PASS: minimal gen-tls-cert produced a usable key+cert pair" + + # Positive case 2: SAN, custom output paths, short validity. + - name: gen-tls-cert-test-san + description: "Positive: SAN extension, custom paths, days=1" + pipeline: + - runs: | + mkdir -p ${{targets.contextdir}}/usr/share/doc/${{context.name}} + echo "placeholder" > ${{targets.contextdir}}/usr/share/doc/${{context.name}}/README + test: + environment: + contents: + packages: + - openssl + pipeline: + - uses: test/tw/gen-tls-cert + with: + cn: webhook.svc + san: "DNS:webhook.svc,DNS:webhook.svc.cluster.local" + key-out: /tmp/serving/tls.key + cert-out: /tmp/serving/tls.crt + days: "1" + - name: Verify SAN and custom paths + runs: | + set -e + test -s /tmp/serving/tls.key + test -s /tmp/serving/tls.crt + text=$(openssl x509 -in /tmp/serving/tls.crt -noout -text) + echo "${text}" | grep -qF "CN=webhook.svc" + echo "${text}" | grep -qF "DNS:webhook.svc" + echo "${text}" | grep -qF "DNS:webhook.svc.cluster.local" + echo "PASS: gen-tls-cert wrote SAN cert at custom path" + + # Positive case 3: custom key size. + - name: gen-tls-cert-test-4096 + description: "Positive: non-default key-bits=4096" + pipeline: + - runs: | + mkdir -p ${{targets.contextdir}}/usr/share/doc/${{context.name}} + echo "placeholder" > ${{targets.contextdir}}/usr/share/doc/${{context.name}}/README + test: + environment: + contents: + packages: + - openssl + pipeline: + - uses: test/tw/gen-tls-cert + with: + cn: bigkey.example.com + key-bits: "4096" + - name: Verify key size honors key-bits input + runs: | + set -e + # `openssl pkey -text` prints something like + # "Private-Key: (4096 bit, 2 primes)" on OpenSSL 3, + # "Private-Key: (4096 bit)" on OpenSSL 1.1. + # Grepping for the literal "4096 bit" covers both. + openssl pkey -in /tmp/tls.key -noout -text | grep -q "4096 bit" + echo "PASS: key-bits=4096 produced a 4096-bit key" From 36674447c4df19cc4dd3607dc46d653a2f30dd94 Mon Sep 17 00:00:00 2001 From: Thomas Bechtold Date: Fri, 29 May 2026 06:28:47 +0200 Subject: [PATCH 2/3] gen-tls-cert: default days to 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are throwaway test certs — no reason to default to a 365-day validity. Callers that need longer-lived certs (e.g. clock-skew exercises) can still override via the `days` input. Co-Authored-By: Claude Opus 4.7 (1M context) --- pipelines/test/tw/INDEX.md | 2 +- pipelines/test/tw/gen-tls-cert.yaml | 9 +++++---- tests/manual/gen-tls-cert.yaml | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pipelines/test/tw/INDEX.md b/pipelines/test/tw/INDEX.md index 5bd5786c..8df094ab 100644 --- a/pipelines/test/tw/INDEX.md +++ b/pipelines/test/tw/INDEX.md @@ -340,7 +340,7 @@ daemon under test starts. - `san` (optional) - subjectAltName value, e.g. `DNS:foo.svc,IP:127.0.0.1` - `key-out` (optional, default: `/tmp/tls.key`) - Private key output path - `cert-out` (optional, default: `/tmp/tls.crt`) - Certificate output path -- `days` (optional, default: `365`) - Validity in days +- `days` (optional, default: `1`) - Validity in days (these are throwaway test certs) - `key-bits` (optional, default: `2048`) - RSA key size **Dependencies:** openssl diff --git a/pipelines/test/tw/gen-tls-cert.yaml b/pipelines/test/tw/gen-tls-cert.yaml index 3cebc4e6..7ff45369 100644 --- a/pipelines/test/tw/gen-tls-cert.yaml +++ b/pipelines/test/tw/gen-tls-cert.yaml @@ -39,9 +39,11 @@ inputs: default: "/tmp/tls.crt" days: description: | - Certificate validity in days. + Certificate validity in days. Defaults to 1 because this pipeline is + only for throwaway test certs — override if your test legitimately + needs a longer-lived cert (e.g. clock-skew exercises). required: false - default: "365" + default: "1" key-bits: description: | RSA key size in bits. @@ -55,14 +57,13 @@ inputs: # with: # cn: webhook.svc # -# Cert with SAN, custom output paths, short validity: +# Cert with SAN and custom output paths: # - uses: test/tw/gen-tls-cert # with: # cn: actions-runner-controller-webhook-service.default.svc # san: "DNS:actions-runner-controller-webhook-service.default.svc" # key-out: /tmp/k8s-webhook-server/serving-certs/tls.key # cert-out: /tmp/k8s-webhook-server/serving-certs/tls.crt -# days: "1" # pipeline: - name: "generate self-signed TLS certificate" diff --git a/tests/manual/gen-tls-cert.yaml b/tests/manual/gen-tls-cert.yaml index d5b3b8cf..5b85ff8c 100644 --- a/tests/manual/gen-tls-cert.yaml +++ b/tests/manual/gen-tls-cert.yaml @@ -43,9 +43,9 @@ subpackages: [ "${cert_mod}" = "${key_mod}" ] echo "PASS: minimal gen-tls-cert produced a usable key+cert pair" - # Positive case 2: SAN, custom output paths, short validity. + # Positive case 2: SAN, custom output paths, non-default validity. - name: gen-tls-cert-test-san - description: "Positive: SAN extension, custom paths, days=1" + description: "Positive: SAN extension, custom paths, days override" pipeline: - runs: | mkdir -p ${{targets.contextdir}}/usr/share/doc/${{context.name}} @@ -62,7 +62,7 @@ subpackages: san: "DNS:webhook.svc,DNS:webhook.svc.cluster.local" key-out: /tmp/serving/tls.key cert-out: /tmp/serving/tls.crt - days: "1" + days: "7" - name: Verify SAN and custom paths runs: | set -e From a713bc93da3c1da7b681215960cb119f9c0c1c34 Mon Sep 17 00:00:00 2001 From: Thomas Bechtold Date: Fri, 29 May 2026 08:07:41 +0200 Subject: [PATCH 3/3] gen-tls-cert: bump default validity from 1 to 7 days 1 day is uncomfortably close to expiry for tests that sit idle in a debug shell or get re-run shortly after build. 7 days keeps the "throwaway" intent while giving more headroom. Co-Authored-By: Claude Opus 4.7 (1M context) --- pipelines/test/tw/INDEX.md | 2 +- pipelines/test/tw/gen-tls-cert.yaml | 4 ++-- tests/manual/gen-tls-cert.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pipelines/test/tw/INDEX.md b/pipelines/test/tw/INDEX.md index 8df094ab..89e7d20c 100644 --- a/pipelines/test/tw/INDEX.md +++ b/pipelines/test/tw/INDEX.md @@ -340,7 +340,7 @@ daemon under test starts. - `san` (optional) - subjectAltName value, e.g. `DNS:foo.svc,IP:127.0.0.1` - `key-out` (optional, default: `/tmp/tls.key`) - Private key output path - `cert-out` (optional, default: `/tmp/tls.crt`) - Certificate output path -- `days` (optional, default: `1`) - Validity in days (these are throwaway test certs) +- `days` (optional, default: `7`) - Validity in days (these are throwaway test certs) - `key-bits` (optional, default: `2048`) - RSA key size **Dependencies:** openssl diff --git a/pipelines/test/tw/gen-tls-cert.yaml b/pipelines/test/tw/gen-tls-cert.yaml index 7ff45369..2b8a90e6 100644 --- a/pipelines/test/tw/gen-tls-cert.yaml +++ b/pipelines/test/tw/gen-tls-cert.yaml @@ -39,11 +39,11 @@ inputs: default: "/tmp/tls.crt" days: description: | - Certificate validity in days. Defaults to 1 because this pipeline is + Certificate validity in days. Defaults to 7 because this pipeline is only for throwaway test certs — override if your test legitimately needs a longer-lived cert (e.g. clock-skew exercises). required: false - default: "1" + default: "7" key-bits: description: | RSA key size in bits. diff --git a/tests/manual/gen-tls-cert.yaml b/tests/manual/gen-tls-cert.yaml index 5b85ff8c..15b0d727 100644 --- a/tests/manual/gen-tls-cert.yaml +++ b/tests/manual/gen-tls-cert.yaml @@ -62,7 +62,7 @@ subpackages: san: "DNS:webhook.svc,DNS:webhook.svc.cluster.local" key-out: /tmp/serving/tls.key cert-out: /tmp/serving/tls.crt - days: "7" + days: "30" - name: Verify SAN and custom paths runs: | set -e