use secrecy::SecretString; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug)] pub struct AgentConfig { pub mongodb_uri: String, pub mongodb_database: String, pub litellm_url: String, pub litellm_api_key: SecretString, pub litellm_model: String, pub litellm_embed_model: String, pub github_token: Option, pub github_webhook_secret: Option, pub gitlab_url: Option, pub gitlab_token: Option, pub gitlab_webhook_secret: Option, pub jira_url: Option, pub jira_email: Option, pub jira_api_token: Option, pub jira_project_key: Option, pub searxng_url: Option, pub nvd_api_key: Option, pub agent_port: u16, pub scan_schedule: String, pub cve_monitor_schedule: String, pub git_clone_base_path: String, /// Base directory for content-addressed artifact blobs and per-run working /// dirs (`/blobs//`, `/work///`). pub artifact_store_base_path: String, pub ssh_key_path: String, pub keycloak_url: Option, pub keycloak_realm: Option, pub keycloak_admin_username: Option, pub keycloak_admin_password: Option, // Pentest defaults pub pentest_verification_email: Option, pub pentest_imap_host: Option, pub pentest_imap_port: Option, /// Use implicit TLS (IMAPS, port 993) instead of plain IMAP. pub pentest_imap_tls: bool, pub pentest_imap_username: Option, pub pentest_imap_password: Option, /// Static bearer for the cross-tenant admin endpoints under /// `/api/v1/admin/*`. When `None`, those endpoints are not /// mounted at all (defense-in-depth: ops endpoints never reach /// any auth path if no operator has explicitly opted in). pub admin_api_token: Option, /// Live tenant-registry URL the scheduler consults for the list /// of tenants to iterate. When `None` or unreachable, scheduler /// falls back to `SCHEDULER_TENANT_IDS` env (M7.2-C). pub tenant_registry_url: Option, /// Ephemeral soft-PLC provisioning for dynamic PLC testing (#183). Off by /// default: it needs Docker access in the agent's runtime, which is a /// deployment opt-in. pub plc_runtime: PlcRuntimeConfig, /// Static bearer for the Werkbank runner endpoints /// (`/api/v1/werkbank/jobs/*`). Machine auth for runners leasing/completing /// jobs — NOT a Keycloak JWT, since a runner acts across tenants. When /// `None`, those endpoints are not mounted at all. pub werkbank_runner_token: Option, /// Source for the OSCAL control catalog pulled from breakpilot-compliance /// (drives the [`crate::traits::ControlsProvider`]). Disabled when /// `base_url` is `None`. pub breakpilot: BreakpilotConfig, } /// Where to pull the OSCAL control catalog from breakpilot-compliance, and where /// to snapshot it for deterministic / offline reuse. #[derive(Debug, Clone)] pub struct BreakpilotConfig { /// Backend base URL (e.g. `http://backend-compliance:8002`). `None` disables /// the OSCAL controls provider. pub base_url: Option, /// Optional bearer token for the catalog endpoint. pub token: Option, /// Directory for catalog snapshots. pub snapshot_dir: String, /// Enable the master-controls **semantic** mapping pass (embed regions, /// Enable the master-controls **semantic** mapping pass (embed regions, /// retrieve nearest controls, grounded-judge). On by default — validated live /// against the deployed master-controls catalog. Still a no-op unless /// `base_url` is set and the catalog is reachable. pub semantic_mapping: bool, /// Enable the **grounded surface** pass for absence-based controls (retrieve /// the code surface a control governs, judge whether it holds). On by default /// — validated live; it covers the 8 absence-based CRA controls that no /// syntactic rule can. pub grounded_control_checks: bool, } impl Default for BreakpilotConfig { fn default() -> Self { Self { base_url: None, token: None, snapshot_dir: "/data/compliance-scanner/oscal".to_string(), semantic_mapping: true, grounded_control_checks: true, } } } /// Configuration for the ephemeral soft-PLC "provision-and-test" path (#183). /// /// When a PLC/SPS target ships control logic but no reachable live device, the /// agent can instantiate that logic itself: spin up a throwaway soft-PLC /// (OpenPLC) container in-cluster, load the program, start the runtime, probe it /// over industrial protocols, then tear it down. This struct carries the knobs /// for that container's lifecycle and the OpenPLC web-UI credentials used to /// upload the program. #[derive(Clone, Debug)] pub struct PlcRuntimeConfig { /// Master switch. Provision-and-test does nothing unless this is set — it /// shells out to `docker`, which requires the agent container to have Docker /// access (socket mount), an explicit deployment decision. pub enabled: bool, /// Container image for the ephemeral soft-PLC (OpenPLC). pub image: String, /// Docker network the instance joins. Must be the agent's own network so it /// is reachable in-cluster by container name and never published to the host. pub network: String, /// Memory cap passed to `docker run --memory` (e.g. `512m`). pub memory: String, /// CPU cap passed to `docker run --cpus` (e.g. `0.5`). pub cpus: String, /// Hard ceiling on a provisioned instance's lifetime. Teardown is guaranteed /// no later than this even if a load/probe step hangs. pub max_lifetime_secs: u64, /// OpenPLC web-UI username for the program upload (image default `openplc`). pub openplc_user: String, /// OpenPLC web-UI password (image default `openplc`). pub openplc_password: SecretString, } impl Default for PlcRuntimeConfig { fn default() -> Self { Self { enabled: false, image: "registry.meghsakha.com/openplc:latest".to_string(), network: "certifai".to_string(), memory: "512m".to_string(), cpus: "0.5".to_string(), max_lifetime_secs: 180, openplc_user: "openplc".to_string(), openplc_password: SecretString::from("openplc".to_string()), } } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DashboardConfig { pub mongodb_uri: String, pub mongodb_database: String, pub agent_api_url: String, pub dashboard_port: u16, pub mcp_endpoint_url: Option, }