Add policy_read_payload helper to build policy response with deployment status, implement nexafabric_rule_version to parse policy ID and version from rule comments, add policy_deployment_status to check active/stale/partial/unresolved states by comparing expected rules from preview against live firewall rules per cluster with version matching, extend PolicyRead schema with
365 lines
7.2 KiB
Python
365 lines
7.2 KiB
Python
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
class OrmModel(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class TokenPair(BaseModel):
|
|
access_token: str
|
|
refresh_token: str
|
|
token_type: str = "bearer"
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
email: str
|
|
password: str
|
|
|
|
|
|
class SetupStatus(BaseModel):
|
|
complete: bool
|
|
has_users: bool
|
|
has_clusters: bool
|
|
|
|
|
|
class SetupCompleteRequest(BaseModel):
|
|
admin_email: str
|
|
admin_name: str
|
|
admin_password: str = Field(min_length=12)
|
|
cluster_name: str | None = None
|
|
cluster_api_url: str | None = None
|
|
cluster_api_token: str | None = None
|
|
cluster_provider: str = "proxmox"
|
|
cluster_mode: str = "read_only"
|
|
verify_tls: bool = True
|
|
|
|
|
|
class UserRead(OrmModel):
|
|
id: str
|
|
email: str
|
|
display_name: str
|
|
is_active: bool
|
|
|
|
|
|
class UserCreate(BaseModel):
|
|
email: str
|
|
display_name: str
|
|
password: str = Field(min_length=12)
|
|
role_ids: list[str] = []
|
|
|
|
|
|
class RoleCreate(BaseModel):
|
|
name: str
|
|
permissions: list[str] = []
|
|
|
|
|
|
class RoleRead(OrmModel):
|
|
id: str
|
|
name: str
|
|
permissions: list[str]
|
|
|
|
|
|
class ClusterCreate(BaseModel):
|
|
name: str
|
|
api_url: str
|
|
api_token: str = Field(min_length=8)
|
|
provider: str = "proxmox"
|
|
mode: str = "read_only"
|
|
verify_tls: bool = True
|
|
|
|
|
|
class ClusterUpdate(BaseModel):
|
|
name: str
|
|
api_url: str
|
|
api_token: str | None = Field(default=None, min_length=8)
|
|
provider: str = "proxmox"
|
|
mode: str = "read_only"
|
|
verify_tls: bool = True
|
|
|
|
|
|
class TenantCreate(BaseModel):
|
|
name: str
|
|
description: str | None = None
|
|
|
|
|
|
class ProjectCreate(BaseModel):
|
|
tenant_id: str
|
|
name: str
|
|
description: str | None = None
|
|
|
|
|
|
class NetworkCreate(BaseModel):
|
|
cluster_id: str
|
|
project_id: str | None = None
|
|
name: str
|
|
kind: str = "bridge"
|
|
vlan_id: int | None = None
|
|
mtu: int = 1500
|
|
gateway: str | None = None
|
|
dns: list[str] = []
|
|
dhcp_enabled: bool = False
|
|
tags: list[str] = []
|
|
description: str | None = None
|
|
|
|
|
|
class SubnetCreate(BaseModel):
|
|
network_id: str
|
|
cidr: str
|
|
gateway: str | None = None
|
|
dns: list[str] = []
|
|
dhcp_enabled: bool = False
|
|
|
|
|
|
class SubnetUpdate(BaseModel):
|
|
network_id: str | None = None
|
|
cidr: str | None = None
|
|
gateway: str | None = None
|
|
dns: list[str] | None = None
|
|
dhcp_enabled: bool | None = None
|
|
|
|
|
|
class SecurityGroupCreate(BaseModel):
|
|
project_id: str | None = None
|
|
name: str
|
|
description: str | None = None
|
|
|
|
|
|
class SecurityRuleCreate(BaseModel):
|
|
security_group_id: str
|
|
direction: str = "ingress"
|
|
action: str = "allow"
|
|
protocol: str = "tcp"
|
|
source: str = "any"
|
|
destination: str = "any"
|
|
port: str | None = None
|
|
priority: int = 1000
|
|
logging: bool = False
|
|
description: str | None = None
|
|
|
|
|
|
class PolicyCreate(BaseModel):
|
|
project_id: str | None = None
|
|
name: str
|
|
enabled: bool = True
|
|
definition: dict[str, Any]
|
|
|
|
|
|
class IpReservationCreate(BaseModel):
|
|
subnet_id: str
|
|
address: str
|
|
status: str = "reserved"
|
|
note: str | None = None
|
|
|
|
|
|
class ServiceCatalogCreate(BaseModel):
|
|
name: str
|
|
protocol: str
|
|
ports: str
|
|
editable: bool = True
|
|
|
|
|
|
class FirewallApplyRequest(BaseModel):
|
|
policy_id: str
|
|
cluster_id: str | None = None
|
|
confirm: bool = False
|
|
dry_run: bool = True
|
|
|
|
|
|
class ClusterRead(OrmModel):
|
|
id: str
|
|
name: str
|
|
api_url: str
|
|
provider: str
|
|
mode: str
|
|
verify_tls: bool
|
|
last_sync_at: datetime | None
|
|
last_sync_status: str | None
|
|
last_sync_error: str | None
|
|
|
|
|
|
class NodeRead(OrmModel):
|
|
id: str
|
|
cluster_id: str
|
|
name: str
|
|
status: str
|
|
cpu_count: int
|
|
memory_mb: int
|
|
|
|
|
|
class NodeAgentRead(BaseModel):
|
|
node_id: str
|
|
status: str
|
|
version: str | None = None
|
|
last_seen_at: datetime | None = None
|
|
install_count: int = 0
|
|
last_payload: dict[str, Any] | None = None
|
|
|
|
|
|
class NodeWithAgentRead(NodeRead):
|
|
agent: NodeAgentRead | None = None
|
|
|
|
|
|
class AgentHeartbeat(BaseModel):
|
|
version: str
|
|
node_name: str | None = None
|
|
collected_at: datetime | None = None
|
|
hostname: str | None = None
|
|
kernel: str | None = None
|
|
uptime_seconds: float | None = None
|
|
loadavg: list[float] = []
|
|
interfaces: list[dict[str, Any]] = []
|
|
interface_traffic: list[dict[str, Any]] = []
|
|
flows: list[dict[str, Any]] = []
|
|
conntrack: dict[str, Any] = {}
|
|
firewall: dict[str, Any] = {}
|
|
extra: dict[str, Any] = {}
|
|
|
|
|
|
class WorkloadRead(OrmModel):
|
|
id: str
|
|
cluster_id: str
|
|
node_id: str
|
|
project_id: str | None
|
|
external_id: str
|
|
name: str
|
|
kind: str
|
|
status: str
|
|
tags: list[str]
|
|
|
|
|
|
class NetworkRead(OrmModel):
|
|
id: str
|
|
cluster_id: str
|
|
project_id: str | None
|
|
name: str
|
|
kind: str
|
|
vlan_id: int | None
|
|
mtu: int
|
|
gateway: str | None
|
|
dns: list[str]
|
|
dhcp_enabled: bool
|
|
tags: list[str]
|
|
description: str | None
|
|
|
|
|
|
class SubnetRead(OrmModel):
|
|
id: str
|
|
network_id: str
|
|
cidr: str
|
|
gateway: str | None
|
|
dns: list[str]
|
|
dhcp_enabled: bool
|
|
|
|
|
|
class IpAddressRead(OrmModel):
|
|
id: str
|
|
subnet_id: str
|
|
subnet_cidr: str | None = None
|
|
address: str
|
|
status: str
|
|
workload_id: str | None
|
|
workload_name: str | None = None
|
|
workload_external_id: str | None = None
|
|
note: str | None
|
|
|
|
|
|
class TenantRead(OrmModel):
|
|
id: str
|
|
name: str
|
|
description: str | None
|
|
|
|
|
|
class ProjectRead(OrmModel):
|
|
id: str
|
|
tenant_id: str
|
|
name: str
|
|
description: str | None
|
|
|
|
|
|
class SecurityGroupRead(OrmModel):
|
|
id: str
|
|
project_id: str | None
|
|
name: str
|
|
description: str | None
|
|
|
|
|
|
class SecurityRuleRead(OrmModel):
|
|
id: str
|
|
security_group_id: str
|
|
direction: str
|
|
action: str
|
|
protocol: str
|
|
source: str
|
|
destination: str
|
|
port: str | None
|
|
priority: int
|
|
logging: bool
|
|
description: str | None
|
|
|
|
|
|
class PolicyRead(OrmModel):
|
|
id: str
|
|
project_id: str | None
|
|
name: str
|
|
version: int
|
|
enabled: bool
|
|
enforcement_mode: str
|
|
definition: dict[str, Any]
|
|
last_compiled: dict[str, Any] | None
|
|
deployment_status: dict[str, Any] | None = None
|
|
|
|
|
|
class WorkloadInsight(BaseModel):
|
|
workload: WorkloadRead
|
|
assigned_ips: list[IpAddressRead]
|
|
traffic: list[dict[str, Any]]
|
|
active_firewall_rules: list[dict[str, Any]] = []
|
|
matching_policies: list[PolicyRead]
|
|
effective_decision: str
|
|
audit_mode_notes: list[str]
|
|
|
|
|
|
class ServiceCatalogRead(OrmModel):
|
|
id: str
|
|
name: str
|
|
protocol: str
|
|
ports: str
|
|
editable: bool
|
|
|
|
|
|
class FirewallPreview(BaseModel):
|
|
policy_id: str
|
|
dry_run: bool = True
|
|
generated_rules: list[dict[str, Any]]
|
|
warnings: list[str]
|
|
conflicts: list[str]
|
|
|
|
|
|
class JobRead(OrmModel):
|
|
id: str
|
|
kind: str
|
|
status: str
|
|
progress: int
|
|
started_at: datetime | None
|
|
finished_at: datetime | None
|
|
logs: list[str]
|
|
error: str | None
|
|
|
|
|
|
class AuditLogRead(OrmModel):
|
|
id: str
|
|
created_at: datetime
|
|
user_id: str | None
|
|
action: str
|
|
object_type: str
|
|
object_id: str | None
|
|
old_values: dict[str, Any] | None
|
|
new_values: dict[str, Any] | None
|
|
ip_address: str | None
|
|
user_agent: str | None
|
|
result: str
|
|
error_text: str | None
|