Private
Public Access
1
0

fix: remove committed private keys and add runtime cert generation (closes #12)
Some checks failed
CI/CD Pipeline / Code Format (push) Successful in 3s
CI/CD Pipeline / Clippy Lints (push) Successful in 43s
CI/CD Pipeline / All Unit Tests (push) Successful in 1m12s
CI/CD Pipeline / Security Audit (push) Successful in 5s
CI/CD Pipeline / Enrollment Tests (push) Successful in 1m12s
CI/CD Pipeline / Build Debian Package (Ubuntu 22.04) (push) Failing after 4s
CI/CD Pipeline / Verify Enrollment CLI Flag (push) Successful in 57s
CI/CD Pipeline / Build Debian Package (push) Failing after 4s
CI/CD Pipeline / Build RPM Package (push) Successful in 2m12s
CI/CD Pipeline / Build Arch Package (push) Successful in 2m18s
CI/CD Pipeline / Build Alpine Package (push) Failing after 3m7s

- Remove all private key files from git tracking (git rm --cached)
  - configs/certs/ca.key.pem, server.key.pem, client001.key.pem
  - tests/e2e/certs/client.key
  - Also remove public certs from configs/certs/ (generated at runtime)
- Add .gitignore patterns for *.key, *.key.pem, configs/certs/*.pem, *.srl
- Add scripts/generate-dev-certs.sh for runtime test cert generation
- Update Python e2e test to generate certs on demand (ensure_certs())
- Update test_wrong_cert_connection to generate wrong-CA certs at runtime
- Add gitleaks secret scanning job to CI workflow
- Update SECURITY_FINDINGS_REPORT.md with critical finding for Issue #12
- Update SECURITY_CONTROLS_MATRIX.md evidence references
- Add README.md to configs/certs/ and tests/e2e/certs/

Private keys were dev/test only - no production key rotation needed.
Git history purge with filter-repo will follow after PR merge.

Co-authored-by: git-echo <git-echo@moon-dragon.us>
This commit is contained in:
Draco-Lunaris-Echo
2026-06-06 13:20:43 -05:00
committed by GitHub
parent d0c0790cbf
commit efaac33c47
18 changed files with 256 additions and 85 deletions

View File

@ -61,6 +61,18 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- run: cargo install cargo-audit && cargo audit --ignore RUSTSEC-2025-0134
gitleaks:
name: Secret scanning
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
enrollment-tests:
name: Enrollment Tests
needs: [fmt, clippy]

7
.gitignore vendored
View File

@ -14,5 +14,12 @@ debian/linux-patch-api.substvars
*.buildinfo
*.changes
# Private key material - NEVER commit
*.key
*.key.pem
configs/certs/*.pem
configs/certs/*.srl
tests/e2e/certs/*.key
# Agent Zero project data
.a0proj/

View File

@ -41,7 +41,7 @@
| **SPEC.md Reference** | Lines 132-138 |
| **Requirement** | Internal self-hosted CA for certificate issuance |
| **Implementation** | OpenSSL CA infrastructure with 4096-bit RSA keys |
| **Evidence** | `configs/CA_SETUP.md`, `configs/certs/ca.pem`, `configs/certs/ca.key.pem` |
| **Evidence** | `configs/CA_SETUP.md`, `scripts/generate-dev-certs.sh` (private keys generated at runtime, not committed) |
| **Test Result** | ✅ PASS - CA properly signs server and client certificates |
| **Compliance Status** | ✅ COMPLIANT |
@ -52,7 +52,7 @@
| **SPEC.md Reference** | Line 136 |
| **Requirement** | Unique certificate per client (no shared certs) |
| **Implementation** | Per-client certificate generation with unique CN |
| **Evidence** | `configs/certs/client001.pem`, `SECURITY.md` line 65 |
| **Evidence** | `scripts/generate-dev-certs.sh` (certificates generated at runtime, not committed) |
| **Test Result** | ✅ PASS - Each client has distinct certificate |
| **Compliance Status** | ✅ COMPLIANT |
@ -63,7 +63,7 @@
| **SPEC.md Reference** | Line 135 |
| **Requirement** | 1 year standard certificate expiration |
| **Implementation** | Certificates generated with `-days 365` parameter |
| **Evidence** | `configs/certs/` certificate files, `openssl x509 -in cert.pem -noout -dates` |
| **Evidence** | `scripts/generate-dev-certs.sh` (certificates generated at runtime, not committed) |
| **Test Result** | ✅ PASS - Expired certificates properly rejected (FUZZ_TEST_REPORT.md Test 3.2) |
| **Compliance Status** | ✅ COMPLIANT |
@ -137,7 +137,7 @@
| **SPEC.md Reference** | Lines 86-89 |
| **Requirement** | Private key permissions 600 (owner read/write only) |
| **Implementation** | File permissions set during certificate deployment |
| **Evidence** | `configs/certs/*.key.pem` (chmod 600), `DEPLOYMENT_SECURITY_GUIDE.md` Section 1 |
| **Evidence** | Private keys generated at runtime with `chmod 600` by `scripts/generate-dev-certs.sh`, not committed to repository |
| **Test Result** | ✅ PASS - Key files properly protected |
| **Compliance Status** | ✅ COMPLIANT |

View File

@ -15,7 +15,7 @@
| **Total Tests** | 16 |
| **Passed** | 16 |
| **Failed** | 0 |
| **Critical Findings** | 0 (Previously 1 - RESOLVED) |
| **Critical Findings** | 1 (Issue #12 - Committed Private Keys - RESOLVED) |
| **High Findings** | 0 (Previously 2 - RESOLVED) |
| **Medium Findings** | 3 (Unchanged) |
| **Low Findings** | 4 (Unchanged) |
@ -150,6 +150,36 @@ Consider storing CA key on separate, more secure host.
---
### 🔴 CRITICAL: Committed Private Key Material (Issue #12)
**Description:**
Private key files (`*.key`, `*.key.pem`) were committed to version control in:
- `configs/certs/ca.key.pem` — CA private key
- `configs/certs/server.key.pem` — Server private key
- `configs/certs/client001.key.pem` — Client private key
- `tests/e2e/certs/client.key` — E2E test client private key
Committed private keys are a critical security risk: anyone with repository access
(even read-only) can impersonate the server or clients, decrypt captured TLS traffic,
or forge certificates signed by the CA.
**Status:** ✅ RESOLVED
**Remediation Applied:**
1. Removed all private key files from git tracking (`git rm --cached`)
2. Added `*.key`, `*.key.pem`, `configs/certs/`, and `tests/e2e/certs/*.key` to `.gitignore`
3. Created `scripts/generate-dev-certs.sh` to generate test certificates at runtime
4. Updated e2e tests to generate certificates on demand instead of loading from disk
5. Added `gitleaks` secret scanning to CI pipeline
6. Git history will be purged with `git filter-repo` after PR merge
**Key Rotation:**
These keys were used for development/testing only. No production key rotation is needed.
All committed keys should be considered compromised and must not be used in any
production environment.
---
### 🟢 LOW: No Automated Security Scanning
**Description:**

33
configs/certs/README.md Normal file
View File

@ -0,0 +1,33 @@
# Development Certificates
**⚠️ Private keys are NOT committed to version control.**
This directory is used for local development certificates only. Private key
files (`*.key`, `*.key.pem`) are excluded from git via `.gitignore`.
## Generating Development Certificates
Run the generation script from the repository root:
```bash
./scripts/generate-dev-certs.sh
```
This creates:
- `ca.pem` / `ca.key.pem` — Internal CA certificate and key
- `server.pem` / `server.key.pem` — Server certificate and key
- `client001.pem` / `client001.key.pem` — Client certificate and key
- `tests/e2e/certs/` — E2E test certificates
## Production Deployments
Production deployments should use certificates issued by the organisation's
internal CA. The `install.sh` script and systemd unit handle production
certificate paths at `/etc/linux_patch_api/certs/`.
## Security
- **Never commit private keys** (`*.key`, `*.key.pem`) to version control
- Private keys must have `0600` permissions in production
- The `gitleaks` CI check scans for accidentally committed secrets
- See `SECURITY_FINDINGS_REPORT.md` and `SECURITY.md` for full details

View File

@ -1,5 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg46Ewu04V/qVbFIaW
ll6hUNA1ocfdND68cRv6GiOBikyhRANCAARORR0UUR6G6ndxeefpKai+82eH58ud
sW5qox3Ed4I0WF12RcSwioAPrt5WNB+ptw0wvzx78wH8CdkqjyUb7Koc
-----END PRIVATE KEY-----

View File

@ -1,12 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBsTCCAVegAwIBAgIQVxQmz3/uqfSgf+8ukKa6GTAKBggqhkjOPQQDAjA4MR4w
HAYDVQQDDBVQYXRjaCBNYW5hZ2VyIFJvb3QgQ0ExFjAUBgNVBAoMDVBhdGNoIE1h
bmFnZXIwHhcNMjYwNTE4MTU1MjUxWhcNMzYwNTE1MTU1MjUxWjA4MR4wHAYDVQQD
DBVQYXRjaCBNYW5hZ2VyIFJvb3QgQ0ExFjAUBgNVBAoMDVBhdGNoIE1hbmFnZXIw
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARORR0UUR6G6ndxeefpKai+82eH58ud
sW5qox3Ed4I0WF12RcSwioAPrt5WNB+ptw0wvzx78wH8CdkqjyUb7Koco0MwQTAP
BgNVHQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBTcLRFILwfBbjqUm3fT8AzIAN5mQDAP
BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQDMHR7n6plBEz7tP9Si
Cs6Rk8m2gt9CL6qHlkeWiDJmtgIgVXrj2Lmqn1dEuKbVu9LaxPyvXU4/t2etWHgJ
lfK+SS8=
-----END CERTIFICATE-----

View File

@ -1 +0,0 @@
790CDB9FA2002BF59B3EE88AF326CB060353D113

View File

@ -1,8 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIH7MIGhAgEAMD8xHTAbBgNVBAMMFHBhdGNoLW1hbmFnZXItY2xpZW50MREwDwYD
VQQKDAhJbnRlcm5hbDELMAkGA1UEBhMCVVMwWTATBgcqhkjOPQIBBggqhkjOPQMB
BwNCAAQauACwaR4SyVoHEPviQgV0I4fbyFuGoHiQExzpYf9Ta025dy88T/a6qG6G
TYJMtbRSjP/piLWfZ/2ze2AdbmczoAAwCgYIKoZIzj0EAwIDSQAwRgIhAJ/BBYsB
aIhKjdwRr0vTqtYPKeeyO2rzHuyRnSvKKkdOAiEA94zCvG0FzkFiqGKT1oHGCVf9
qZdkjkodRAUk6/4S2AU=
-----END CERTIFICATE REQUEST-----

View File

@ -1,5 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg0iNlJbfLqO8Y5sOh
1xRe2bPq8fF9M1ybEOnqmbSGpdGhRANCAAQauACwaR4SyVoHEPviQgV0I4fbyFuG
oHiQExzpYf9Ta025dy88T/a6qG6GTYJMtbRSjP/piLWfZ/2ze2Adbmcz
-----END PRIVATE KEY-----

View File

@ -1,12 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBuzCCAWGgAwIBAgIUeQzbn6IAK/WbPuiK8ybLBgNT0RMwCgYIKoZIzj0EAwIw
ODEeMBwGA1UEAwwVUGF0Y2ggTWFuYWdlciBSb290IENBMRYwFAYDVQQKDA1QYXRj
aCBNYW5hZ2VyMB4XDTI2MDUxODE2MDAwNloXDTI3MDUxODE2MDAwNlowPzEdMBsG
A1UEAwwUcGF0Y2gtbWFuYWdlci1jbGllbnQxETAPBgNVBAoMCEludGVybmFsMQsw
CQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBq4ALBpHhLJWgcQ
++JCBXQjh9vIW4ageJATHOlh/1NrTbl3LzxP9rqoboZNgky1tFKM/+mItZ9n/bN7
YB1uZzOjQjBAMB0GA1UdDgQWBBQhTcmoHT0HqIuEUkL891TKMlWWjjAfBgNVHSME
GDAWgBTcLRFILwfBbjqUm3fT8AzIAN5mQDAKBggqhkjOPQQDAgNIADBFAiApQ6N8
qQR1vWLU3QNrcIwLxK8g2shV5ggypS/CKkfTgwIhAJdZd0silwqEpPo5ng0I5SJ9
MOd4Kx0dps2kY/wqgMSI
-----END CERTIFICATE-----

View File

@ -1,8 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIH1MIGcAgEAMDoxGDAWBgNVBAMMD2xpbnV4LXBhdGNoLWFwaTERMA8GA1UECgwI
SW50ZXJuYWwxCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
C32xU18H3OljGW+wQUesT1qSB+bp5cCkNW9rfpv7wjr79eHriZkQ8EgrdVAK9Zw0
fZJNdd4LyekDGXiQU/qAJ6AAMAoGCCqGSM49BAMCA0gAMEUCIQDf7FSy4YiZvWkj
G9BgdSPTcIq8VYSGm7nnXprD8u1ZTwIgO6/5jH72reiCaaMm62X1Vrpc+8SDMVtO
+dlP4dZ+BM8=
-----END CERTIFICATE REQUEST-----

View File

@ -1,5 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKWrGjaMdvANVPz/d
LQPtDS4FmU8H0gg8zix2AvxaQp2hRANCAAQLfbFTXwfc6WMZb7BBR6xPWpIH5unl
wKQ1b2t+m/vCOvv14euJmRDwSCt1UAr1nDR9kk113gvJ6QMZeJBT+oAn
-----END PRIVATE KEY-----

View File

@ -1,12 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBtjCCAVygAwIBAgIUeQzbn6IAK/WbPuiK8ybLBgNT0RIwCgYIKoZIzj0EAwIw
ODEeMBwGA1UEAwwVUGF0Y2ggTWFuYWdlciBSb290IENBMRYwFAYDVQQKDA1QYXRj
aCBNYW5hZ2VyMB4XDTI2MDUxODE2MDAwNloXDTI3MDUxODE2MDAwNlowOjEYMBYG
A1UEAwwPbGludXgtcGF0Y2gtYXBpMREwDwYDVQQKDAhJbnRlcm5hbDELMAkGA1UE
BhMCVVMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQLfbFTXwfc6WMZb7BBR6xP
WpIH5unlwKQ1b2t+m/vCOvv14euJmRDwSCt1UAr1nDR9kk113gvJ6QMZeJBT+oAn
o0IwQDAdBgNVHQ4EFgQUDTnKCjj1BJ0MdwJHPUGf0raJ6/kwHwYDVR0jBBgwFoAU
3C0RSC8HwW46lJt30/AMyADeZkAwCgYIKoZIzj0EAwIDSAAwRQIhAJ4jy8W2hbqK
kiTI9aYS+xwMJlxH6cFJaKplrA+a5Ay8AiANPJdJN9ucgCsq/N3Ai6kO89rcXy8Z
60kvNNc3Zg/Oog==
-----END CERTIFICATE-----

82
scripts/generate-dev-certs.sh Executable file
View File

@ -0,0 +1,82 @@
#!/usr/bin/env bash
# Generate development/test certificates for Linux Patch API.
#
# This script creates a self-signed CA, server certificate, and client
# certificate suitable for local development and testing. It is NOT
# intended for production use.
#
# Usage:
# ./scripts/generate-dev-certs.sh [OUTPUT_DIR]
#
# If OUTPUT_DIR is omitted, certificates are written to configs/certs/
# relative to the repository root. The e2e Python test certs are also
# regenerated under tests/e2e/certs/.
#
# Private keys (*.key, *.key.pem) are excluded from git via .gitignore
# and must NEVER be committed to version control.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
OUTPUT_DIR="${1:-$REPO_ROOT/configs/certs}"
E2E_DIR="$REPO_ROOT/tests/e2e/certs"
DAYS_CA=3650
DAYS_CERT=365
echo "Generating development certificates..."
echo " Output dir: $OUTPUT_DIR"
echo " E2E dir: $E2E_DIR"
mkdir -p "$OUTPUT_DIR"
mkdir -p "$E2E_DIR"
# CA
echo "[1/6] Generating CA key and certificate..."
openssl genrsa -out "$OUTPUT_DIR/ca.key.pem" 4096 2>/dev/null
chmod 600 "$OUTPUT_DIR/ca.key.pem"
openssl req -x509 -new -nodes -key "$OUTPUT_DIR/ca.key.pem" -sha256 -days "$DAYS_CA" -out "$OUTPUT_DIR/ca.pem" -subj "/CN=LinuxPatchAPI Dev CA/O=Internal/C=US"
# Server certificate
echo "[2/6] Generating server key and certificate..."
openssl genrsa -out "$OUTPUT_DIR/server.key.pem" 2048 2>/dev/null
chmod 600 "$OUTPUT_DIR/server.key.pem"
openssl req -new -key "$OUTPUT_DIR/server.key.pem" -out "$OUTPUT_DIR/server.csr.pem" -subj "/CN=localhost/O=Internal/C=US"
openssl x509 -req -in "$OUTPUT_DIR/server.csr.pem" -CA "$OUTPUT_DIR/ca.pem" -CAkey "$OUTPUT_DIR/ca.key.pem" -CAcreateserial -out "$OUTPUT_DIR/server.pem" -days "$DAYS_CERT" -sha256
# Client certificate
echo "[3/6] Generating client key and certificate..."
openssl genrsa -out "$OUTPUT_DIR/client001.key.pem" 2048 2>/dev/null
chmod 600 "$OUTPUT_DIR/client001.key.pem"
openssl req -new -key "$OUTPUT_DIR/client001.key.pem" -out "$OUTPUT_DIR/client001.csr.pem" -subj "/CN=client001/O=Internal/C=US"
openssl x509 -req -in "$OUTPUT_DIR/client001.csr.pem" -CA "$OUTPUT_DIR/ca.pem" -CAkey "$OUTPUT_DIR/ca.key.pem" -CAcreateserial -out "$OUTPUT_DIR/client001.pem" -days "$DAYS_CERT" -sha256
# E2E test certificates
echo "[4/6] Generating e2e test CA certificate..."
cp "$OUTPUT_DIR/ca.pem" "$E2E_DIR/ca.crt"
echo "[5/6] Generating e2e test client certificate..."
openssl genrsa -out "$E2E_DIR/client.key" 2048 2>/dev/null
chmod 600 "$E2E_DIR/client.key"
openssl req -new -key "$E2E_DIR/client.key" -out "$E2E_DIR/client.csr" -subj "/CN=e2e-test-client/O=Internal/C=US"
openssl x509 -req -in "$E2E_DIR/client.csr" -CA "$OUTPUT_DIR/ca.pem" -CAkey "$OUTPUT_DIR/ca.key.pem" -CAcreateserial -out "$E2E_DIR/client.crt" -days "$DAYS_CERT" -sha256
# Cleanup CSR files
echo "[6/6] Cleaning up CSR files..."
rm -f "$OUTPUT_DIR/server.csr.pem" "$OUTPUT_DIR/client001.csr.pem" "$E2E_DIR/client.csr"
echo
echo "Development certificates generated successfully."
echo " CA cert: $OUTPUT_DIR/ca.pem"
echo " Server cert: $OUTPUT_DIR/server.pem"
echo " Server key: $OUTPUT_DIR/server.key.pem"
echo " Client cert: $OUTPUT_DIR/client001.pem"
echo " Client key: $OUTPUT_DIR/client001.key.pem"
echo " E2E CA cert: $E2E_DIR/ca.crt"
echo " E2E client cert: $E2E_DIR/client.crt"
echo " E2E client key: $E2E_DIR/client.key"
echo
echo "⚠ WARNING: These are development-only certificates. Do NOT use in production."
echo "⚠ Private keys (*.key, *.key.pem) are excluded from git via .gitignore."

24
tests/e2e/certs/README.md Normal file
View File

@ -0,0 +1,24 @@
# E2E Test Certificates
**⚠️ Private keys are NOT committed to version control.**
This directory holds mTLS certificates used by the Python E2E test suite.
The `client.key` private key is excluded from git via `.gitignore`.
## Generating Test Certificates
Certificates are generated automatically by the E2E test runner, or manually:
```bash
./scripts/generate-dev-certs.sh
```
This script creates `ca.crt`, `client.crt`, and `client.key` in this directory.
## Files
| File | Committed | Description |
|------|-----------|-------------|
| `ca.crt` | ✅ Yes | CA certificate (public) |
| `client.crt` | ✅ Yes | Client certificate (public) |
| `client.key` | ❌ No | Client private key (gitignored) |

View File

@ -1,5 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg5dkQDY44tZkcnQ6M
lGDNFyFrEvcOlnDoKfA/uTvBCtehRANCAAT8X1WUWE52l/i2I3MmlSiPgrESEJ2R
I6CJvV2hHKirY+wJbanH39b1ebW8b+W3fuhEHPaFFcpPFEnPriA+xWvT
-----END PRIVATE KEY-----

View File

@ -16,6 +16,7 @@ Usage:
import argparse
import json
import subprocess
import sys
import time
from dataclasses import dataclass, field
@ -37,6 +38,19 @@ CA_CERT = CERTS_DIR / "ca.crt"
CLIENT_CERT = CERTS_DIR / "client.crt"
CLIENT_KEY = CERTS_DIR / "client.key"
def ensure_certs() -> None:
"""Generate e2e test certificates at runtime if they do not exist."""
if CLIENT_KEY.exists() and CLIENT_CERT.exists() and CA_CERT.exists():
return
script = Path(__file__).resolve().parent.parent.parent / "scripts" / "generate-dev-certs.sh"
if not script.exists():
raise FileNotFoundError(
f"Certificate generation script not found: {script}. "
"Run ./scripts/generate-dev-certs.sh manually."
)
subprocess.check_call([str(script)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
TARGETS = {
"dev": {
"name": "linux-patch-manager-dev",
@ -537,14 +551,51 @@ def test_wrong_cert_connection(client: PatchAPIClient) -> str:
"""Verify that connections with wrong cert are rejected.
Per spec: invalid/expired certificates should be silently dropped.
Uses project test certs (different CA) which should be rejected.
Generates a separate "wrong CA" certificate at runtime to test that
certificates signed by an untrusted CA are rejected.
"""
project_ca = "/a0/usr/projects/linux_patch_api/configs/certs/ca.pem"
project_cert = "/a0/usr/projects/linux_patch_api/configs/certs/client001.pem"
project_key = "/a0/usr/projects/linux_patch_api/configs/certs/client001.key.pem"
import tempfile
wrong_ca_dir = Path(tempfile.mkdtemp(prefix="lpa-wrong-ca-"))
try:
# Generate a completely separate CA + client cert (wrong CA)
wrong_ca_key = wrong_ca_dir / "ca.key"
wrong_ca_cert = wrong_ca_dir / "ca.crt"
wrong_client_key = wrong_ca_dir / "client.key"
wrong_client_csr = wrong_ca_dir / "client.csr"
wrong_client_cert = wrong_ca_dir / "client.crt"
if not Path(project_ca).exists():
return "SKIPPED: Project test certs not available"
subprocess.run(
["openssl", "genrsa", "-out", str(wrong_ca_key), "2048"],
check=True, capture_output=True,
)
subprocess.run(
["openssl", "req", "-x509", "-new", "-nodes", "-key", str(wrong_ca_key),
"-sha256", "-days", "1", "-out", str(wrong_ca_cert),
"-subj", "/CN=Wrong CA/O=Attacker/C=US"],
check=True, capture_output=True,
)
subprocess.run(
["openssl", "genrsa", "-out", str(wrong_client_key), "2048"],
check=True, capture_output=True,
)
subprocess.run(
["openssl", "req", "-new", "-key", str(wrong_client_key),
"-out", str(wrong_client_csr),
"-subj", "/CN=wrong-client/O=Attacker/C=US"],
check=True, capture_output=True,
)
subprocess.run(
["openssl", "x509", "-req", "-in", str(wrong_client_csr),
"-CA", str(wrong_ca_cert), "-CAkey", str(wrong_ca_key),
"-CAcreateserial", "-out", str(wrong_client_cert),
"-days", "1", "-sha256"],
check=True, capture_output=True,
)
project_cert = str(wrong_client_cert)
project_key = str(wrong_client_key)
except (subprocess.CalledProcessError, FileNotFoundError):
return "SKIPPED: Could not generate wrong-CA test certificates"
session = requests.Session()
session.cert = (project_cert, project_key)
@ -559,6 +610,8 @@ def test_wrong_cert_connection(client: PatchAPIClient) -> str:
return "Correctly rejected connection with untrusted client certificate"
finally:
session.close()
import shutil
shutil.rmtree(wrong_ca_dir, ignore_errors=True)
def test_job_lifecycle(client: PatchAPIClient) -> str:
@ -776,7 +829,10 @@ def main():
)
args = parser.parse_args()
# Verify certs exist
# Generate certs at runtime if missing (private keys are not committed)
ensure_certs()
# Verify certs exist after generation attempt
if not CA_CERT.exists():
print(f"ERROR: CA cert not found: {CA_CERT}")
sys.exit(1)