Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions backend/controlplane/src/integrations/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! Enterprise Integrations — governed connectors to enterprise & government systems.

pub mod model;
pub mod placeholders;
pub mod store;

pub use model::{
classify_risk, CredentialRef, IntegrationAuditEvent, IntegrationKind, IntegrationPermission,
IntegrationProvider, NewIntegration,
};
pub use store::IntegrationRegistry;
201 changes: 201 additions & 0 deletions backend/controlplane/src/integrations/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
//! Enterprise integration domain model.
//!
//! ClawForge governs *connections* to enterprise and government systems. The
//! control plane never stores secrets — only a [`CredentialRef`] pointing at
//! where the secret actually lives (a vault, an env var, an SSO provider).

use chrono::Utc;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::constants::{LifecycleStatus, RiskLevel};

/// The category of an enterprise/government integration.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IntegrationKind {
Oracle,
SqlServer,
Postgres,
MongoDb,
SharePoint,
ServiceNow,
ArcGis,
ActiveDirectory,
Sso,
ApiGateway,
Email,
Webhook,
}

impl IntegrationKind {
/// Baseline risk for this category before considering granted permissions.
/// Identity stores and primary databases of record are the most sensitive.
pub fn default_risk(&self) -> RiskLevel {
use IntegrationKind::*;
match self {
ActiveDirectory | Sso => RiskLevel::Critical,
Oracle | SqlServer | Postgres | MongoDb => RiskLevel::High,
ServiceNow | SharePoint | ApiGateway => RiskLevel::Medium,
ArcGis | Email | Webhook => RiskLevel::Medium,
}
}
}

/// Classify an integration's effective risk: the higher of the category
/// baseline and a floor implied by any elevated (write/delete/admin) permission.
pub fn classify_risk(kind: IntegrationKind, permissions: &[IntegrationPermission]) -> RiskLevel {
let base = kind.default_risk();
let has_elevated = permissions.iter().any(|p| p.is_elevated());
if has_elevated && base.weight() < RiskLevel::High.weight() {
RiskLevel::High
} else {
base
}
}

/// Where an integration's credentials are kept. This is a *reference*, never
/// the secret material itself.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CredentialRef {
/// Backing secret store: `vault` | `env` | `sso` | `keychain` | `none`.
pub store: String,
/// Lookup key within that store (e.g. a vault path or env var name).
pub key: String,
/// Human-friendly note about the credential.
#[serde(default)]
pub description: String,
}

impl CredentialRef {
/// A reference into a secrets vault.
pub fn vault(path: impl Into<String>) -> Self {
CredentialRef { store: "vault".into(), key: path.into(), description: String::new() }
}

/// A reference to an environment variable.
pub fn env(var: impl Into<String>) -> Self {
CredentialRef { store: "env".into(), key: var.into(), description: String::new() }
}

/// A placeholder for integrations that need no stored credential.
pub fn none() -> Self {
CredentialRef { store: "none".into(), key: String::new(), description: String::new() }
}

/// Whether this reference actually points at a secret store.
pub fn is_present(&self) -> bool {
self.store != "none" && !self.key.is_empty()
}
}

/// An operation an integration is permitted to perform. Granting `Write`,
/// `Delete`, or `Admin` is what elevates an integration's risk.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum IntegrationPermission {
/// Establish a connection / authenticate.
Connect,
/// Read data.
Read,
/// Write or update data.
Write,
/// Delete data.
Delete,
/// Administrative / privileged operations.
Admin,
}

impl IntegrationPermission {
/// Whether this permission grants mutating or privileged access.
pub fn is_elevated(&self) -> bool {
matches!(
self,
IntegrationPermission::Write | IntegrationPermission::Delete | IntegrationPermission::Admin
)
}
}

/// An append-only audit entry for an integration's lifecycle.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IntegrationAuditEvent {
pub id: String,
pub integration_id: String,
/// `registered` | `approved` | `blocked`.
pub action: String,
pub at: i64,
}

/// A registered enterprise/government integration and its governance metadata.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IntegrationProvider {
/// Stable UUID.
pub id: String,
pub name: String,
pub kind: IntegrationKind,
pub description: String,
/// Accountable owner.
pub owner: String,
/// Owning department.
pub department: String,
/// Connection endpoint (host, URL, or service identifier).
pub endpoint: String,
/// Reference to where credentials live (never the secret itself).
pub credential: CredentialRef,
/// Operations this integration is permitted to perform.
pub permissions: Vec<IntegrationPermission>,
/// Assessed risk level.
pub risk_level: RiskLevel,
/// Governance status (`pending_approval` / `active` / `blocked` / …).
pub status: LifecycleStatus,
pub created_at: i64,
pub updated_at: i64,
}

