Shiggii's picture
Create models.py
10bd457 verified
Raw
History Blame Contribute Delete
5.24 kB
"""Pydantic V2 models for CloudGuard-S3-Auditor environment."""
from __future__ import annotations
import enum
from typing import Literal
from pydantic import BaseModel, Field, field_validator
# ── Enums ────────────────────────────────────────────────────────────────────
class BucketPolicy(str, enum.Enum):
PUBLIC = "public"
PRIVATE = "private"
class EncryptionType(str, enum.Enum):
NONE = "none"
AES256 = "aes256"
AWS_KMS = "aws-kms"
class ActionType(str, enum.Enum):
MAKE_PRIVATE = "make_private"
MAKE_PUBLIC = "make_public"
ENABLE_ENCRYPTION = "enable_encryption"
NOOP = "noop"
# ── Bucket State ─────────────────────────────────────────────────────────────
class S3Bucket(BaseModel):
"""Single S3 bucket in the simulated environment."""
name: str = Field(..., description="Bucket identifier, e.g. 'website-css' or 'user-pii-db'")
policy: BucketPolicy = Field(default=BucketPolicy.PUBLIC)
encryption: EncryptionType = Field(default=EncryptionType.NONE)
contains_pii: bool = Field(default=False)
is_required_public: bool = Field(default=False)
def policy_json(self) -> dict:
return {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow" if self.policy == BucketPolicy.PUBLIC else "Deny",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": f"arn:aws:s3:::{self.name}/*",
}
],
}
# ── Action ───────────────────────────────────────────────────────────────────
class Action(BaseModel):
"""Agent action submitted each step."""
action_type: ActionType
bucket_name: str | None = Field(
default=None,
description="Target bucket name (required for all actions except noop)",
)
encryption_algo: EncryptionType | None = Field(
default=None,
description="Encryption algorithm (required only for enable_encryption)",
)
@field_validator("bucket_name")
@classmethod
def require_bucket_for_non_noop(cls, v: str | None, info) -> str | None:
if info.data.get("action_type") != ActionType.NOOP and v is None:
raise ValueError("bucket_name is required for non-noop actions")
return v
def to_str(self) -> str:
if self.action_type == ActionType.NOOP:
return "noop"
if self.action_type == ActionType.ENABLE_ENCRYPTION:
algo = self.encryption_algo.value if self.encryption_algo else "aes256"
return f"enable_encryption:{self.bucket_name}:{algo}"
return f"{self.action_type.value}:{self.bucket_name}"
@classmethod
def from_str(cls, raw: str) -> "Action":
raw = raw.strip()
if raw == "noop":
return cls(action_type=ActionType.NOOP)
parts = raw.split(":")
action_type = ActionType(parts[0])
if action_type == ActionType.ENABLE_ENCRYPTION:
if len(parts) < 3:
raise ValueError("enable_encryption requires bucket_name and algo")
return cls(
action_type=action_type,
bucket_name=parts[1],
encryption_algo=EncryptionType(parts[2]),
)
return cls(action_type=action_type, bucket_name=parts[1])
# ── Observation ──────────────────────────────────────────────────────────────
class BucketObservation(BaseModel):
"""Visible state of a single bucket (sent to agent)."""
name: str
policy: BucketPolicy
encryption: EncryptionType
contains_pii: bool
is_required_public: bool
class Observation(BaseModel):
"""Full observation returned to agent each step."""
buckets: list[BucketObservation]
step_number: int = Field(ge=0)
max_steps: int = Field(ge=1)
done: bool = False
message: str = ""
# ── Reward / Step Result ─────────────────────────────────────────────────────
class StepResult(BaseModel):
"""Result of a single env.step() call."""
observation: Observation
reward: float = Field(ge=-10.0, le=10.0)
done: bool = False
info: dict = Field(default_factory=dict)
# ── Grading ──────────────────────────────────────────────────────────────────
class GradeResult(BaseModel):
"""Output of the SuccessGrader."""
success: bool
score: float = Field(ge=0.0, le=1.0)
details: dict = Field(default_factory=dict)