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

@ -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)