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
6 changes: 6 additions & 0 deletions architecture/security-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ connection metadata agrees. When request paths overlap, a path endpoint with a
higher specificity rank deterministically overrides broader request-processing
metadata. Equally specific overlapping endpoints must agree.

Endpoint `tls`, `enforcement`, and `access` use protobuf enums and retain their
named YAML spellings. `protocol` remains a string so the supported protocol set
can evolve, but every ingress validates it before persistence or activation.
The supervisor also refuses unknown enum numbers and protocol values
defensively; an unrecognized enforcement value never falls back to audit.

Gateway mutation paths validate the complete effective candidate before
persistence when the affected sandbox scope is known. Direct replacements,
incremental merges and approvals, provider attachment, and profile fanout reject
Expand Down
20 changes: 14 additions & 6 deletions crates/openshell-cli/src/policy_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,8 +472,10 @@ fn parse_add_endpoint_spec(spec: &str) -> Result<NetworkEndpoint> {
port,
ports: vec![port],
protocol: protocol.to_string(),
enforcement: enforcement.to_string(),
access: access.to_string(),
enforcement: openshell_policy::network_enforcement_mode_from_str(enforcement)
.expect("validated enforcement") as i32,
access: openshell_policy::network_access_preset_from_str(access).expect("validated access")
as i32,
..Default::default()
};
apply_add_endpoint_options(spec, &mut endpoint, options)?;
Expand Down Expand Up @@ -616,7 +618,7 @@ mod tests {
port: 443,
ports: vec![443],
protocol: "rest".to_string(),
access: "read-only".to_string(),
access: openshell_core::proto::NetworkAccessPreset::ReadOnly as i32,
..Default::default()
}],
},
Expand Down Expand Up @@ -733,8 +735,14 @@ mod tests {
let endpoint = &rule.endpoints[0];
assert_eq!(endpoint.host, "realtime.example.com");
assert_eq!(endpoint.protocol, "websocket");
assert_eq!(endpoint.access, "read-write");
assert_eq!(endpoint.enforcement, "enforce");
assert_eq!(
endpoint.access,
openshell_core::proto::NetworkAccessPreset::ReadWrite as i32
);
assert_eq!(
endpoint.enforcement,
openshell_core::proto::NetworkEnforcementMode::Enforce as i32
);
}

#[test]
Expand All @@ -754,7 +762,7 @@ mod tests {
panic!("expected add-rule preview");
};
assert_eq!(rule.endpoints[0].protocol, "tcp");
assert!(rule.endpoints[0].access.is_empty());
assert_eq!(rule.endpoints[0].access, 0);
}

#[test]
Expand Down
9 changes: 6 additions & 3 deletions crates/openshell-cli/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6194,8 +6194,11 @@ fn format_endpoint(endpoint: &openshell_core::proto::NetworkEndpoint) -> String
};
tags.push(layer_tag.to_string());

if !endpoint.access.is_empty() {
tags.push(format!("access={}", endpoint.access));
if endpoint.access != 0 {
tags.push(format!(
"access={}",
openshell_policy::network_access_preset_to_str(endpoint.access).unwrap_or("unknown")
));
}

for r in &endpoint.rules {
Expand Down Expand Up @@ -7352,7 +7355,7 @@ mod tests {
host: "host.example.test".to_string(),
port: 443,
protocol: "rest".to_string(),
access: "read-only".to_string(),
access: openshell_core::proto::NetworkAccessPreset::ReadOnly as i32,
..Default::default()
};
assert_eq!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,9 @@ impl OpenShell for TestOpenShell {
host: "api.user.example.com".to_string(),
port: 443,
protocol: "rest".to_string(),
enforcement: "enforce".to_string(),
access: "read-only".to_string(),
enforcement: openshell_core::proto::NetworkEnforcementMode::Enforce
as i32,
access: openshell_core::proto::NetworkAccessPreset::ReadOnly as i32,
..Default::default()
}],
..Default::default()
Expand All @@ -221,8 +222,9 @@ impl OpenShell for TestOpenShell {
host: "api.provider.example.com".to_string(),
port: 443,
protocol: "rest".to_string(),
enforcement: "enforce".to_string(),
access: "read-only".to_string(),
enforcement: openshell_core::proto::NetworkEnforcementMode::Enforce
as i32,
access: openshell_core::proto::NetworkAccessPreset::ReadOnly as i32,
..Default::default()
}],
..Default::default()
Expand Down Expand Up @@ -496,8 +498,8 @@ impl OpenShell for TestOpenShell {
host: "api.example.com".to_string(),
port: 443,
protocol: "rest".to_string(),
enforcement: "enforce".to_string(),
access: "read-only".to_string(),
enforcement: openshell_core::proto::NetworkEnforcementMode::Enforce as i32,
access: openshell_core::proto::NetworkAccessPreset::ReadOnly as i32,
..Default::default()
}],
..Default::default()
Expand Down
25 changes: 11 additions & 14 deletions crates/openshell-driver-mxc/src/policy_map/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,27 +517,25 @@ fn report_endpoint_l7_losses(endpoint: &NetworkEndpoint, path: &str, items: &mut
);
}

