mirror of
https://github.com/taylanbakircioglu/haproxy-openmanager.git
synced 2026-09-24 19:32:01 +00:00
07942a82e8
Issue #10 — Silent timezone failure on ACME certificate save - Normalize tz-aware expiry_date to UTC tz-naive before INSERT/UPDATE in ssl_certificates (TIMESTAMP WITHOUT TIME ZONE) — restores ACME download path Issue #11 — Duplicate _acme_challenge_backend in generated config - Generator guard: skip auto-append when backend already rendered - Agent-sync filter: _should_sync_backend() drops system-managed backends and detaches their server rows to prevent orphans - Restore filter: IGNORED_BACKENDS skips reserved names during cluster restore - Parser warning: reserved_backend_names blocks accidental manual import - Cleanup migration: removes orphan rows + cascading server entries (idempotent) Issue #12 — Validated orders required manual completion - New 60s background task complete_pending_acme_orders, flag-independent, multi-replica safe via FOR UPDATE SKIP LOCKED + 30s updated_at watermark - Per-order pg_advisory_lock(0x41434D45, order_id) serializes UI-Complete and auto-task races; idempotency guard returns existing certificate cleanly - retry_order endpoint reports in_progress: true within 30s window so the UI surfaces an info toast instead of duplicating CA requests - ACMEAutomation surfaces stuck orders (status=valid && !ssl_certificate_id) with a one-click Complete action and Cancel fallback; conditional 30s polling Other hardening - ACME state machine error_detail persisted as structured JSON across challenge, finalize, download stages for actionable post-mortems - CertificateRequest Pydantic model: domain regex + min_length/max_length and cluster_ids defaulting to all ACME-enabled clusters when "global" is selected - Renewal cluster fallback now requires acme_enabled=TRUE in addition to active - _complete_certificate preserves manual cluster assignments on renewal, surfaces cluster_errors, raises explicit error on missing private key - Audit logging covers acme_certificate_requested/revoked, ca_chain_imported, account created/deactivated/purged, order retried/cancelled - Settings UI exposes acme.staging_url_override for private test CAs (Pebble) - Schema additions: acme_challenges.attempts (default 0) and last_attempt_at, index idx_letsencrypt_orders_status_updated; all migrations idempotent Tests (41/41 passing) - test_acme_expiry_normalize, test_acme_duplicate_backend, test_acme_state_machine, test_acme_pydantic_validation, test_acme_audit_logging, test_acme_concurrency CI / packaging - docker-build.yml reads version.json and pushes additional product-version tag (e.g. 1.4.0) alongside latest and timestamp build id Closes #10 Closes #11 Closes #12
54 lines
2.2 KiB
Python
54 lines
2.2 KiB
Python
"""
|
|
Audit Tur 4/5 / Commit 8: ACME endpoints are covered by audit middleware.
|
|
"""
|
|
import pytest
|
|
import sys
|
|
import os
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from middleware.activity_logger import RESOURCE_MAPPING, SPECIAL_ACTIONS, extract_resource_info
|
|
|
|
|
|
class TestACMERouteCoverage:
|
|
def test_letsencrypt_in_resource_mapping(self):
|
|
assert '/api/letsencrypt' in RESOURCE_MAPPING
|
|
assert RESOURCE_MAPPING['/api/letsencrypt'] == 'letsencrypt_order'
|
|
|
|
def test_revoke_certificate_special_action(self):
|
|
path = '/api/letsencrypt/certificates/{cert_id}/revoke'
|
|
assert path in SPECIAL_ACTIONS
|
|
assert SPECIAL_ACTIONS[path] == 'acme_certificate_revoked'
|
|
|
|
def test_import_ca_chain_special_action(self):
|
|
assert '/api/letsencrypt/import-ca-chain' in SPECIAL_ACTIONS
|
|
|
|
def test_account_ops_special_actions(self):
|
|
assert '/api/letsencrypt/accounts' in SPECIAL_ACTIONS
|
|
assert '/api/letsencrypt/accounts/{account_id}' in SPECIAL_ACTIONS
|
|
assert '/api/letsencrypt/accounts/{account_id}/permanent' in SPECIAL_ACTIONS
|
|
|
|
|
|
class TestExtractResourceInfo:
|
|
def test_post_certificates_resolves_to_acme(self):
|
|
rt, action, _ = extract_resource_info('/api/letsencrypt/certificates', 'POST')
|
|
# Either matches SPECIAL_ACTIONS (acme_certificate_requested) or generic create.
|
|
assert rt == 'letsencrypt_order'
|
|
|
|
def test_post_revoke_resolves_to_revoked_action(self):
|
|
rt, action, rid = extract_resource_info(
|
|
'/api/letsencrypt/certificates/42/revoke', 'POST'
|
|
)
|
|
assert rt == 'letsencrypt_order'
|
|
assert action == 'acme_certificate_revoked'
|
|
assert rid == '42'
|
|
|
|
def test_get_request_returns_default_unknown(self):
|
|
# GET on /api/letsencrypt/orders is not in SPECIAL_ACTIONS, and GET is not
|
|
# in LOGGABLE_ACTIONS, so extract_resource_info() falls through to the
|
|
# default ('unknown', 'get', None). The middleware itself skips logging
|
|
# GETs; this test just ensures no crash on the codepath.
|
|
rt, action, _ = extract_resource_info('/api/letsencrypt/orders', 'GET')
|
|
assert rt == 'unknown'
|
|
assert action == 'get'
|