coreason-manifest 0.6.0__py3-none-any.whl → 0.7.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.
- coreason_manifest/__init__.py +11 -73
- coreason_manifest/definitions/__init__.py +0 -0
- coreason_manifest/definitions/audit.py +7 -0
- coreason_manifest/definitions/simulation.py +19 -0
- coreason_manifest/definitions/topology.py +140 -0
- coreason_manifest/recipes.py +3 -126
- {coreason_manifest-0.6.0.dist-info → coreason_manifest-0.7.0.dist-info}/METADATA +1 -7
- coreason_manifest-0.7.0.dist-info/RECORD +16 -0
- coreason_manifest/engine.py +0 -222
- coreason_manifest/errors.py +0 -53
- coreason_manifest/integrity.py +0 -141
- coreason_manifest/loader.py +0 -271
- coreason_manifest/main.py +0 -17
- coreason_manifest/policies/compliance.rego +0 -81
- coreason_manifest/policies/tbom.json +0 -14
- coreason_manifest/policy.py +0 -138
- coreason_manifest/server.py +0 -123
- coreason_manifest/validator.py +0 -67
- coreason_manifest-0.6.0.dist-info/RECORD +0 -22
- /coreason_manifest/{models.py → definitions/agent.py} +0 -0
- {coreason_manifest-0.6.0.dist-info → coreason_manifest-0.7.0.dist-info}/WHEEL +0 -0
- {coreason_manifest-0.6.0.dist-info → coreason_manifest-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {coreason_manifest-0.6.0.dist-info → coreason_manifest-0.7.0.dist-info}/licenses/NOTICE +0 -0
coreason_manifest/policy.py
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
# Prosperity-3.0
|
|
2
|
-
"""Policy enforcement functionality using Open Policy Agent (OPA).
|
|
3
|
-
|
|
4
|
-
This module provides the `PolicyEnforcer` class, which interacts with OPA to
|
|
5
|
-
evaluate agent definitions against compliance policies defined in Rego.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
import shutil
|
|
12
|
-
import subprocess
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from typing import Any, List, Optional
|
|
15
|
-
|
|
16
|
-
from coreason_manifest.errors import PolicyViolationError
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class PolicyEnforcer:
|
|
20
|
-
"""Component C: PolicyEnforcer (The Compliance Officer).
|
|
21
|
-
|
|
22
|
-
Responsibility:
|
|
23
|
-
- Evaluate the agent against the compliance.rego policy file using OPA.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def __init__(
|
|
27
|
-
self,
|
|
28
|
-
policy_path: str | Path,
|
|
29
|
-
opa_path: str = "opa",
|
|
30
|
-
data_paths: Optional[List[str | Path]] = None,
|
|
31
|
-
) -> None:
|
|
32
|
-
"""Initialize the PolicyEnforcer.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
policy_path: Path to the Rego policy file.
|
|
36
|
-
opa_path: Path to the OPA executable. Defaults to "opa" (expected in PATH).
|
|
37
|
-
data_paths: List of paths to data files (e.g. JSON/YAML) to be loaded by OPA.
|
|
38
|
-
|
|
39
|
-
Raises:
|
|
40
|
-
FileNotFoundError: If OPA, policy file, or data files are not found.
|
|
41
|
-
"""
|
|
42
|
-
self.policy_path = Path(policy_path)
|
|
43
|
-
self.data_paths = [Path(p) for p in data_paths] if data_paths else []
|
|
44
|
-
|
|
45
|
-
# Validate OPA executable
|
|
46
|
-
# If opa_path is a simple name (like "opa"), use shutil.which to find it
|
|
47
|
-
if "/" not in str(opa_path) and "\\" not in str(opa_path):
|
|
48
|
-
resolved_opa: Optional[str] = shutil.which(opa_path)
|
|
49
|
-
if not resolved_opa:
|
|
50
|
-
raise FileNotFoundError(f"OPA executable not found in PATH: {opa_path}")
|
|
51
|
-
self.opa_path: str = resolved_opa
|
|
52
|
-
else:
|
|
53
|
-
# If it's a path, check existence
|
|
54
|
-
if not Path(opa_path).exists():
|
|
55
|
-
raise FileNotFoundError(f"OPA executable not found at: {opa_path}")
|
|
56
|
-
self.opa_path = str(opa_path)
|
|
57
|
-
|
|
58
|
-
if not self.policy_path.exists():
|
|
59
|
-
raise FileNotFoundError(f"Policy file not found: {self.policy_path}")
|
|
60
|
-
|
|
61
|
-
for path in self.data_paths:
|
|
62
|
-
if not path.exists():
|
|
63
|
-
raise FileNotFoundError(f"Data file not found: {path}")
|
|
64
|
-
|
|
65
|
-
def evaluate(self, agent_data: dict[str, Any]) -> None:
|
|
66
|
-
"""Evaluates the agent data against the policy.
|
|
67
|
-
|
|
68
|
-
Args:
|
|
69
|
-
agent_data: The dictionary representation of the AgentDefinition.
|
|
70
|
-
|
|
71
|
-
Raises:
|
|
72
|
-
PolicyViolationError: If there are any policy violations.
|
|
73
|
-
RuntimeError: If OPA execution fails.
|
|
74
|
-
"""
|
|
75
|
-
# Prepare input for OPA
|
|
76
|
-
# We invoke OPA via subprocess: opa eval -d <policy> -d <data> ... -I <input> "data.coreason.compliance.deny"
|
|
77
|
-
# We pass input via stdin to avoid temp files
|
|
78
|
-
|
|
79
|
-
try:
|
|
80
|
-
# We use 'data.coreason.compliance.deny' as the query
|
|
81
|
-
query = "data.coreason.compliance.deny"
|
|
82
|
-
|
|
83
|
-
# Serialize input to JSON
|
|
84
|
-
input_json = json.dumps(agent_data)
|
|
85
|
-
|
|
86
|
-
cmd = [
|
|
87
|
-
self.opa_path,
|
|
88
|
-
"eval",
|
|
89
|
-
"-d",
|
|
90
|
-
str(self.policy_path),
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
# Add data paths
|
|
94
|
-
for data_path in self.data_paths:
|
|
95
|
-
cmd.extend(["-d", str(data_path)])
|
|
96
|
-
|
|
97
|
-
cmd.extend(
|
|
98
|
-
[
|
|
99
|
-
"-I", # Read input from stdin
|
|
100
|
-
query,
|
|
101
|
-
"--format",
|
|
102
|
-
"json",
|
|
103
|
-
]
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
process = subprocess.run(
|
|
107
|
-
cmd,
|
|
108
|
-
input=input_json,
|
|
109
|
-
capture_output=True,
|
|
110
|
-
text=True,
|
|
111
|
-
check=False, # We handle return code manually
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
if process.returncode != 0:
|
|
115
|
-
# Include stdout in error message if stderr is empty or insufficient
|
|
116
|
-
error_msg = process.stderr.strip()
|
|
117
|
-
if not error_msg:
|
|
118
|
-
error_msg = process.stdout.strip() or "Unknown error (empty stdout/stderr)"
|
|
119
|
-
raise RuntimeError(f"OPA execution failed: {error_msg}")
|
|
120
|
-
|
|
121
|
-
# Parse OPA output
|
|
122
|
-
# Format: {"result": [{"expressions": [{"value": ["violation1", "violation2"]}]}]}
|
|
123
|
-
result = json.loads(process.stdout)
|
|
124
|
-
|
|
125
|
-
violations: List[str] = []
|
|
126
|
-
if "result" in result and len(result["result"]) > 0:
|
|
127
|
-
# Assuming the query returns a set/list of strings
|
|
128
|
-
expressions = result["result"][0].get("expressions", [])
|
|
129
|
-
if expressions:
|
|
130
|
-
violations = expressions[0].get("value", [])
|
|
131
|
-
|
|
132
|
-
if violations:
|
|
133
|
-
raise PolicyViolationError("Policy violations found.", violations=violations)
|
|
134
|
-
|
|
135
|
-
except FileNotFoundError as e:
|
|
136
|
-
raise RuntimeError(f"OPA executable not found at: {self.opa_path}") from e
|
|
137
|
-
except json.JSONDecodeError as e:
|
|
138
|
-
raise RuntimeError(f"Failed to parse OPA output: {e}") from e
|
coreason_manifest/server.py
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
from contextlib import asynccontextmanager
|
|
2
|
-
from importlib import resources
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import AsyncIterator, List, Optional, Union
|
|
5
|
-
|
|
6
|
-
from fastapi import FastAPI, HTTPException, Request, status
|
|
7
|
-
from fastapi.responses import JSONResponse
|
|
8
|
-
from pydantic import BaseModel, Field
|
|
9
|
-
|
|
10
|
-
from coreason_manifest.engine import ManifestConfig, ManifestEngineAsync
|
|
11
|
-
from coreason_manifest.errors import ManifestSyntaxError, PolicyViolationError
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# Response Model
|
|
15
|
-
class ValidationResponse(BaseModel):
|
|
16
|
-
valid: bool
|
|
17
|
-
agent_id: Optional[str] = None
|
|
18
|
-
version: Optional[str] = None
|
|
19
|
-
policy_violations: List[str] = Field(default_factory=list)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@asynccontextmanager
|
|
23
|
-
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
24
|
-
# Locate policies
|
|
25
|
-
policy_path: Optional[Path] = None
|
|
26
|
-
tbom_path: Optional[Path] = None
|
|
27
|
-
|
|
28
|
-
# 1. Check local directory (Docker runtime with COPY or relative dev path)
|
|
29
|
-
# In Docker we will COPY to /app/policies/ or ./policies/ relative to WORKDIR
|
|
30
|
-
# We'll check common locations.
|
|
31
|
-
possible_dirs = [
|
|
32
|
-
Path("policies"),
|
|
33
|
-
Path("/app/policies"),
|
|
34
|
-
Path("src/coreason_manifest/policies"), # Dev from root
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
for d in possible_dirs:
|
|
38
|
-
if (d / "compliance.rego").exists():
|
|
39
|
-
policy_path = d / "compliance.rego"
|
|
40
|
-
if (d / "tbom.json").exists():
|
|
41
|
-
tbom_path = d / "tbom.json"
|
|
42
|
-
break
|
|
43
|
-
|
|
44
|
-
# 2. Fallback to package resources
|
|
45
|
-
resource_context = None
|
|
46
|
-
if not policy_path:
|
|
47
|
-
try:
|
|
48
|
-
# Check if it exists as a resource
|
|
49
|
-
ref = resources.files("coreason_manifest.policies").joinpath("compliance.rego")
|
|
50
|
-
if ref.is_file():
|
|
51
|
-
resource_context = resources.as_file(ref)
|
|
52
|
-
policy_path = resource_context.__enter__()
|
|
53
|
-
# Check for TBOM in same dir if possible, or ignore for fallback
|
|
54
|
-
except Exception:
|
|
55
|
-
pass
|
|
56
|
-
|
|
57
|
-
# If still not found, fail.
|
|
58
|
-
if not policy_path:
|
|
59
|
-
raise RuntimeError("Could not locate compliance.rego policy file.")
|
|
60
|
-
|
|
61
|
-
config = ManifestConfig(
|
|
62
|
-
policy_path=policy_path,
|
|
63
|
-
tbom_path=tbom_path,
|
|
64
|
-
opa_path="opa", # Assumes OPA is in PATH (installed via Dockerfile)
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
engine = ManifestEngineAsync(config)
|
|
68
|
-
|
|
69
|
-
try:
|
|
70
|
-
async with engine:
|
|
71
|
-
app.state.engine = engine
|
|
72
|
-
yield
|
|
73
|
-
finally:
|
|
74
|
-
if resource_context:
|
|
75
|
-
resource_context.__exit__(None, None, None)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
app = FastAPI(lifespan=lifespan)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
@app.post("/validate", response_model=ValidationResponse) # type: ignore[misc]
|
|
82
|
-
async def validate_manifest(request: Request) -> Union[ValidationResponse, JSONResponse]:
|
|
83
|
-
engine: ManifestEngineAsync = app.state.engine
|
|
84
|
-
|
|
85
|
-
try:
|
|
86
|
-
raw_body = await request.json()
|
|
87
|
-
except Exception:
|
|
88
|
-
raise HTTPException(status_code=400, detail="Invalid JSON body") from None
|
|
89
|
-
|
|
90
|
-
try:
|
|
91
|
-
agent_def = await engine.validate_manifest_dict(raw_body)
|
|
92
|
-
return ValidationResponse(
|
|
93
|
-
valid=True,
|
|
94
|
-
agent_id=str(agent_def.metadata.id),
|
|
95
|
-
version=agent_def.metadata.version,
|
|
96
|
-
policy_violations=[],
|
|
97
|
-
)
|
|
98
|
-
except ManifestSyntaxError as e:
|
|
99
|
-
# Return 422 with the error
|
|
100
|
-
resp = ValidationResponse(valid=False, policy_violations=[f"Syntax Error: {str(e)}"])
|
|
101
|
-
return JSONResponse(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, content=resp.model_dump())
|
|
102
|
-
except PolicyViolationError as e:
|
|
103
|
-
resp = ValidationResponse(valid=False, policy_violations=e.violations)
|
|
104
|
-
return JSONResponse(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, content=resp.model_dump())
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
@app.get("/health") # type: ignore[misc]
|
|
108
|
-
async def health_check() -> dict[str, str]:
|
|
109
|
-
engine: ManifestEngineAsync = app.state.engine
|
|
110
|
-
policy_version = "unknown"
|
|
111
|
-
try:
|
|
112
|
-
import hashlib
|
|
113
|
-
|
|
114
|
-
# policy_path is guaranteed to exist by lifespan check
|
|
115
|
-
policy_path = Path(engine.config.policy_path)
|
|
116
|
-
if policy_path.exists():
|
|
117
|
-
with open(policy_path, "rb") as f:
|
|
118
|
-
digest = hashlib.sha256(f.read()).hexdigest()[:8]
|
|
119
|
-
policy_version = digest
|
|
120
|
-
except Exception:
|
|
121
|
-
pass
|
|
122
|
-
|
|
123
|
-
return {"status": "active", "policy_version": policy_version}
|
coreason_manifest/validator.py
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# Prosperity-3.0
|
|
2
|
-
"""Schema validation functionality.
|
|
3
|
-
|
|
4
|
-
This module provides the `SchemaValidator` class, which uses JSON Schema to
|
|
5
|
-
validate the structure and types of agent definitions.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
from importlib.resources import files
|
|
12
|
-
from typing import Any
|
|
13
|
-
|
|
14
|
-
from jsonschema import FormatChecker, ValidationError, validate
|
|
15
|
-
|
|
16
|
-
from coreason_manifest.errors import ManifestSyntaxError
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class SchemaValidator:
|
|
20
|
-
"""Component B: SchemaValidator (The Structural Engineer).
|
|
21
|
-
|
|
22
|
-
Responsibility:
|
|
23
|
-
- Validate the dictionary against the Master JSON Schema.
|
|
24
|
-
- Check required fields, data types, and format constraints.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
def __init__(self) -> None:
|
|
28
|
-
"""Initialize the validator by loading the schema."""
|
|
29
|
-
self.schema = self._load_schema()
|
|
30
|
-
|
|
31
|
-
def _load_schema(self) -> dict[str, Any]:
|
|
32
|
-
"""Loads the JSON schema from the package resources.
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
The JSON schema dictionary.
|
|
36
|
-
|
|
37
|
-
Raises:
|
|
38
|
-
ManifestSyntaxError: If the schema file cannot be loaded or is invalid.
|
|
39
|
-
"""
|
|
40
|
-
try:
|
|
41
|
-
schema_path = files("coreason_manifest.schemas").joinpath("agent.schema.json")
|
|
42
|
-
with schema_path.open("r", encoding="utf-8") as f:
|
|
43
|
-
schema = json.load(f)
|
|
44
|
-
if not isinstance(schema, dict):
|
|
45
|
-
raise ManifestSyntaxError("Schema file is not a valid JSON object.")
|
|
46
|
-
return schema
|
|
47
|
-
except (IOError, json.JSONDecodeError) as e:
|
|
48
|
-
raise ManifestSyntaxError(f"Failed to load agent schema: {e}") from e
|
|
49
|
-
|
|
50
|
-
def validate(self, data: dict[str, Any]) -> bool:
|
|
51
|
-
"""Validates the given dictionary against the agent schema.
|
|
52
|
-
|
|
53
|
-
Args:
|
|
54
|
-
data: The raw dictionary to validate.
|
|
55
|
-
|
|
56
|
-
Returns:
|
|
57
|
-
True if validation passes.
|
|
58
|
-
|
|
59
|
-
Raises:
|
|
60
|
-
ManifestSyntaxError: If validation fails.
|
|
61
|
-
"""
|
|
62
|
-
try:
|
|
63
|
-
validate(instance=data, schema=self.schema, format_checker=FormatChecker())
|
|
64
|
-
return True
|
|
65
|
-
except ValidationError as e:
|
|
66
|
-
# We treat schema validation errors as syntax errors in the manifest
|
|
67
|
-
raise ManifestSyntaxError(f"Schema validation failed: {e.message}") from e
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
coreason_manifest/__init__.py,sha256=wDlmlYxr29P_fX5Et6pP-jcMtQe6eTzKvpUrh7zFMOM,1857
|
|
2
|
-
coreason_manifest/engine.py,sha256=ENAkRuGFd1uY_5u4EYEsTZUMrqw5HgijKtkF9lk-E-c,8199
|
|
3
|
-
coreason_manifest/errors.py,sha256=CKFWSucncoPLYUJcsuXtLIfneyr2d89oHRTiWPT4psU,1437
|
|
4
|
-
coreason_manifest/integrity.py,sha256=CMBaa2A8DpC9hDSO-aj-YOzss6if1CajaeLEmTC703c,5344
|
|
5
|
-
coreason_manifest/loader.py,sha256=WET8HAnSw4dgon_zdiZaaU1dIUrsIQPYZzxlp0crTYo,10027
|
|
6
|
-
coreason_manifest/main.py,sha256=YQO98w2wxkfNs-DpSLmiDTs9DtUeGsC48GEfbElXVPk,357
|
|
7
|
-
coreason_manifest/models.py,sha256=EuaE1Wl5T8f_eI9vF1VFp5hd0y9C7E52zAjB52EulKw,7497
|
|
8
|
-
coreason_manifest/policies/compliance.rego,sha256=-drMuno6YkGOXKjvdLWawCZWK8iUxY7OcXpoXa_9Oyo,3035
|
|
9
|
-
coreason_manifest/policies/tbom.json,sha256=rSn4V44_IdFqCC86J3Jc31qQKTV4J5BdmyO0CI4iOu0,167
|
|
10
|
-
coreason_manifest/policy.py,sha256=vvEivq5HSjv-bMSrZ5VMM3eTomYFp39SBtuFajG9RU4,5009
|
|
11
|
-
coreason_manifest/recipes.py,sha256=tMNOE-JYJtG6NYRH1a-IoCLD4gXo7qoTb8dypLkmHC0,5849
|
|
12
|
-
coreason_manifest/schemas/__init__.py,sha256=9TMs6jWKCIewAKkj-u0tq9c6N_-0CU6b5s9q6MTS6v4,17
|
|
13
|
-
coreason_manifest/schemas/agent.schema.json,sha256=VigUX3ltX7YaW9P7n0DNYGNOf8C6VCuXNy71u3LB9i4,6812
|
|
14
|
-
coreason_manifest/server.py,sha256=ezuk-4sABF92U4cY1ppYJGJf8q6yeoteVlrwnmk7Jis,4253
|
|
15
|
-
coreason_manifest/utils/__init__.py,sha256=Q9gXiBtX3mD9GTu4z0JDHSHkbXC-MRHagrOaOmRH_1Q,435
|
|
16
|
-
coreason_manifest/utils/logger.py,sha256=A7E6Hd_Jk1XDUajNEJQl-WtUv9M2LT76b4_TsbxnILw,1227
|
|
17
|
-
coreason_manifest/validator.py,sha256=v33EzKroRwLEjZeuRRpTB7cqB38op3DV2EZpZhJ80a0,2240
|
|
18
|
-
coreason_manifest-0.6.0.dist-info/METADATA,sha256=nkC0JpE_Itg26p5U7-mS8KizLv-lBc9eZM2FrB3oioM,7421
|
|
19
|
-
coreason_manifest-0.6.0.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
20
|
-
coreason_manifest-0.6.0.dist-info/licenses/LICENSE,sha256=3tYb7ZQe7sVXcbNmX22fDESFjOSIlCZodUGpZMkuSlk,3063
|
|
21
|
-
coreason_manifest-0.6.0.dist-info/licenses/NOTICE,sha256=tqzUyP9VTCGxoHLgBI0AC1i0G7m_PSyESFL8Jwuw0dA,610
|
|
22
|
-
coreason_manifest-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|