if !endpoint.tls.is_empty() {
let severity = if endpoint.tls == "skip" {
"warning"
} else {
"error"
};
if endpoint.tls != 0 {
let tls = openshell_policy::network_tls_mode_to_str(endpoint.tls).unwrap_or("unknown");
let severity = if tls == "skip" { "warning" } else { "error" };
add_loss(
items,
&format!("{path}.tls"),
severity,
&format!(
"MXC has no OpenShell TLS inspection mode equivalent for '{}'.",
endpoint.tls
tls
),
"TLS inspection mode",
"MXC network policy is host-level only.",
);
}

if !endpoint.enforcement.is_empty() {
if endpoint.enforcement == "audit" {
if endpoint.enforcement != 0 {
if openshell_policy::network_enforcement_mode_to_str(endpoint.enforcement) == Some("audit")
{
add_loss(
items,
&format!("{path}.enforcement"),
Expand All @@ -558,15 +556,14 @@ fn report_endpoint_l7_losses(endpoint: &NetworkEndpoint, path: &str, items: &mut
}
}

if !endpoint.access.is_empty() {
if endpoint.access != 0 {
let access =
openshell_policy::network_access_preset_to_str(endpoint.access).unwrap_or("unknown");
add_loss(
items,
&format!("{path}.access"),
"error",
&format!(
"MXC has no access preset equivalent for '{}'.",
endpoint.access
),
&format!("MXC has no access preset equivalent for '{}'.", access),
"REST/WebSocket/GraphQL access preset",
"MXC cannot enforce method or operation-level access.",
);
Expand Down
16 changes: 8 additions & 8 deletions crates/openshell-driver-mxc/tests/policy_mapper_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ fn b_tls_skip_warning() {
"r",
NetworkEndpoint {
host: "api.example.com".into(),
tls: "skip".into(),
tls: openshell_core::proto::NetworkTlsMode::Skip as i32,
..Default::default()
},
);
Expand All @@ -457,7 +457,7 @@ fn b_tls_non_skip_error() {
"r",
NetworkEndpoint {
host: "api.example.com".into(),
tls: "terminate".into(),
tls: openshell_core::proto::NetworkTlsMode::Terminate as i32,
..Default::default()
},
);
Expand All @@ -472,7 +472,7 @@ fn b_enforcement_audit_error() {
"r",
NetworkEndpoint {
host: "api.example.com".into(),
enforcement: "audit".into(),
enforcement: openshell_core::proto::NetworkEnforcementMode::Audit as i32,
..Default::default()
},
);
Expand All @@ -492,7 +492,7 @@ fn b_enforcement_non_audit_warning() {
"r",
NetworkEndpoint {
host: "api.example.com".into(),
enforcement: "enforce".into(),
enforcement: openshell_core::proto::NetworkEnforcementMode::Enforce as i32,
..Default::default()
},
);
Expand All @@ -512,7 +512,7 @@ fn b_access_error() {
"r",
NetworkEndpoint {
host: "api.example.com".into(),
access: "read-only".into(),
access: openshell_core::proto::NetworkAccessPreset::ReadOnly as i32,
..Default::default()
},
);
Expand Down Expand Up @@ -1193,9 +1193,9 @@ fn handled_fields_inventory() {
// Two ports → serializes as `ports: [80, 443]` (array form).
ports: vec![80, 443],
protocol: "graphql".into(),
tls: "skip".into(),
enforcement: "enforce".into(),
access: "full".into(),
tls: openshell_core::proto::NetworkTlsMode::Skip as i32,
enforcement: openshell_core::proto::NetworkEnforcementMode::Enforce as i32,
access: openshell_core::proto::NetworkAccessPreset::Full as i32,
allowed_ips: vec!["10.0.0.1".into()],
allow_encoded_slash: true,
websocket_credential_rewrite: true,
Expand Down
9 changes: 5 additions & 4 deletions crates/openshell-driver-mxc/tests/wxc_exec_real.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
use base64::Engine as _;
use openshell_core::proto::compute::v1::{DriverSandbox, DriverSandboxSpec, DriverSandboxTemplate};
use openshell_core::proto::{
FilesystemPolicy, NetworkBinary, NetworkEndpoint, NetworkPolicyRule, SandboxPolicy,
FilesystemPolicy, NetworkAccessPreset, NetworkBinary, NetworkEndpoint, NetworkEnforcementMode,
NetworkPolicyRule, NetworkTlsMode, SandboxPolicy,
};
use openshell_driver_mxc::{MxcComputeBackend, MxcComputeConfig};
use std::path::PathBuf;
Expand Down Expand Up @@ -732,9 +733,9 @@ async fn pc_https_egress_reads_injected_ca_bundle() {
host: "example.com".to_string(),
ports: vec![443],
protocol: "rest".to_string(),
tls: "terminate".to_string(),
enforcement: "enforce".to_string(),
access: "read-only".to_string(),
tls: NetworkTlsMode::Unspecified as i32,
enforcement: NetworkEnforcementMode::Enforce as i32,
access: NetworkAccessPreset::ReadOnly as i32,
..Default::default()
}],
binaries: vec![NetworkBinary { path: cmd_string }],
Expand Down
Loading
Loading