From efaac33c47413fa01b912e7e0356f210f1d35dd4 Mon Sep 17 00:00:00 2001 From: Draco-Lunaris-Echo Date: Sat, 6 Jun 2026 13:20:43 -0500 Subject: [PATCH] fix: remove committed private keys and add runtime cert generation (closes #12) - 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 --- .github/workflows/ci.yml | 12 +++++ .gitignore | 7 +++ SECURITY_CONTROLS_MATRIX.md | 8 ++-- SECURITY_FINDINGS_REPORT.md | 32 ++++++++++++- configs/certs/README.md | 33 +++++++++++++ configs/certs/ca.key.pem | 5 -- configs/certs/ca.pem | 12 ----- configs/certs/ca.srl | 1 - configs/certs/client001.csr.pem | 8 ---- configs/certs/client001.key.pem | 5 -- configs/certs/client001.pem | 12 ----- configs/certs/server.csr.pem | 8 ---- configs/certs/server.key.pem | 5 -- configs/certs/server.pem | 12 ----- scripts/generate-dev-certs.sh | 82 +++++++++++++++++++++++++++++++++ tests/e2e/certs/README.md | 24 ++++++++++ tests/e2e/certs/client.key | 5 -- tests/e2e/test_e2e.py | 70 +++++++++++++++++++++++++--- 18 files changed, 256 insertions(+), 85 deletions(-) create mode 100644 configs/certs/README.md delete mode 100644 configs/certs/ca.key.pem delete mode 100644 configs/certs/ca.pem delete mode 100644 configs/certs/ca.srl delete mode 100644 configs/certs/client001.csr.pem delete mode 100644 configs/certs/client001.key.pem delete mode 100644 configs/certs/client001.pem delete mode 100644 configs/certs/server.csr.pem delete mode 100644 configs/certs/server.key.pem delete mode 100644 configs/certs/server.pem create mode 100755 scripts/generate-dev-certs.sh create mode 100644 tests/e2e/certs/README.md delete mode 100644 tests/e2e/certs/client.key diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 459bc4d..05b2d2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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] diff --git a/.gitignore b/.gitignore index 5fdb7f3..8bf7f5c 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/SECURITY_CONTROLS_MATRIX.md b/SECURITY_CONTROLS_MATRIX.md index 85cf420..13d3c3d 100644 --- a/SECURITY_CONTROLS_MATRIX.md +++ b/SECURITY_CONTROLS_MATRIX.md @@ -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 | diff --git a/SECURITY_FINDINGS_REPORT.md b/SECURITY_FINDINGS_REPORT.md index 39cd672..a0992af 100644 --- a/SECURITY_FINDINGS_REPORT.md +++ b/SECURITY_FINDINGS_REPORT.md @@ -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:** diff --git a/configs/certs/README.md b/configs/certs/README.md new file mode 100644 index 0000000..a96055a --- /dev/null +++ b/configs/certs/README.md @@ -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 diff --git a/configs/certs/ca.key.pem b/configs/certs/ca.key.pem deleted file mode 100644 index 1fdbb81..0000000 --- a/configs/certs/ca.key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg46Ewu04V/qVbFIaW -ll6hUNA1ocfdND68cRv6GiOBikyhRANCAARORR0UUR6G6ndxeefpKai+82eH58ud -sW5qox3Ed4I0WF12RcSwioAPrt5WNB+ptw0wvzx78wH8CdkqjyUb7Koc ------END PRIVATE KEY----- diff --git a/configs/certs/ca.pem b/configs/certs/ca.pem deleted file mode 100644 index 548a0a0..0000000 --- a/configs/certs/ca.pem +++ /dev/null @@ -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----- diff --git a/configs/certs/ca.srl b/configs/certs/ca.srl deleted file mode 100644 index af9819b..0000000 --- a/configs/certs/ca.srl +++ /dev/null @@ -1 +0,0 @@ -790CDB9FA2002BF59B3EE88AF326CB060353D113 diff --git a/configs/certs/client001.csr.pem b/configs/certs/client001.csr.pem deleted file mode 100644 index 86ef440..0000000 --- a/configs/certs/client001.csr.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIH7MIGhAgEAMD8xHTAbBgNVBAMMFHBhdGNoLW1hbmFnZXItY2xpZW50MREwDwYD -VQQKDAhJbnRlcm5hbDELMAkGA1UEBhMCVVMwWTATBgcqhkjOPQIBBggqhkjOPQMB -BwNCAAQauACwaR4SyVoHEPviQgV0I4fbyFuGoHiQExzpYf9Ta025dy88T/a6qG6G -TYJMtbRSjP/piLWfZ/2ze2AdbmczoAAwCgYIKoZIzj0EAwIDSQAwRgIhAJ/BBYsB -aIhKjdwRr0vTqtYPKeeyO2rzHuyRnSvKKkdOAiEA94zCvG0FzkFiqGKT1oHGCVf9 -qZdkjkodRAUk6/4S2AU= ------END CERTIFICATE REQUEST----- diff --git a/configs/certs/client001.key.pem b/configs/certs/client001.key.pem deleted file mode 100644 index 8df25c5..0000000 --- a/configs/certs/client001.key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg0iNlJbfLqO8Y5sOh -1xRe2bPq8fF9M1ybEOnqmbSGpdGhRANCAAQauACwaR4SyVoHEPviQgV0I4fbyFuG -oHiQExzpYf9Ta025dy88T/a6qG6GTYJMtbRSjP/piLWfZ/2ze2Adbmcz ------END PRIVATE KEY----- diff --git a/configs/certs/client001.pem b/configs/certs/client001.pem deleted file mode 100644 index 1d18b6b..0000000 --- a/configs/certs/client001.pem +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBuzCCAWGgAwIBAgIUeQzbn6IAK/WbPuiK8ybLBgNT0RMwCgYIKoZIzj0EAwIw -ODEeMBwGA1UEAwwVUGF0Y2ggTWFuYWdlciBSb290IENBMRYwFAYDVQQKDA1QYXRj -aCBNYW5hZ2VyMB4XDTI2MDUxODE2MDAwNloXDTI3MDUxODE2MDAwNlowPzEdMBsG -A1UEAwwUcGF0Y2gtbWFuYWdlci1jbGllbnQxETAPBgNVBAoMCEludGVybmFsMQsw -CQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBq4ALBpHhLJWgcQ -++JCBXQjh9vIW4ageJATHOlh/1NrTbl3LzxP9rqoboZNgky1tFKM/+mItZ9n/bN7 -YB1uZzOjQjBAMB0GA1UdDgQWBBQhTcmoHT0HqIuEUkL891TKMlWWjjAfBgNVHSME -GDAWgBTcLRFILwfBbjqUm3fT8AzIAN5mQDAKBggqhkjOPQQDAgNIADBFAiApQ6N8 -qQR1vWLU3QNrcIwLxK8g2shV5ggypS/CKkfTgwIhAJdZd0silwqEpPo5ng0I5SJ9 -MOd4Kx0dps2kY/wqgMSI ------END CERTIFICATE----- diff --git a/configs/certs/server.csr.pem b/configs/certs/server.csr.pem deleted file mode 100644 index 0b561d2..0000000 --- a/configs/certs/server.csr.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIH1MIGcAgEAMDoxGDAWBgNVBAMMD2xpbnV4LXBhdGNoLWFwaTERMA8GA1UECgwI -SW50ZXJuYWwxCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE -C32xU18H3OljGW+wQUesT1qSB+bp5cCkNW9rfpv7wjr79eHriZkQ8EgrdVAK9Zw0 -fZJNdd4LyekDGXiQU/qAJ6AAMAoGCCqGSM49BAMCA0gAMEUCIQDf7FSy4YiZvWkj -G9BgdSPTcIq8VYSGm7nnXprD8u1ZTwIgO6/5jH72reiCaaMm62X1Vrpc+8SDMVtO -+dlP4dZ+BM8= ------END CERTIFICATE REQUEST----- diff --git a/configs/certs/server.key.pem b/configs/certs/server.key.pem deleted file mode 100644 index db2e750..0000000 --- a/configs/certs/server.key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKWrGjaMdvANVPz/d -LQPtDS4FmU8H0gg8zix2AvxaQp2hRANCAAQLfbFTXwfc6WMZb7BBR6xPWpIH5unl -wKQ1b2t+m/vCOvv14euJmRDwSCt1UAr1nDR9kk113gvJ6QMZeJBT+oAn ------END PRIVATE KEY----- diff --git a/configs/certs/server.pem b/configs/certs/server.pem deleted file mode 100644 index 66f2bfe..0000000 --- a/configs/certs/server.pem +++ /dev/null @@ -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----- diff --git a/scripts/generate-dev-certs.sh b/scripts/generate-dev-certs.sh new file mode 100755 index 0000000..c542fac --- /dev/null +++ b/scripts/generate-dev-certs.sh @@ -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." diff --git a/tests/e2e/certs/README.md b/tests/e2e/certs/README.md new file mode 100644 index 0000000..e9d8f1d --- /dev/null +++ b/tests/e2e/certs/README.md @@ -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) | diff --git a/tests/e2e/certs/client.key b/tests/e2e/certs/client.key deleted file mode 100644 index a061a3b..0000000 --- a/tests/e2e/certs/client.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg5dkQDY44tZkcnQ6M -lGDNFyFrEvcOlnDoKfA/uTvBCtehRANCAAT8X1WUWE52l/i2I3MmlSiPgrESEJ2R -I6CJvV2hHKirY+wJbanH39b1ebW8b+W3fuhEHPaFFcpPFEnPriA+xWvT ------END PRIVATE KEY----- diff --git a/tests/e2e/test_e2e.py b/tests/e2e/test_e2e.py index b7d0c4e..55b5288 100644 --- a/tests/e2e/test_e2e.py +++ b/tests/e2e/test_e2e.py @@ -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)