diff --git a/pipelines/test/tw/INDEX.md b/pipelines/test/tw/INDEX.md index 47e38d53..89e7d20c 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: `7`) - Validity in days (these are throwaway test certs) +- `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..2b8a90e6 --- /dev/null +++ b/pipelines/test/tw/gen-tls-cert.yaml @@ -0,0 +1,110 @@ +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. 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: "7" + 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 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 +# +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..15b0d727 --- /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, non-default validity. + - name: gen-tls-cert-test-san + description: "Positive: SAN extension, custom paths, days override" + 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: "30" + - 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"