"""Native finding producers + posture wiring (PR-B). Covers: MFA / secret / cert / PAT producers (violation criteria + severity + active-scope), and the key integration — a binary control's posture pass/fail derives from open findings and honors an accept_risk decision. """ from __future__ import annotations import uuid from datetime import UTC, datetime import pytest from sqlalchemy import select from app.domains.auditor import compliance_service as compsvc from app.domains.auditor import constants as const from app.domains.auditor import findings_service as fs from app.domains.auditor.models import ( AuditorCertExpiry, AuditorFinding, AuditorMfaStatus, AuditorPatAudit, AuditorSecretRotationRecord, ComplianceControl, ComplianceControlStatus, ) from app.identity.person_models import IdentityPerson from app.scheduled_jobs.models import ScheduledJob def _naive_now() -> datetime: return datetime.now(UTC).replace(tzinfo=None) def _person(active=True, **kw) -> IdentityPerson: return IdentityPerson( id=uuid.uuid4(), source_system="successfactors", hris_person_id=f"sf-{uuid.uuid4().hex[:8]}", work_email=kw.get("email", f"u-{uuid.uuid4().hex[:6]}@x.com"), display_name=kw.get("name", "Person"), employment_status="active" if active else "left", ) # ── MFA producer ──────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_mfa_producer_scope_and_severity(db_session): db = db_session active = _person(active=True, name="Active") inactive = _person(active=False, name="Leaver") db.add_all([active, inactive]) await db.flush() # ``enrollment_observed`` carries what MfaSyncService really writes: the # four rows below are genuine per-user readings; the fifth is the shape # that broke this producer in production. db.add_all([ # active, not enrolled, enforced → high AuditorMfaStatus(person_id=active.id, provider="google", is_enrolled=False, is_enforced=True, enrollment_observed=True, enforcement_observed=True, last_synced_at=_naive_now()), # active, not enrolled, not enforced → medium AuditorMfaStatus(person_id=active.id, provider="github", is_enrolled=False, is_enforced=False, enrollment_observed=True, enforcement_observed=False, last_synced_at=_naive_now()), # active, enrolled → no finding AuditorMfaStatus(person_id=active.id, provider="feishu", is_enrolled=True, is_enforced=True, enrollment_observed=True, enforcement_observed=False, last_synced_at=_naive_now()), # leaver, not enrolled → excluded (inactive) AuditorMfaStatus(person_id=inactive.id, provider="google", is_enrolled=False, is_enforced=True, enrollment_observed=True, enforcement_observed=True, last_synced_at=_naive_now()), # active, unenrolled, but the provider never answered — the entire # Atlassian population in prod (``policy_status='policy_absent'``). # Must NOT become a worklist item: nobody can enrol a user into a # policy Helix cannot read, and 412 such items shipped. AuditorMfaStatus(person_id=active.id, provider="atlassian", is_enrolled=False, is_enforced=False, enrollment_observed=False, enforcement_observed=False, last_synced_at=_naive_now()), ]) await db.flush() await fs.reconcile_mfa_findings(db) await db.flush() rows = (await db.execute( select(AuditorFinding).where( AuditorFinding.control_key == "mfa_coverage") )).scalars().all() by_provider = {r.detail["provider"]: r for r in rows} assert set(by_provider) == {"google", "github"} # enrolled + leaver excluded assert by_provider["google"].severity == const.FINDING_SEVERITY_HIGH assert by_provider["github"].severity == const.FINDING_SEVERITY_MEDIUM assert all(r.person_id == active.id for r in rows) @pytest.mark.asyncio async def test_mfa_dashboard_counts_exclude_leavers(db_session): """Regression (2026-06-16): DashboardService.summary's mfa_total / mfa_unenrolled must scope to ACTIVE, non-deleted employees — matching the worklist producer above. A leaver's stale unenrolled MFA row used to be counted, dragging mfa_coverage below the 95% SOC2 threshold and flipping the control to FAIL on out-of-scope people. Extended 2026-08-18 with the second exclusion of the same shape: a row whose provider never reported an enrolment state at all. """ from app.domains.auditor.services import DashboardService db = db_session active = _person(active=True, name="Active") leaver = _person(active=False, name="Leaver") db.add_all([active, leaver]) await db.flush() db.add_all([ # active + enrolled → counted, not unenrolled AuditorMfaStatus(person_id=active.id, provider="google", is_enrolled=True, is_enforced=True, enrollment_observed=True, enforcement_observed=True, last_synced_at=_naive_now()), # leaver + unenrolled → must NOT be counted at all AuditorMfaStatus(person_id=leaver.id, provider="google", is_enrolled=False, is_enforced=True, enrollment_observed=True, enforcement_observed=True, last_synced_at=_naive_now()), # active but unobservable → out of the population for the same reason # leavers are (2026-08-18): this count and the worklist above must # agree, and neither can act on a fact no provider ever reported. AuditorMfaStatus(person_id=active.id, provider="atlassian", is_enrolled=False, is_enforced=False, enrollment_observed=False, enforcement_observed=False, last_synced_at=_naive_now()), ]) await db.flush() summary = await DashboardService.summary(db) # Only the active employee's row is in the population. assert summary["mfa_total_count"] == 1 assert summary["mfa_unenrolled_count"] == 0 # ── Secret producer ───────────────────────────────────────────────────── @pytest.mark.asyncio async def test_secret_producer_overdue_only(db_session): db = db_session db.add_all([ AuditorSecretRotationRecord( secret_kind="api_key", source="aws", resource_id="AKIA-overdue", status=const.SECRET_STATUS_OVERDUE, last_synced_at=_naive_now()), AuditorSecretRotationRecord( secret_kind="api_key", source="aws", resource_id="AKIA-current", status=const.SECRET_STATUS_CURRENT, last_synced_at=_naive_now()), ]) await db.flush() await fs.reconcile_secret_findings(db) await db.flush() rows = (await db.execute( select(AuditorFinding).where( AuditorFinding.control_key == "secret_rotation") )).scalars().all() assert len(rows) == 1 assert rows[0].subject_ref == "AKIA-overdue" assert rows[0].severity == const.FINDING_SEVERITY_HIGH # ── Cert producer ─────────────────────────────────────────────────────── def _cert(alert, **kw) -> AuditorCertExpiry: return AuditorCertExpiry( asset_kind="ssl_cert", source="aws_acm", external_id=f"arn-{alert}", name=f"cert-{alert}", expires_at=_naive_now(), alert_level=alert, ) @pytest.mark.asyncio async def test_cert_producer_severity_band(db_session): db = db_session db.add_all([ _cert(const.CERT_ALERT_EXPIRED), _cert(const.CERT_ALERT_30D), _cert(const.CERT_ALERT_60D), _cert(const.CERT_ALERT_90D), _cert(const.CERT_ALERT_NONE), ]) await db.flush() await fs.reconcile_cert_findings(db) await db.flush() rows = (await db.execute( select(AuditorFinding).where( AuditorFinding.control_key == "cert_expiry_monitoring") )).scalars().all() sev = {r.subject_ref: r.severity for r in rows} assert sev == { "arn-expired": const.FINDING_SEVERITY_CRITICAL, "arn-30d": const.FINDING_SEVERITY_HIGH, "arn-60d": const.FINDING_SEVERITY_MEDIUM, } # 90d + none excluded # ── PAT producer ──────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_pat_producer_risk_and_severity(db_session): db = db_session db.add_all([ AuditorPatAudit(kind="github_pat", source="github", token_external_id="t-owner-left", status="active", risk_flags=[const.PAT_RISK_OWNER_LEFT], last_synced_at=_naive_now()), AuditorPatAudit(kind="github_pat", source="github", token_external_id="t-noexpiry", status="active", risk_flags=[const.PAT_RISK_NO_EXPIRY], last_synced_at=_naive_now()), AuditorPatAudit(kind="github_pat", source="github", token_external_id="t-clean", status="active", risk_flags=None, last_synced_at=_naive_now()), ]) await db.flush() await fs.reconcile_pat_findings(db) await db.flush() rows = (await db.execute( select(AuditorFinding).where(AuditorFinding.control_key == "pat_audit") )).scalars().all() sev = {r.subject_ref: r.severity for r in rows} assert sev == { "t-owner-left": const.FINDING_SEVERITY_HIGH, "t-noexpiry": const.FINDING_SEVERITY_MEDIUM, } # clean token excluded # ── Posture wiring honors decisions ───────────────────────────────────── async def _exposure_control_status(db) -> str: await compsvc.recompute_control_status(db) ctrl = (await db.execute( select(ComplianceControl).where( ComplianceControl.key == "public_exposure_scan") )).scalar_one() st = (await db.execute( select(ComplianceControlStatus).where( ComplianceControlStatus.control_id == ctrl.id) )).scalar_one() return st.status @pytest.mark.asyncio async def test_posture_fail_then_pass_on_accept_risk(db_session): db = db_session actor = _person() db.add(actor) # Fresh feeding job so the freshness gate passes and the metric is read. db.add(ScheduledJob( id=uuid.uuid4(), name="pubex", display_name="pubex", handler_key="auditor.public_exposure_scan_daily", trigger_type="interval", enabled=True, last_run_at=_naive_now(), )) # One open exposure finding → control should fail. finding = AuditorFinding( control_key="public_exposure_scan", finding_type=const.FINDING_TYPE_PUBLIC_EXPOSURE, dedup_key="open-sg", subject_type=const.FINDING_SUBJECT_RESOURCE, severity=const.FINDING_SEVERITY_HIGH, title="open sg", status=const.FINDING_STATUS_OPEN, first_seen_at=_naive_now(), last_seen_at=_naive_now(), ) db.add(finding) await db.flush() assert await _exposure_control_status(db) == compsvc.cat.STATUS_FAIL # Operator accepts the risk → control passes (decision honored). await fs.decide_finding( db, finding_id=finding.id, decision=const.FINDING_DECISION_ACCEPT_RISK, note="internal VPC, accepted", accepted_days=None, actor_user_id=actor.id, ) await db.flush() assert await _exposure_control_status(db) == compsvc.cat.STATUS_PASS