Add: Exact client certificate pinning for openvasd mTLS#2250
Open
mde-gb wants to merge 17 commits into
Open
Conversation
Support pinning exact client certificates for openvasd mTLS in addition to CA-based client trust. Pinned client leaves are validated for time validity and clientAuth extended key usage, and CA hints are only sent when CA-based client trust is configured. Also includes supporting changes: - Update x509-parser to 0.18.1 - Add pinned mTLS smoke-test workflow and client-auth test certificates - Restore arm64 Rust builds on pull requests and keep dependency review fail-closed - Add RELICENSE entry for mde-gb Co-authored-by: AI (codex/partial)
Dependency ReviewThe following issues were found:
Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. License Issues.github/workflows/openvasd-pinned-mtls-smoke.yml
OpenSSF Scorecard
Scanned Files
|
Contributor
There was a problem hiding this comment.
Pull request overview
Adds support for exact (pinned) client leaf certificates for openvasd mTLS, allowing client authentication by matching the presented leaf certificate DER against a configured set, optionally alongside existing CA-based client cert validation.
Changes:
- Introduces a
PinnedClientCertVerifier(rustlsClientCertVerifier) and config plumbing to accept pinned client leaf certs in addition to (or instead of) CA-based validation. - Adds CLI/env/config/docs for
pinned_client_certs, plus new test fixtures and a CI smoke workflow validating pinned-mTLS behavior. - Includes small incidental cleanups (tempfile usage in tests, SQL whitespace tweak) and bumps
x509-parserto0.18.1.
Reviewed changes
Copilot reviewed 15 out of 17 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| rust/src/openvasd/README.md | Documents pinned client cert option and CLI flag for openvasd mTLS. |
| rust/src/openvasd/main.rs | Wires pinned_client_certs from config into the runtime builder. |
| rust/src/openvasd/container_image_scanner/scheduling/db/sqlite/images.rs | Trims trailing whitespace in a SQL string. |
| rust/src/openvasd/container_image_scanner/detection.rs | Makes OS-detection test use NamedTempFile instead of a fixed /tmp path. |
| rust/src/openvasd/config/mod.rs | Adds [tls].pinned_client_certs plus CLI/env handling. |
| rust/examples/tls/self-signed/client_certificates.sh | Adjusts OpenSSL extension section used for client cert generation. |
| rust/examples/openvasd/config.example.toml | Adds example/commentary for pinned_client_certs. |
| rust/crates/greenbone-scanner-framework/src/tls.rs | Implements pinned-leaf verification and updates TLS config assembly; adds unit tests. |
| rust/crates/greenbone-scanner-framework/src/test-data/pinned-client.pem | Adds pinned client cert fixture. |
| rust/crates/greenbone-scanner-framework/src/test-data/other-client.pem | Adds alternate client cert fixture. |
| rust/crates/greenbone-scanner-framework/src/test-data/ca.pem | Adds CA cert fixture used by tests. |
| rust/crates/greenbone-scanner-framework/src/lib.rs | Extends TLS config/state to carry pinned client cert path and treat it as mTLS auth. |
| rust/crates/greenbone-scanner-framework/Cargo.toml | Adds x509-parser dependency to the framework crate. |
| rust/Cargo.toml | Bumps/introduces workspace dependency for x509-parser. |
| rust/Cargo.lock | Updates lockfile for x509-parser bump and new dependency edges. |
| RELICENSE/mde-gb.md | Adds relicensing/DCO document for the contribution. |
| .github/workflows/openvasd-pinned-mtls-smoke.yml | Adds CI smoke workflow exercising pinned-mTLS end-to-end. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: AI (copilot/partial)
The pinned mTLS smoke workflow previously built the binary in a separate job via the reusable build-rs.yaml workflow and passed it to the smoke job using actions/upload-artifact and actions/download-artifact. The dependency review flagged actions/download-artifact (and actions/checkout) with an unknown license. Inline the same Docker build directly in the smoke job and resolve the binary from the local build output, removing the cross-job artifact handoff entirely. This drops the actions/download-artifact dependency and its license warning without relying on greenbone/actions/download-artifact, which fetches artifacts from a separate completed run via the GitHub API and does not fit a same-run handoff. - Remove the build job and its dependency on build-rs.yaml - Build openvasd inline with the same target, build args, and GHA cache - Resolve the binary from ./dist-amd64 instead of the downloaded artifact - Drop the build-rs.yaml path trigger Co-authored-by: AI (copilot/full)
- Reword openvasd README to "registered clients'" leaf certificates - Dedupe x509-parser to the workspace dependency in scannerlib - Rename the CA-rejection test to reflect what it asserts - Add a mixed-mode test: pinned leaf from an untrusted CA is accepted while a sibling leaf from that CA is rejected - Fill the MIT-0 copyright placeholder in RELICENSE/mde-gb.md Co-authored-by: AI (copilot/partial)
- Clarify --tls-pinned-client-certs help: path may be a file or directory - Reword load_client_cert_paths errors to "client authentication certificate" since the path may hold CA certs, not only client leaves - Add a negative test: a pinned leaf without the clientAuth EKU is rejected (new no-client-auth-client.pem fixture, CA:FALSE + serverAuth) Co-authored-by: AI (copilot/partial)
Only apply client certificate and pinned client certificate paths after both server TLS certificate and key are configured. This prevents client authentication settings from creating a TLS runtime with empty server certificate paths when openvasd should run without TLS. Co-authored-by: AI (codex/partial)
Include directory and entry paths when reading pinned client certificate directories fails, so startup errors identify the problematic path. Add a regression test for metadata failures caused by broken symlink entries. Co-authored-by: AI (codex/partial)
Apply rustfmt output for pinned client certificate tests so cargo fmt --check passes in CI. Co-authored-by: AI (codex/partial)
Follow symlink metadata before accepting legacy client_certs entries as certificate files. This avoids treating symlinked directories or broken symlinks as candidate CA certificates while keeping the existing non-recursive loading behavior. Co-authored-by: AI (codex/partial)
Document that pinned client certificate configuration accepts a file or directory and centralize the startup error for client authentication without complete server TLS. Co-authored-by: AI (codex/partial)
Ignore unreadable entries while scanning pinned client certificate directories, so broken symlinks are not treated as certificate files. Keep failing startup when no usable pinned certificate files remain. Co-authored-by: AI (codex/partial)
Comment on lines
+380
to
+383
| let root_hint_subjects = ca_verifier | ||
| .as_ref() | ||
| .map(|verifier| verifier.root_hint_subjects().to_vec()) | ||
| .unwrap_or_default(); |
| smoke: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: greenbone/actions/checkout@v3 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds support for pinned (exact) client leaf certificates for openvasd mTLS,
alongside the existing CA-based client certificate validation.
[tls]optionpinned_client_certs(file or directory of client leafcerts), with CLI flag
--tls-pinned-client-certsand envTLS_PINNED_CLIENT_CERTS.PinnedClientCertVerifier(rustlsClientCertVerifier) that authenticatesa client when its leaf certificate exactly matches a configured/registered
certificate — without trusting the issuing CA for any other client.
rejected if they are CA certificates, must carry the
clientAuthextendedkey usage, and must allow
digitalSignaturewhen aKeyUsageextension ispresent.
client_certs(CA-based) andpinned_client_certscan be combined: CA trustkeeps working as before, while pinned certs are accepted in addition.
client_certsbehaviour for missing or empty directories ispreserved, and symlinked directories or broken symlinks are not treated as
client certificate files.
pinned_client_certsfails startup when theconfigured path is missing, invalid, or contains no certificate files.
fail startup with an explicit error otherwise.
openvasd/README.md,config.example.toml), tests, test fixtures, anda
pinned-mtlsCI smoke workflow.Incidental cleanups pulled in while building this: an OS-detection test now uses
a
NamedTempFileinstead of a predictable/tmp/os-releasepath, a trailingwhitespace fix in a SQL string, and the self-signed client certificate example
now uses the client certificate extension profile.
Why
Pinned client certificates let an operator authenticate clients that cannot or
should not share a common CA:
self-signed certificate. There is no intermediate/root CA to run, distribute,
or rotate — lowering the barrier for smaller deployments and integrations.
issues. Pinning trusts exactly the enrolled leaf and nothing else, so a
compromised or over-permissive CA cannot mint additional accepted clients.
This is a least-privilege / reduced-blast-radius improvement.
can be onboarded individually without forcing them under one shared CA.
time validity, rejected if they are CA certs, required to have the
clientAuthEKU, and checked for signing-capableKeyUsagewhen present -so pinning adds an authentication mode without loosening the existing one. CA
mode is unchanged and remains supported.
How
build_client_cert_verifierdecides the verifier based on configuration:WebPkiClientVerifier(unchanged behaviour).PinnedClientCertVerifier, which first tries the CAverifier (when CA certs are also configured) and otherwise falls back to an
exact DER match against the pinned set, followed by validity/CA/EKU/KeyUsage
checks.
built over the union of CA + pinned roots, so handshake signature checks keep
using vetted rustls logic.
pinned_client_certsflows from TOML/CLI/env →RunnerBuilder::path_pinned_client_certs→tls_config.non-
clientAuthcerts, missingdigitalSignature, pinned CA certificates,empty pinned cert directories, symlinked directories, and rejection of unknown
clients. A smoke workflow generates ephemeral certs and exercises the path end
to end in CI.
IMPORTANT — contributing checklist
RELICENSE/mde-gb.md) — first contribution.Add:(new feature → minor release).tests, and this PR description were drafted with AI assistance (Codex /
GitHub Copilot) and reviewed by a human before submission. See the
Co-authored-by: AI (codex/partial)trailer on the relevant commit.Jira: SC-1629