kekkai-cli 1.0.0__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.
- kekkai/__init__.py +7 -0
- kekkai/cli.py +1038 -0
- kekkai/config.py +403 -0
- kekkai/dojo.py +419 -0
- kekkai/dojo_import.py +213 -0
- kekkai/github/__init__.py +16 -0
- kekkai/github/commenter.py +198 -0
- kekkai/github/models.py +56 -0
- kekkai/github/sanitizer.py +112 -0
- kekkai/installer/__init__.py +39 -0
- kekkai/installer/errors.py +23 -0
- kekkai/installer/extract.py +161 -0
- kekkai/installer/manager.py +252 -0
- kekkai/installer/manifest.py +189 -0
- kekkai/installer/verify.py +86 -0
- kekkai/manifest.py +77 -0
- kekkai/output.py +218 -0
- kekkai/paths.py +46 -0
- kekkai/policy.py +326 -0
- kekkai/runner.py +70 -0
- kekkai/scanners/__init__.py +67 -0
- kekkai/scanners/backends/__init__.py +14 -0
- kekkai/scanners/backends/base.py +73 -0
- kekkai/scanners/backends/docker.py +178 -0
- kekkai/scanners/backends/native.py +240 -0
- kekkai/scanners/base.py +110 -0
- kekkai/scanners/container.py +144 -0
- kekkai/scanners/falco.py +237 -0
- kekkai/scanners/gitleaks.py +237 -0
- kekkai/scanners/semgrep.py +227 -0
- kekkai/scanners/trivy.py +246 -0
- kekkai/scanners/url_policy.py +163 -0
- kekkai/scanners/zap.py +340 -0
- kekkai/threatflow/__init__.py +94 -0
- kekkai/threatflow/artifacts.py +476 -0
- kekkai/threatflow/chunking.py +361 -0
- kekkai/threatflow/core.py +438 -0
- kekkai/threatflow/mermaid.py +374 -0
- kekkai/threatflow/model_adapter.py +491 -0
- kekkai/threatflow/prompts.py +277 -0
- kekkai/threatflow/redaction.py +228 -0
- kekkai/threatflow/sanitizer.py +643 -0
- kekkai/triage/__init__.py +33 -0
- kekkai/triage/app.py +168 -0
- kekkai/triage/audit.py +203 -0
- kekkai/triage/ignore.py +269 -0
- kekkai/triage/models.py +185 -0
- kekkai/triage/screens.py +341 -0
- kekkai/triage/widgets.py +169 -0
- kekkai_cli-1.0.0.dist-info/METADATA +135 -0
- kekkai_cli-1.0.0.dist-info/RECORD +90 -0
- kekkai_cli-1.0.0.dist-info/WHEEL +5 -0
- kekkai_cli-1.0.0.dist-info/entry_points.txt +3 -0
- kekkai_cli-1.0.0.dist-info/top_level.txt +3 -0
- kekkai_core/__init__.py +3 -0
- kekkai_core/ci/__init__.py +11 -0
- kekkai_core/ci/benchmarks.py +354 -0
- kekkai_core/ci/metadata.py +104 -0
- kekkai_core/ci/validators.py +92 -0
- kekkai_core/docker/__init__.py +17 -0
- kekkai_core/docker/metadata.py +153 -0
- kekkai_core/docker/sbom.py +173 -0
- kekkai_core/docker/security.py +158 -0
- kekkai_core/docker/signing.py +135 -0
- kekkai_core/redaction.py +84 -0
- kekkai_core/slsa/__init__.py +13 -0
- kekkai_core/slsa/verify.py +121 -0
- kekkai_core/windows/__init__.py +29 -0
- kekkai_core/windows/chocolatey.py +335 -0
- kekkai_core/windows/installer.py +256 -0
- kekkai_core/windows/scoop.py +165 -0
- kekkai_core/windows/validators.py +220 -0
- portal/__init__.py +19 -0
- portal/api.py +155 -0
- portal/auth.py +103 -0
- portal/enterprise/__init__.py +32 -0
- portal/enterprise/audit.py +435 -0
- portal/enterprise/licensing.py +342 -0
- portal/enterprise/rbac.py +276 -0
- portal/enterprise/saml.py +595 -0
- portal/ops/__init__.py +53 -0
- portal/ops/backup.py +553 -0
- portal/ops/log_shipper.py +469 -0
- portal/ops/monitoring.py +517 -0
- portal/ops/restore.py +469 -0
- portal/ops/secrets.py +408 -0
- portal/ops/upgrade.py +591 -0
- portal/tenants.py +340 -0
- portal/uploads.py +259 -0
- portal/web.py +384 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Docker image signing and verification with Cosign."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CosignError(Exception):
|
|
9
|
+
"""Raised when Cosign operations fail."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def sign_image(
|
|
13
|
+
image: str,
|
|
14
|
+
key_path: Path | None = None,
|
|
15
|
+
password: str | None = None,
|
|
16
|
+
) -> bool:
|
|
17
|
+
"""
|
|
18
|
+
Sign Docker image with Cosign.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
image: Docker image to sign (e.g., 'kademoslabs/kekkai:latest')
|
|
22
|
+
key_path: Path to Cosign private key (optional, uses keyless if None)
|
|
23
|
+
password: Password for private key (optional)
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
True if signing succeeded
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
CosignError: If signing fails
|
|
30
|
+
"""
|
|
31
|
+
cmd = ["cosign", "sign", "--yes"]
|
|
32
|
+
|
|
33
|
+
if key_path:
|
|
34
|
+
cmd.extend(["--key", str(key_path)])
|
|
35
|
+
|
|
36
|
+
cmd.append(image)
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
env = {}
|
|
40
|
+
if password:
|
|
41
|
+
env["COSIGN_PASSWORD"] = password
|
|
42
|
+
|
|
43
|
+
result = subprocess.run(
|
|
44
|
+
cmd,
|
|
45
|
+
capture_output=True,
|
|
46
|
+
text=True,
|
|
47
|
+
check=True,
|
|
48
|
+
timeout=120,
|
|
49
|
+
env={**os.environ, **env} if env else None,
|
|
50
|
+
)
|
|
51
|
+
return result.returncode == 0
|
|
52
|
+
|
|
53
|
+
except subprocess.CalledProcessError as e:
|
|
54
|
+
raise CosignError(f"Image signing failed: {e.stderr}") from e
|
|
55
|
+
except subprocess.TimeoutExpired as e:
|
|
56
|
+
raise CosignError("Image signing timed out after 120s") from e
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def verify_signature(
|
|
60
|
+
image: str,
|
|
61
|
+
key_path: Path | None = None,
|
|
62
|
+
) -> bool:
|
|
63
|
+
"""
|
|
64
|
+
Verify Docker image signature with Cosign.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
image: Docker image to verify (e.g., 'kademoslabs/kekkai:latest')
|
|
68
|
+
key_path: Path to Cosign public key (optional, uses keyless if None)
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
True if signature is valid
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
CosignError: If verification fails
|
|
75
|
+
"""
|
|
76
|
+
cmd = ["cosign", "verify"]
|
|
77
|
+
|
|
78
|
+
if key_path:
|
|
79
|
+
cmd.extend(["--key", str(key_path)])
|
|
80
|
+
|
|
81
|
+
cmd.append(image)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
result = subprocess.run(
|
|
85
|
+
cmd,
|
|
86
|
+
capture_output=True,
|
|
87
|
+
text=True,
|
|
88
|
+
check=False, # Don't raise on non-zero exit
|
|
89
|
+
timeout=120,
|
|
90
|
+
)
|
|
91
|
+
return result.returncode == 0
|
|
92
|
+
|
|
93
|
+
except subprocess.TimeoutExpired as e:
|
|
94
|
+
raise CosignError("Signature verification timed out after 120s") from e
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def generate_keypair(output_dir: Path) -> tuple[Path, Path]:
|
|
98
|
+
"""
|
|
99
|
+
Generate Cosign keypair.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
output_dir: Directory to store keys
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Tuple of (private_key_path, public_key_path)
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
CosignError: If key generation fails
|
|
109
|
+
"""
|
|
110
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
|
|
112
|
+
private_key = output_dir / "cosign.key"
|
|
113
|
+
public_key = output_dir / "cosign.pub"
|
|
114
|
+
|
|
115
|
+
cmd = ["cosign", "generate-key-pair"]
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
subprocess.run(
|
|
119
|
+
cmd,
|
|
120
|
+
capture_output=True,
|
|
121
|
+
text=True,
|
|
122
|
+
check=True,
|
|
123
|
+
cwd=str(output_dir),
|
|
124
|
+
timeout=60,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if not private_key.exists() or not public_key.exists():
|
|
128
|
+
raise CosignError("Key generation succeeded but keys not found")
|
|
129
|
+
|
|
130
|
+
return (private_key, public_key)
|
|
131
|
+
|
|
132
|
+
except subprocess.CalledProcessError as e:
|
|
133
|
+
raise CosignError(f"Key generation failed: {e.stderr}") from e
|
|
134
|
+
except subprocess.TimeoutExpired as e:
|
|
135
|
+
raise CosignError("Key generation timed out after 60s") from e
|
kekkai_core/redaction.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
# Conservative redaction: hide common token/secret patterns while preserving debugging structure.
|
|
6
|
+
_SECRET_PATTERNS: list[re.Pattern[str]] = [
|
|
7
|
+
re.compile(r"(?i)(api[_-]?key|token|secret|password)\s*[:=]\s*([^\s,;]+)"),
|
|
8
|
+
re.compile(r"(?i)\b(bearer)\s+([a-z0-9\-\._~\+\/]+=*)"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
# Extended patterns for comprehensive secret detection
|
|
12
|
+
_EXTENDED_PATTERNS: list[re.Pattern[str]] = [
|
|
13
|
+
# AWS keys
|
|
14
|
+
re.compile(r"\b(AKIA[0-9A-Z]{16})\b"),
|
|
15
|
+
re.compile(r"(?i)(aws[_-]?secret[_-]?access[_-]?key)\s*[:=]\s*([A-Za-z0-9/+=]{40})"),
|
|
16
|
+
# GitHub tokens
|
|
17
|
+
re.compile(r"\b(ghp_[A-Za-z0-9]{36})\b"),
|
|
18
|
+
re.compile(r"\b(gho_[A-Za-z0-9]{36})\b"),
|
|
19
|
+
re.compile(r"\b(github_pat_[A-Za-z0-9_]{22,})\b"),
|
|
20
|
+
# GitLab tokens
|
|
21
|
+
re.compile(r"\b(glpat-[A-Za-z0-9\-_]{20,})\b"),
|
|
22
|
+
# Slack tokens
|
|
23
|
+
re.compile(r"\b(xox[baprs]-[A-Za-z0-9\-]+)\b"),
|
|
24
|
+
# Stripe keys
|
|
25
|
+
re.compile(r"\b(sk_(?:live|test)_[A-Za-z0-9]{24,})\b"),
|
|
26
|
+
re.compile(r"\b(pk_(?:live|test)_[A-Za-z0-9]{24,})\b"),
|
|
27
|
+
# Private key headers
|
|
28
|
+
re.compile(r"-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----"),
|
|
29
|
+
# JWT tokens (simplified)
|
|
30
|
+
re.compile(r"\beyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*\b"),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def redact(text: str) -> str:
|
|
35
|
+
"""Redact likely secrets from a string (best-effort, non-destructive)."""
|
|
36
|
+
redacted = text
|
|
37
|
+
for pat in _SECRET_PATTERNS:
|
|
38
|
+
redacted = pat.sub(lambda m: f"{m.group(1)} [REDACTED]", redacted)
|
|
39
|
+
return redacted
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def redact_extended(text: str) -> str:
|
|
43
|
+
"""Redact secrets using both core and extended patterns.
|
|
44
|
+
|
|
45
|
+
Includes AWS, GitHub, GitLab, Slack, Stripe tokens, private keys, and JWTs.
|
|
46
|
+
"""
|
|
47
|
+
result = redact(text)
|
|
48
|
+
for pat in _EXTENDED_PATTERNS:
|
|
49
|
+
result = pat.sub("[REDACTED]", result)
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def detect_secrets(text: str) -> list[str]:
|
|
54
|
+
"""Detect types of secrets present in text without returning values.
|
|
55
|
+
|
|
56
|
+
Returns list of pattern names/descriptions found.
|
|
57
|
+
"""
|
|
58
|
+
found: list[str] = []
|
|
59
|
+
|
|
60
|
+
# Check core patterns
|
|
61
|
+
for pat in _SECRET_PATTERNS:
|
|
62
|
+
if pat.search(text):
|
|
63
|
+
found.append("credential_pattern")
|
|
64
|
+
|
|
65
|
+
# Check extended patterns with descriptions
|
|
66
|
+
pattern_names = [
|
|
67
|
+
("aws_key", _EXTENDED_PATTERNS[0]),
|
|
68
|
+
("aws_secret", _EXTENDED_PATTERNS[1]),
|
|
69
|
+
("github_token", _EXTENDED_PATTERNS[2]),
|
|
70
|
+
("github_oauth", _EXTENDED_PATTERNS[3]),
|
|
71
|
+
("github_pat", _EXTENDED_PATTERNS[4]),
|
|
72
|
+
("gitlab_token", _EXTENDED_PATTERNS[5]),
|
|
73
|
+
("slack_token", _EXTENDED_PATTERNS[6]),
|
|
74
|
+
("stripe_secret", _EXTENDED_PATTERNS[7]),
|
|
75
|
+
("stripe_publishable", _EXTENDED_PATTERNS[8]),
|
|
76
|
+
("private_key", _EXTENDED_PATTERNS[9]),
|
|
77
|
+
("jwt_token", _EXTENDED_PATTERNS[10]),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for name, pat in pattern_names:
|
|
81
|
+
if pat.search(text):
|
|
82
|
+
found.append(name)
|
|
83
|
+
|
|
84
|
+
return list(set(found)) # Dedupe
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""SLSA Level 3 provenance verification for kekkai releases."""
|
|
2
|
+
|
|
3
|
+
from kekkai_core.slsa.verify import (
|
|
4
|
+
AttestationError,
|
|
5
|
+
ProvenanceResult,
|
|
6
|
+
verify_provenance,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AttestationError",
|
|
11
|
+
"ProvenanceResult",
|
|
12
|
+
"verify_provenance",
|
|
13
|
+
]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""SLSA provenance verification using slsa-verifier."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AttestationError(Exception):
|
|
12
|
+
"""Raised when attestation verification fails."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ProvenanceResult:
|
|
17
|
+
"""Result of SLSA provenance verification."""
|
|
18
|
+
|
|
19
|
+
verified: bool
|
|
20
|
+
builder_id: str | None = None
|
|
21
|
+
source_repo: str | None = None
|
|
22
|
+
commit_sha: str | None = None
|
|
23
|
+
error: str | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def verify_provenance(
|
|
27
|
+
artifact_path: Path,
|
|
28
|
+
provenance_path: Path,
|
|
29
|
+
expected_repo: str = "kademoslabs/kekkai",
|
|
30
|
+
) -> ProvenanceResult:
|
|
31
|
+
"""
|
|
32
|
+
Verify SLSA provenance for an artifact.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
artifact_path: Path to the artifact to verify
|
|
36
|
+
provenance_path: Path to the provenance attestation file
|
|
37
|
+
expected_repo: Expected GitHub repository (owner/repo)
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
ProvenanceResult with verification status and metadata
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
AttestationError: If verification encounters an unrecoverable error
|
|
44
|
+
"""
|
|
45
|
+
if not artifact_path.exists():
|
|
46
|
+
return ProvenanceResult(
|
|
47
|
+
verified=False,
|
|
48
|
+
error=f"Artifact not found: {artifact_path}",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if not provenance_path.exists():
|
|
52
|
+
return ProvenanceResult(
|
|
53
|
+
verified=False,
|
|
54
|
+
error=f"Provenance file not found: {provenance_path}",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
result = subprocess.run(
|
|
59
|
+
[
|
|
60
|
+
"slsa-verifier", # nosec B607 - trusted binary
|
|
61
|
+
"verify-artifact",
|
|
62
|
+
str(artifact_path),
|
|
63
|
+
"--provenance-path",
|
|
64
|
+
str(provenance_path),
|
|
65
|
+
"--source-uri",
|
|
66
|
+
f"github.com/{expected_repo}",
|
|
67
|
+
],
|
|
68
|
+
capture_output=True,
|
|
69
|
+
text=True,
|
|
70
|
+
timeout=60,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if result.returncode != 0:
|
|
74
|
+
return ProvenanceResult(
|
|
75
|
+
verified=False,
|
|
76
|
+
error=result.stderr.strip() or "Verification failed",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Parse provenance for metadata
|
|
80
|
+
provenance_data = _parse_provenance(provenance_path)
|
|
81
|
+
|
|
82
|
+
return ProvenanceResult(
|
|
83
|
+
verified=True,
|
|
84
|
+
builder_id=provenance_data.get("builder_id"),
|
|
85
|
+
source_repo=provenance_data.get("source_repo"),
|
|
86
|
+
commit_sha=provenance_data.get("commit_sha"),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
except subprocess.TimeoutExpired:
|
|
90
|
+
return ProvenanceResult(
|
|
91
|
+
verified=False,
|
|
92
|
+
error="Verification timed out after 60s",
|
|
93
|
+
)
|
|
94
|
+
except FileNotFoundError:
|
|
95
|
+
return ProvenanceResult(
|
|
96
|
+
verified=False,
|
|
97
|
+
error="slsa-verifier not found. Install: https://github.com/slsa-framework/slsa-verifier",
|
|
98
|
+
)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
raise AttestationError(f"Unexpected error during verification: {e}") from e
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _parse_provenance(provenance_path: Path) -> dict[str, str | None]:
|
|
104
|
+
"""Extract metadata from provenance file."""
|
|
105
|
+
try:
|
|
106
|
+
data = json.loads(provenance_path.read_text())
|
|
107
|
+
|
|
108
|
+
# Handle SLSA provenance format
|
|
109
|
+
predicate = data.get("predicate", {})
|
|
110
|
+
invocation = predicate.get("invocation", {})
|
|
111
|
+
config_source = invocation.get("configSource", {})
|
|
112
|
+
|
|
113
|
+
builder = predicate.get("builder", {})
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
"builder_id": builder.get("id"),
|
|
117
|
+
"source_repo": config_source.get("uri"),
|
|
118
|
+
"commit_sha": config_source.get("digest", {}).get("sha1"),
|
|
119
|
+
}
|
|
120
|
+
except (json.JSONDecodeError, KeyError):
|
|
121
|
+
return {}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Windows distribution integration for Kekkai.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for Windows package managers:
|
|
4
|
+
- Scoop manifest generation and validation
|
|
5
|
+
- PowerShell installer script generation
|
|
6
|
+
- Windows-specific validation (Python version, PATH)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from kekkai_core.windows.installer import (
|
|
10
|
+
generate_installer_script,
|
|
11
|
+
generate_uninstaller_script,
|
|
12
|
+
)
|
|
13
|
+
from kekkai_core.windows.scoop import (
|
|
14
|
+
generate_scoop_manifest,
|
|
15
|
+
validate_scoop_manifest,
|
|
16
|
+
)
|
|
17
|
+
from kekkai_core.windows.validators import (
|
|
18
|
+
validate_python_version,
|
|
19
|
+
validate_windows_path,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"generate_scoop_manifest",
|
|
24
|
+
"validate_scoop_manifest",
|
|
25
|
+
"generate_installer_script",
|
|
26
|
+
"generate_uninstaller_script",
|
|
27
|
+
"validate_python_version",
|
|
28
|
+
"validate_windows_path",
|
|
29
|
+
]
|