/// Input used to register a new integration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewIntegration {
pub name: String,
pub kind: IntegrationKind,
pub description: String,
pub owner: String,
pub department: String,
pub endpoint: String,
pub credential: CredentialRef,
#[serde(default)]
pub permissions: Vec<IntegrationPermission>,
/// Explicit risk level; if omitted, callers can derive one from the kind.
pub risk_level: RiskLevel,
}

impl IntegrationProvider {
/// Materialise a fresh integration record; starts in `PendingApproval`.
pub fn from_new(input: NewIntegration) -> Self {
let now = Utc::now().timestamp();
IntegrationProvider {
id: Uuid::new_v4().to_string(),
name: input.name,
kind: input.kind,
description: input.description,
owner: input.owner,
department: input.department,
endpoint: input.endpoint,
credential: input.credential,
permissions: input.permissions,
risk_level: input.risk_level,
status: LifecycleStatus::PendingApproval,
created_at: now,
updated_at: now,
}
}

/// Whether this integration may currently be used.
pub fn is_usable(&self) -> bool {
self.status.is_operational()
}

/// Whether any granted permission is elevated (write/delete/admin).
pub fn has_elevated_permission(&self) -> bool {
self.permissions.iter().any(|p| p.is_elevated())
}
}
94 changes: 94 additions & 0 deletions backend/controlplane/src/integrations/placeholders.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//! Placeholder constructors for common integration categories.
//!
//! These are convenience builders that produce a [`NewIntegration`] with
//! sensible defaults for each category. They are *connection blueprints*, not
//! live clients — the actual wire protocol is implemented by the runtime; the
//! control plane only governs the connection.

use crate::constants::RiskLevel;

use super::model::{CredentialRef, IntegrationKind, IntegrationPermission, NewIntegration};

/// Placeholder for a database integration (Oracle, SQL Server, Postgres,
/// MongoDB, …). Defaults to read-only `Connect`+`Read` at high risk.
pub fn database(
name: &str,
owner: &str,
department: &str,
kind: IntegrationKind,
endpoint: &str,
credential: CredentialRef,
) -> NewIntegration {
NewIntegration {
name: name.into(),
kind,
description: "Database integration".into(),
owner: owner.into(),
department: department.into(),
endpoint: endpoint.into(),
credential,
permissions: vec![IntegrationPermission::Connect, IntegrationPermission::Read],
risk_level: RiskLevel::High,
}
}

/// Placeholder for a GIS integration (ArcGIS and similar spatial services).
pub fn gis(name: &str, owner: &str, department: &str, endpoint: &str, credential: CredentialRef) -> NewIntegration {
NewIntegration {
name: name.into(),
kind: IntegrationKind::ArcGis,
description: "GIS / spatial data integration".into(),
owner: owner.into(),
department: department.into(),
endpoint: endpoint.into(),
credential,
permissions: vec![IntegrationPermission::Connect, IntegrationPermission::Read],
risk_level: RiskLevel::Medium,
}
}

/// Placeholder for an SSO / identity-provider integration (OIDC/SAML, AD).
/// Identity integrations are high risk by default.
pub fn sso(name: &str, owner: &str, department: &str, endpoint: &str, credential: CredentialRef) -> NewIntegration {
NewIntegration {
name: name.into(),
kind: IntegrationKind::Sso,
description: "Single sign-on / identity provider integration".into(),
owner: owner.into(),
department: department.into(),
endpoint: endpoint.into(),
credential,
permissions: vec![IntegrationPermission::Connect, IntegrationPermission::Read],
risk_level: RiskLevel::High,
}
}

/// Placeholder for an email-sending integration (SMTP / provider API).
pub fn email(name: &str, owner: &str, department: &str, endpoint: &str, credential: CredentialRef) -> NewIntegration {
NewIntegration {
name: name.into(),
kind: IntegrationKind::Email,
description: "Outbound email integration".into(),
owner: owner.into(),
department: department.into(),
endpoint: endpoint.into(),
credential,
permissions: vec![IntegrationPermission::Connect, IntegrationPermission::Write],
risk_level: RiskLevel::Medium,
}
}

/// Placeholder for an outbound webhook integration.
pub fn webhook(name: &str, owner: &str, department: &str, url: &str) -> NewIntegration {
NewIntegration {
name: name.into(),
kind: IntegrationKind::Webhook,
description: "Outbound webhook integration".into(),
owner: owner.into(),
department: department.into(),
endpoint: url.into(),
credential: CredentialRef::none(),
permissions: vec![IntegrationPermission::Connect, IntegrationPermission::Write],
risk_level: RiskLevel::Medium,
}
}
Loading
Loading