umbermark-cli 1.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- umbermark/__init__.py +3 -0
- umbermark/checks/__init__.py +88 -0
- umbermark/checks/auto_updates_configured.py +149 -0
- umbermark/checks/dns_resolver_sanity.py +47 -0
- umbermark/checks/fail2ban_enabled.py +67 -0
- umbermark/checks/failed_login_anomaly.py +48 -0
- umbermark/checks/failed_systemd_units.py +41 -0
- umbermark/checks/firewall_protective.py +247 -0
- umbermark/checks/journald_persistence.py +55 -0
- umbermark/checks/kernel_age.py +74 -0
- umbermark/checks/listen_vs_firewall_delta.py +136 -0
- umbermark/checks/mac_framework_enforcing.py +97 -0
- umbermark/checks/pending_security_updates.py +188 -0
- umbermark/checks/public_listen_anomalies.py +200 -0
- umbermark/checks/reboot_pending.py +71 -0
- umbermark/checks/repo_gpg_trust.py +97 -0
- umbermark/checks/root_disk_free.py +46 -0
- umbermark/checks/ssh_non_default_port.py +62 -0
- umbermark/checks/ssh_password_auth.py +71 -0
- umbermark/checks/ssh_permit_root.py +59 -0
- umbermark/checks/ssh_weak_ciphers.py +85 -0
- umbermark/checks/sudo_requires_authentication.py +41 -0
- umbermark/checks/sudoers_nopasswd.py +111 -0
- umbermark/checks/swap_pressure.py +51 -0
- umbermark/checks/time_sync.py +55 -0
- umbermark/checks/tmp_hardening.py +45 -0
- umbermark/checks/user_password_aging.py +95 -0
- umbermark/checks/world_writable_suid_audit.py +171 -0
- umbermark/cli/__init__.py +4 -0
- umbermark/cli/app.py +23 -0
- umbermark/cli/auth.py +44 -0
- umbermark/cli/commands/__init__.py +0 -0
- umbermark/cli/commands/doctor.py +16 -0
- umbermark/cli/commands/login.py +80 -0
- umbermark/cli/commands/push.py +54 -0
- umbermark/cli/commands/report.py +26 -0
- umbermark/cli/commands/scan.py +77 -0
- umbermark/cli/commands/scan_local.py +72 -0
- umbermark/cli/commands/scan_wsl.py +35 -0
- umbermark/cli/commands/version.py +11 -0
- umbermark/cli/config.py +13 -0
- umbermark/core/__init__.py +0 -0
- umbermark/core/accepted.py +61 -0
- umbermark/core/access.py +896 -0
- umbermark/core/capture.py +228 -0
- umbermark/core/check.py +82 -0
- umbermark/core/checkin.py +89 -0
- umbermark/core/crypto.py +15 -0
- umbermark/core/duration.py +45 -0
- umbermark/core/engine.py +74 -0
- umbermark/core/external/__init__.py +6 -0
- umbermark/core/external/check.py +54 -0
- umbermark/core/external/checks/__init__.py +31 -0
- umbermark/core/external/checks/exposed_datastores.py +55 -0
- umbermark/core/external/checks/http_security_headers.py +249 -0
- umbermark/core/external/checks/legacy_services.py +52 -0
- umbermark/core/external/checks/open_management_ports.py +65 -0
- umbermark/core/external/checks/ssh_alt_ports.py +77 -0
- umbermark/core/external/checks/ssh_weak_algorithms.py +93 -0
- umbermark/core/external/checks/tls_hygiene.py +107 -0
- umbermark/core/external/engine.py +89 -0
- umbermark/core/external/ownership.py +607 -0
- umbermark/core/external/probe.py +103 -0
- umbermark/core/external/socket_probe.py +400 -0
- umbermark/core/firewall.py +713 -0
- umbermark/core/fleet.py +92 -0
- umbermark/core/history.py +235 -0
- umbermark/core/html_report.py +306 -0
- umbermark/core/keyenc.py +42 -0
- umbermark/core/labels.py +29 -0
- umbermark/core/lifecycle.py +100 -0
- umbermark/core/logredact.py +44 -0
- umbermark/core/partial.py +57 -0
- umbermark/core/pdf/__init__.py +0 -0
- umbermark/core/pdf/renderer.py +26 -0
- umbermark/core/pdf/weasyprint_renderer.py +18 -0
- umbermark/core/platform.py +73 -0
- umbermark/core/redact.py +75 -0
- umbermark/core/report.py +25 -0
- umbermark/core/scoring.py +105 -0
- umbermark/core/settings.py +238 -0
- umbermark/core/short_id.py +27 -0
- umbermark/core/signing.py +24 -0
- umbermark/core/ssh.py +156 -0
- umbermark/core/ssh_fingerprint.py +16 -0
- umbermark/core/tiers.py +128 -0
- umbermark/core/webhooks.py +177 -0
- umbermark/core/wording.py +371 -0
- umbermark_cli-1.0.0rc1.dist-info/METADATA +11 -0
- umbermark_cli-1.0.0rc1.dist-info/RECORD +93 -0
- umbermark_cli-1.0.0rc1.dist-info/WHEEL +5 -0
- umbermark_cli-1.0.0rc1.dist-info/entry_points.txt +2 -0
- umbermark_cli-1.0.0rc1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Accepted risks: a finding the owner has decided to live with, with a reason and an expiry (UMBR-66).
|
|
2
|
+
|
|
3
|
+
The stored scan is never rewritten. The raw score stays what the scanner measured; the "score with accepted risks"
|
|
4
|
+
is computed at read time from the scan's own deductions, leaving out the points of every check that has an active
|
|
5
|
+
(unexpired) acceptance on that server. When an acceptance expires it simply stops applying.
|
|
6
|
+
|
|
7
|
+
Only the owner's pages show it. A public /r share link keeps the raw score and says nothing about acceptances
|
|
8
|
+
(their reasons may describe internal decisions).
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import datetime as dt
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
|
|
16
|
+
from umbermark.core.scoring import _band
|
|
17
|
+
|
|
18
|
+
CHECK_ID_RE = re.compile(r"^[a-z0-9_]{1,64}$")
|
|
19
|
+
REASON_MAX = 500
|
|
20
|
+
DURATIONS_DAYS: tuple[int, ...] = (30, 90, 180, 365) # the choices the form offers; no open-ended acceptance
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True, slots=True)
|
|
24
|
+
class Adjusted:
|
|
25
|
+
score: int
|
|
26
|
+
band: str
|
|
27
|
+
points_back: int # points the accepted checks cost in the raw score
|
|
28
|
+
accepted: tuple[str, ...] # check ids that were left out (only those the scan actually deducted for)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def expires_at(days: int, now: dt.datetime | None = None) -> str:
|
|
32
|
+
now = now or dt.datetime.now(dt.UTC)
|
|
33
|
+
return (now + dt.timedelta(days=days)).isoformat()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_active(expires_iso: str, now: dt.datetime | None = None) -> bool:
|
|
37
|
+
now = now or dt.datetime.now(dt.UTC)
|
|
38
|
+
try:
|
|
39
|
+
exp = dt.datetime.fromisoformat(expires_iso)
|
|
40
|
+
except ValueError:
|
|
41
|
+
return False # unreadable expiry: treat as expired (fail closed: the raw score applies)
|
|
42
|
+
if exp.tzinfo is None:
|
|
43
|
+
exp = exp.replace(tzinfo=dt.UTC)
|
|
44
|
+
return exp > now
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def adjust(payload: dict, accepted_ids: set[str], coverage: float) -> Adjusted | None:
|
|
48
|
+
"""The score with `accepted_ids` left out, or None when no accepted check cost points in this scan.
|
|
49
|
+
|
|
50
|
+
Rebuilt from the scan's own deduction list (100 minus every remaining deduction, clamped to 0..100), so it
|
|
51
|
+
matches the scoring engine exactly. The band follows the engine's rule, including 'incomplete' on low coverage.
|
|
52
|
+
"""
|
|
53
|
+
deductions = (payload.get("result") or {}).get("deductions") or []
|
|
54
|
+
hit = tuple(sorted({d.get("check_id", "") for d in deductions
|
|
55
|
+
if d.get("check_id") in accepted_ids and int(d.get("points_lost", 0) or 0) > 0}))
|
|
56
|
+
if not hit:
|
|
57
|
+
return None
|
|
58
|
+
kept = sum(int(d.get("points_lost", 0) or 0) for d in deductions if d.get("check_id") not in accepted_ids)
|
|
59
|
+
back = sum(int(d.get("points_lost", 0) or 0) for d in deductions if d.get("check_id") in accepted_ids)
|
|
60
|
+
score = max(0, min(100, 100 - kept))
|
|
61
|
+
return Adjusted(score=score, band=_band(score, coverage), points_back=back, accepted=hit)
|