Require explicit canonical network readiness targets
This commit is contained in:
parent
c8acf116d6
commit
fbd332c31f
8 changed files with 339 additions and 12 deletions
|
|
@ -12,8 +12,10 @@ use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use crate::identity::{ConfigurationFingerprint, DeviceId, DomainId, HostPath, InodeId, MountId};
|
use crate::identity::{ConfigurationFingerprint, DeviceId, DomainId, HostPath, InodeId, MountId};
|
||||||
use crate::records::{
|
use crate::records::{
|
||||||
ConfigurationFingerprintRecordV1, EnvironmentReference, MailboxRequest, ProfileConfigurationV1,
|
ConfigurationFingerprintRecordV1, EnvironmentReference, MailboxRequest,
|
||||||
ResolvedAutoApprovalRule, ResolvedVolume, TRUSTED_HOST_CONFIGURATION_SCHEMA_ID,
|
NetworkProbeTargetConfigurationV1, NetworkReadinessConfigurationV1, ProfileConfigurationV1,
|
||||||
|
ResolvedAutoApprovalRule, ResolvedDnsProbeTargetV1, ResolvedNetworkProbeTargetV1,
|
||||||
|
ResolvedNetworkReadinessV1, ResolvedVolume, TRUSTED_HOST_CONFIGURATION_SCHEMA_ID,
|
||||||
TrustedHostConfigurationV1, VolumeAccess, VolumeKind,
|
TrustedHostConfigurationV1, VolumeAccess, VolumeKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -656,6 +658,11 @@ impl ResolvedDomainConfiguration {
|
||||||
&self.effective_deny_prefixes
|
&self.effective_deny_prefixes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn network_readiness(&self) -> &ResolvedNetworkReadinessV1 {
|
||||||
|
&self.fingerprint_record.network_readiness
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn ephemeral_environment(&self) -> &BTreeMap<String, EnvironmentValue> {
|
pub const fn ephemeral_environment(&self) -> &BTreeMap<String, EnvironmentValue> {
|
||||||
&self.ephemeral_environment
|
&self.ephemeral_environment
|
||||||
|
|
@ -843,6 +850,11 @@ pub fn load_trusted_configuration(
|
||||||
)?;
|
)?;
|
||||||
let (effective_allow_prefixes, effective_deny_prefixes) =
|
let (effective_allow_prefixes, effective_deny_prefixes) =
|
||||||
resolve_network(&configuration, selected)?;
|
resolve_network(&configuration, selected)?;
|
||||||
|
let network_readiness = resolve_network_readiness(
|
||||||
|
&selected.readiness,
|
||||||
|
&effective_allow_prefixes,
|
||||||
|
&effective_deny_prefixes,
|
||||||
|
)?;
|
||||||
let environment_references = resolve_environment_references(selected)?;
|
let environment_references = resolve_environment_references(selected)?;
|
||||||
|
|
||||||
let mut volumes = Vec::with_capacity(selected.volumes.len());
|
let mut volumes = Vec::with_capacity(selected.volumes.len());
|
||||||
|
|
@ -1045,6 +1057,7 @@ pub fn load_trusted_configuration(
|
||||||
.iter()
|
.iter()
|
||||||
.map(|prefix| (prefix.address, prefix.prefix_length))
|
.map(|prefix| (prefix.address, prefix.prefix_length))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
network_readiness,
|
||||||
escalation_enabled: selected.escalation.enabled,
|
escalation_enabled: selected.escalation.enabled,
|
||||||
auto_approval_rules: resolved_auto_approval_records,
|
auto_approval_rules: resolved_auto_approval_records,
|
||||||
};
|
};
|
||||||
|
|
@ -1134,6 +1147,34 @@ pub fn derive_configuration_fingerprint(
|
||||||
.iter()
|
.iter()
|
||||||
.map(|prefix| Ok([prefix.0.as_slice(), &[prefix.1]].concat())),
|
.map(|prefix| Ok([prefix.0.as_slice(), &[prefix.1]].concat())),
|
||||||
)?;
|
)?;
|
||||||
|
push_sorted_list(
|
||||||
|
&mut preimage,
|
||||||
|
record.network_readiness.denied.iter().map(|target| {
|
||||||
|
Ok([
|
||||||
|
target.address.as_slice(),
|
||||||
|
target.port.to_be_bytes().as_slice(),
|
||||||
|
]
|
||||||
|
.concat())
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
push_sorted_list(
|
||||||
|
&mut preimage,
|
||||||
|
record.network_readiness.permitted.iter().map(|target| {
|
||||||
|
Ok([
|
||||||
|
target.address.as_slice(),
|
||||||
|
target.port.to_be_bytes().as_slice(),
|
||||||
|
]
|
||||||
|
.concat())
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
push_sorted_list(
|
||||||
|
&mut preimage,
|
||||||
|
record.network_readiness.dns.iter().map(|target| {
|
||||||
|
let mut item = target.resolver.to_vec();
|
||||||
|
push_bytes(&mut item, target.query.as_bytes())?;
|
||||||
|
Ok(item)
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
push_bool(&mut preimage, record.escalation_enabled);
|
push_bool(&mut preimage, record.escalation_enabled);
|
||||||
push_sorted_list(
|
push_sorted_list(
|
||||||
&mut preimage,
|
&mut preimage,
|
||||||
|
|
@ -1739,6 +1780,173 @@ fn resolve_network(
|
||||||
Ok((allow.into_iter().collect(), deny.into_iter().collect()))
|
Ok((allow.into_iter().collect(), deny.into_iter().collect()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_READINESS_TARGETS: usize = 32;
|
||||||
|
fn resolve_network_readiness(
|
||||||
|
readiness: &NetworkReadinessConfigurationV1,
|
||||||
|
allow: &[Ipv4Prefix],
|
||||||
|
deny: &[Ipv4Prefix],
|
||||||
|
) -> Result<ResolvedNetworkReadinessV1, ConfigurationError> {
|
||||||
|
if readiness.denied.is_empty() || readiness.permitted.is_empty() || readiness.dns.is_empty() {
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"profile readiness requires denied, permitted, and DNS targets",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if readiness.denied.len() > MAX_READINESS_TARGETS
|
||||||
|
|| readiness.permitted.len() > MAX_READINESS_TARGETS
|
||||||
|
|| readiness.dns.len() > MAX_READINESS_TARGETS
|
||||||
|
{
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"profile readiness target count exceeds 32",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let denied = resolve_tcp_readiness_targets(&readiness.denied, allow, deny, false)?;
|
||||||
|
let permitted = resolve_tcp_readiness_targets(&readiness.permitted, allow, deny, true)?;
|
||||||
|
|
||||||
|
let dns = readiness
|
||||||
|
.dns
|
||||||
|
.iter()
|
||||||
|
.map(|target| {
|
||||||
|
Ok(ResolvedDnsProbeTargetV1 {
|
||||||
|
resolver: parse_probe_address(&target.resolver)?,
|
||||||
|
query: normalize_dns_query(&target.query)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<BTreeSet<_>, ConfigurationError>>()?;
|
||||||
|
if dns.len() != readiness.dns.len() {
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"profile readiness contains duplicate DNS targets",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ResolvedNetworkReadinessV1 {
|
||||||
|
denied: denied.into_iter().collect(),
|
||||||
|
permitted: permitted.into_iter().collect(),
|
||||||
|
dns: dns.into_iter().collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_tcp_readiness_targets(
|
||||||
|
targets: &[NetworkProbeTargetConfigurationV1],
|
||||||
|
allow: &[Ipv4Prefix],
|
||||||
|
deny: &[Ipv4Prefix],
|
||||||
|
expected_allowed: bool,
|
||||||
|
) -> Result<BTreeSet<ResolvedNetworkProbeTargetV1>, ConfigurationError> {
|
||||||
|
let resolved = targets
|
||||||
|
.iter()
|
||||||
|
.map(|target| {
|
||||||
|
if target.port == 0 {
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"readiness TCP target has port zero",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let address = parse_probe_address(&target.address)?;
|
||||||
|
if is_effectively_allowed(address, allow, deny) != expected_allowed {
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"readiness TCP target disagrees with effective profile policy",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(ResolvedNetworkProbeTargetV1 {
|
||||||
|
address,
|
||||||
|
port: target.port,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<BTreeSet<_>, _>>()?;
|
||||||
|
if resolved.len() != targets.len() {
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"profile readiness contains duplicate TCP targets",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if resolved
|
||||||
|
.iter()
|
||||||
|
.map(|target| target.address)
|
||||||
|
.collect::<BTreeSet<_>>()
|
||||||
|
.len()
|
||||||
|
!= resolved.len()
|
||||||
|
{
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"readiness TCP targets repeat an observation address",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_probe_address(value: &str) -> Result<[u8; 4], ConfigurationError> {
|
||||||
|
let address = value.parse::<std::net::Ipv4Addr>().map_err(|_| {
|
||||||
|
ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"readiness target address is not IPv4",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if address.is_unspecified() || address.is_multicast() || address.is_broadcast() {
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"readiness target address is not unicast",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(address.octets())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_dns_query(value: &str) -> Result<String, ConfigurationError> {
|
||||||
|
let query = value.strip_suffix('.').unwrap_or(value);
|
||||||
|
if query.is_empty() || query.len() > 253 || !query.is_ascii() {
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"DNS readiness query name is invalid",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for label in query.split('.') {
|
||||||
|
if label.is_empty()
|
||||||
|
|| label.len() > 63
|
||||||
|
|| label.starts_with('-')
|
||||||
|
|| label.ends_with('-')
|
||||||
|
|| !label
|
||||||
|
.bytes()
|
||||||
|
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
|
||||||
|
{
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"DNS readiness query name is invalid",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(query.to_ascii_lowercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_effectively_allowed(address: [u8; 4], allow: &[Ipv4Prefix], deny: &[Ipv4Prefix]) -> bool {
|
||||||
|
let best_allow = allow
|
||||||
|
.iter()
|
||||||
|
.filter(|prefix| prefix_contains(**prefix, address))
|
||||||
|
.map(|prefix| prefix.prefix_length)
|
||||||
|
.max();
|
||||||
|
let best_deny = deny
|
||||||
|
.iter()
|
||||||
|
.filter(|prefix| prefix_contains(**prefix, address))
|
||||||
|
.map(|prefix| prefix.prefix_length)
|
||||||
|
.max();
|
||||||
|
match (best_allow, best_deny) {
|
||||||
|
(Some(allowed), Some(denied)) => allowed > denied,
|
||||||
|
(Some(_) | None, None) => true,
|
||||||
|
(None, Some(_)) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prefix_contains(prefix: Ipv4Prefix, address: [u8; 4]) -> bool {
|
||||||
|
let mask = if prefix.prefix_length == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
u32::MAX << (32 - prefix.prefix_length)
|
||||||
|
};
|
||||||
|
u32::from_be_bytes(address) & mask == u32::from_be_bytes(prefix.address)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_ipv4_prefix(value: &str) -> Result<Ipv4Prefix, ConfigurationError> {
|
fn parse_ipv4_prefix(value: &str) -> Result<Ipv4Prefix, ConfigurationError> {
|
||||||
let (address, prefix) = value.split_once('/').ok_or_else(|| {
|
let (address, prefix) = value.split_once('/').ok_or_else(|| {
|
||||||
ConfigurationError::new(
|
ConfigurationError::new(
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,31 @@ pub struct NetworkConfigurationV1 {
|
||||||
pub default_deny: Vec<String>,
|
pub default_deny: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct NetworkReadinessConfigurationV1 {
|
||||||
|
#[serde(default)]
|
||||||
|
pub denied: Vec<NetworkProbeTargetConfigurationV1>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub permitted: Vec<NetworkProbeTargetConfigurationV1>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub dns: Vec<DnsProbeTargetConfigurationV1>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct NetworkProbeTargetConfigurationV1 {
|
||||||
|
pub address: String,
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct DnsProbeTargetConfigurationV1 {
|
||||||
|
pub resolver: String,
|
||||||
|
pub query: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum VolumeKind {
|
pub enum VolumeKind {
|
||||||
|
|
@ -81,6 +106,8 @@ pub struct ProfileConfigurationV1 {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub deny_egress: Vec<String>,
|
pub deny_egress: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub readiness: NetworkReadinessConfigurationV1,
|
||||||
|
#[serde(default)]
|
||||||
pub env: BTreeMap<String, String>,
|
pub env: BTreeMap<String, String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub volumes: Vec<VolumeConfigurationV1>,
|
pub volumes: Vec<VolumeConfigurationV1>,
|
||||||
|
|
@ -194,6 +221,26 @@ pub struct ConfigurationFingerprintRecordV1 {
|
||||||
pub volumes: Vec<ResolvedVolume>,
|
pub volumes: Vec<ResolvedVolume>,
|
||||||
pub effective_allow_prefixes: Vec<([u8; 4], u8)>,
|
pub effective_allow_prefixes: Vec<([u8; 4], u8)>,
|
||||||
pub effective_deny_prefixes: Vec<([u8; 4], u8)>,
|
pub effective_deny_prefixes: Vec<([u8; 4], u8)>,
|
||||||
|
pub network_readiness: ResolvedNetworkReadinessV1,
|
||||||
pub escalation_enabled: bool,
|
pub escalation_enabled: bool,
|
||||||
pub auto_approval_rules: Vec<ResolvedAutoApprovalRule>,
|
pub auto_approval_rules: Vec<ResolvedAutoApprovalRule>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct ResolvedNetworkReadinessV1 {
|
||||||
|
pub denied: Vec<ResolvedNetworkProbeTargetV1>,
|
||||||
|
pub permitted: Vec<ResolvedNetworkProbeTargetV1>,
|
||||||
|
pub dns: Vec<ResolvedDnsProbeTargetV1>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||||
|
pub struct ResolvedNetworkProbeTargetV1 {
|
||||||
|
pub address: [u8; 4],
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||||
|
pub struct ResolvedDnsProbeTargetV1 {
|
||||||
|
pub resolver: [u8; 4],
|
||||||
|
pub query: String,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,14 @@ use super::{
|
||||||
PolicyDelivery, PrefixRule, PrepareOperation, PrepareStageState, PreparedArtifact,
|
PolicyDelivery, PrefixRule, PrepareOperation, PrepareStageState, PreparedArtifact,
|
||||||
PreparedLowerOwnership, ProcessOwnershipCommitAck, ProcessOwnershipMessageVersion,
|
PreparedLowerOwnership, ProcessOwnershipCommitAck, ProcessOwnershipMessageVersion,
|
||||||
ProtocolCredential, ProtocolCredentialRole, ProtocolPublicationIdentity, PtyIdentity,
|
ProtocolCredential, ProtocolCredentialRole, ProtocolPublicationIdentity, PtyIdentity,
|
||||||
PublicationOwnership, ResolvedAutoApprovalRule, ResolvedVolume, Resolver, ResultStream,
|
PublicationOwnership, ResolvedAutoApprovalRule, ResolvedNetworkReadinessV1, ResolvedVolume,
|
||||||
SandboxCredentialBundle, SandboxOwnership, Session, SessionBinding, SessionClose, SessionExit,
|
Resolver, ResultStream, SandboxCredentialBundle, SandboxOwnership, Session, SessionBinding,
|
||||||
SessionExitOutcome, SessionRegistrationRequest, SessionRegistrationResponse,
|
SessionClose, SessionExit, SessionExitOutcome, SessionRegistrationRequest,
|
||||||
SessionRequestCapability, SessionTerminal, SessionTerminalEvent, SessionTerminalMessage,
|
SessionRegistrationResponse, SessionRequestCapability, SessionTerminal, SessionTerminalEvent,
|
||||||
SessionTerminalState, SignedProtocolRecord, StagedProcessOwnership, StateAnchorOwnership,
|
SessionTerminalMessage, SessionTerminalState, SignedProtocolRecord, StagedProcessOwnership,
|
||||||
StorageCatalog, StoreMetadata, TerminalInput, TerminalOutput, TerminalResize, TerminalSignal,
|
StateAnchorOwnership, StorageCatalog, StoreMetadata, TerminalInput, TerminalOutput,
|
||||||
TerminalSignalKind, TlsCredential, TlsRole, TrustManifest, TrustedHostConfigurationV1,
|
TerminalResize, TerminalSignal, TerminalSignalKind, TlsCredential, TlsRole, TrustManifest,
|
||||||
|
TrustedHostConfigurationV1,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait SchemaType {
|
pub trait SchemaType {
|
||||||
|
|
@ -133,6 +134,7 @@ schema_types!(
|
||||||
"EnvironmentReference" => EnvironmentReference,
|
"EnvironmentReference" => EnvironmentReference,
|
||||||
"ResolvedVolume" => ResolvedVolume,
|
"ResolvedVolume" => ResolvedVolume,
|
||||||
"ResolvedAutoApprovalRule" => ResolvedAutoApprovalRule,
|
"ResolvedAutoApprovalRule" => ResolvedAutoApprovalRule,
|
||||||
|
"ResolvedNetworkReadinessV1" => ResolvedNetworkReadinessV1,
|
||||||
"DomainStore" => DomainStore,
|
"DomainStore" => DomainStore,
|
||||||
"StorageCatalog" => StorageCatalog,
|
"StorageCatalog" => StorageCatalog,
|
||||||
"PrepareStageState" => PrepareStageState;
|
"PrepareStageState" => PrepareStageState;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use lazaret_core::configuration::{
|
||||||
use lazaret_core::identity::HostPath;
|
use lazaret_core::identity::HostPath;
|
||||||
use lazaret_core::records::{
|
use lazaret_core::records::{
|
||||||
ConfigurationFingerprintRecordV1, EnvironmentReference, ResolvedAutoApprovalRule,
|
ConfigurationFingerprintRecordV1, EnvironmentReference, ResolvedAutoApprovalRule,
|
||||||
|
ResolvedDnsProbeTargetV1, ResolvedNetworkProbeTargetV1, ResolvedNetworkReadinessV1,
|
||||||
ResolvedVolume, VolumeAccess, VolumeKind,
|
ResolvedVolume, VolumeAccess, VolumeKind,
|
||||||
};
|
};
|
||||||
use static_assertions::assert_not_impl_any;
|
use static_assertions::assert_not_impl_any;
|
||||||
|
|
@ -107,6 +108,11 @@ volumes = [
|
||||||
{{ host = "{{profile_dir}}/agent-state", container = "/root/.local/state/agent", kind = "directory", access = "read-write", create = true }},
|
{{ host = "{{profile_dir}}/agent-state", container = "/root/.local/state/agent", kind = "directory", access = "read-write", create = true }},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[profiles.default.readiness]
|
||||||
|
denied = [{{ address = "10.2.3.4", port = 443 }}]
|
||||||
|
permitted = [{{ address = "192.0.2.4", port = 443 }}]
|
||||||
|
dns = [{{ resolver = "198.51.100.53", query = "example.invalid" }}]
|
||||||
|
|
||||||
[profiles.default.escalation]
|
[profiles.default.escalation]
|
||||||
enabled = false
|
enabled = false
|
||||||
"#,
|
"#,
|
||||||
|
|
@ -698,6 +704,20 @@ fn digest_vectors_are_framed_and_order_independent() {
|
||||||
}],
|
}],
|
||||||
effective_allow_prefixes: vec![([8, 8, 8, 8], 32), ([1, 1, 1, 1], 32)],
|
effective_allow_prefixes: vec![([8, 8, 8, 8], 32), ([1, 1, 1, 1], 32)],
|
||||||
effective_deny_prefixes: vec![([10, 0, 0, 0], 8)],
|
effective_deny_prefixes: vec![([10, 0, 0, 0], 8)],
|
||||||
|
network_readiness: ResolvedNetworkReadinessV1 {
|
||||||
|
denied: vec![ResolvedNetworkProbeTargetV1 {
|
||||||
|
address: [10, 0, 0, 1],
|
||||||
|
port: 9,
|
||||||
|
}],
|
||||||
|
permitted: vec![ResolvedNetworkProbeTargetV1 {
|
||||||
|
address: [1, 1, 1, 1],
|
||||||
|
port: 443,
|
||||||
|
}],
|
||||||
|
dns: vec![ResolvedDnsProbeTargetV1 {
|
||||||
|
resolver: [8, 8, 8, 8],
|
||||||
|
query: "example.invalid".into(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
escalation_enabled: true,
|
escalation_enabled: true,
|
||||||
auto_approval_rules: vec![ResolvedAutoApprovalRule {
|
auto_approval_rules: vec![ResolvedAutoApprovalRule {
|
||||||
canonical_executable: HostPath::new(b"/usr/bin/true".to_vec()),
|
canonical_executable: HostPath::new(b"/usr/bin/true".to_vec()),
|
||||||
|
|
@ -709,9 +729,9 @@ fn digest_vectors_are_framed_and_order_independent() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
first.as_bytes(),
|
first.as_bytes(),
|
||||||
&[
|
&[
|
||||||
0xd3, 0xba, 0xef, 0x91, 0x21, 0x0f, 0x98, 0x31, 0x6c, 0xea, 0x34, 0xfb, 0x41, 0xbd,
|
0x22, 0x57, 0x11, 0xe7, 0xc9, 0x11, 0xba, 0x8e, 0x08, 0x11, 0xae, 0x56, 0x5b, 0x7d,
|
||||||
0x6c, 0xa5, 0x07, 0xf0, 0xb8, 0x9b, 0x90, 0x77, 0xaf, 0x0a, 0x0e, 0xd0, 0x6a, 0xd6,
|
0x89, 0xb0, 0x73, 0x48, 0xaa, 0x73, 0x87, 0x19, 0x5e, 0xcd, 0xf8, 0xfa, 0xd1, 0x48,
|
||||||
0x46, 0xc7, 0x2d, 0xbf,
|
0xc1, 0x5b, 0x90, 0x57,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
record.persistent_dirs.reverse();
|
record.persistent_dirs.reverse();
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,7 @@ fn trusted_host_configuration_postcard_dispatch_rejects_unknown_schema() {
|
||||||
persistent_dirs: vec![],
|
persistent_dirs: vec![],
|
||||||
allow_egress: vec![],
|
allow_egress: vec![],
|
||||||
deny_egress: vec![],
|
deny_egress: vec![],
|
||||||
|
readiness: NetworkReadinessConfigurationV1::default(),
|
||||||
env: BTreeMap::new(),
|
env: BTreeMap::new(),
|
||||||
volumes: vec![],
|
volumes: vec![],
|
||||||
escalation: EscalationConfigurationV1::default(),
|
escalation: EscalationConfigurationV1::default(),
|
||||||
|
|
|
||||||
|
|
@ -514,6 +514,20 @@ image = "example.invalid/agent:v1"
|
||||||
}],
|
}],
|
||||||
effective_allow_prefixes: vec![([1, 1, 1, 1], 32)],
|
effective_allow_prefixes: vec![([1, 1, 1, 1], 32)],
|
||||||
effective_deny_prefixes: vec![([10, 0, 0, 0], 8)],
|
effective_deny_prefixes: vec![([10, 0, 0, 0], 8)],
|
||||||
|
network_readiness: ResolvedNetworkReadinessV1 {
|
||||||
|
denied: vec![ResolvedNetworkProbeTargetV1 {
|
||||||
|
address: [10, 0, 0, 1],
|
||||||
|
port: 9,
|
||||||
|
}],
|
||||||
|
permitted: vec![ResolvedNetworkProbeTargetV1 {
|
||||||
|
address: [1, 1, 1, 1],
|
||||||
|
port: 443,
|
||||||
|
}],
|
||||||
|
dns: vec![ResolvedDnsProbeTargetV1 {
|
||||||
|
resolver: [8, 8, 8, 8],
|
||||||
|
query: "example.invalid".into(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
escalation_enabled: true,
|
escalation_enabled: true,
|
||||||
auto_approval_rules: vec![ResolvedAutoApprovalRule {
|
auto_approval_rules: vec![ResolvedAutoApprovalRule {
|
||||||
canonical_executable: HostPath::new(b"/usr/bin/true".to_vec()),
|
canonical_executable: HostPath::new(b"/usr/bin/true".to_vec()),
|
||||||
|
|
@ -548,6 +562,23 @@ image = "example.invalid/agent:v1"
|
||||||
},
|
},
|
||||||
&mut seen,
|
&mut seen,
|
||||||
);
|
);
|
||||||
|
verify_shape(
|
||||||
|
ResolvedNetworkReadinessV1 {
|
||||||
|
denied: vec![ResolvedNetworkProbeTargetV1 {
|
||||||
|
address: [10, 0, 0, 1],
|
||||||
|
port: 9,
|
||||||
|
}],
|
||||||
|
permitted: vec![ResolvedNetworkProbeTargetV1 {
|
||||||
|
address: [1, 1, 1, 1],
|
||||||
|
port: 443,
|
||||||
|
}],
|
||||||
|
dns: vec![ResolvedDnsProbeTargetV1 {
|
||||||
|
resolver: [8, 8, 8, 8],
|
||||||
|
query: "example.invalid".into(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
&mut seen,
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(seen.len(), IMPLEMENTED_SERIALIZED_SCHEMA_TYPE_NAMES.len());
|
assert_eq!(seen.len(), IMPLEMENTED_SERIALIZED_SCHEMA_TYPE_NAMES.len());
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,10 @@ volumes = [
|
||||||
{ host = "{profile_dir}/agent-state", container = "/root/.local/state/agent", kind = "directory", access = "read-write", create = true },
|
{ host = "{profile_dir}/agent-state", container = "/root/.local/state/agent", kind = "directory", access = "read-write", create = true },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[profiles.default.readiness]
|
||||||
|
denied = [{ address = "10.20.30.40", port = 443 }]
|
||||||
|
permitted = [{ address = "203.0.113.10", port = 443 }]
|
||||||
|
dns = [{ resolver = "198.51.100.53", query = "example.invalid" }]
|
||||||
|
|
||||||
[profiles.default.escalation]
|
[profiles.default.escalation]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,13 @@ tables, field aliases, and unknown enumeration values.
|
||||||
| profile | `persistent_dirs` | optional list of sandbox paths; defaults empty |
|
| profile | `persistent_dirs` | optional list of sandbox paths; defaults empty |
|
||||||
| profile | `allow_egress` | optional list of IPv4 prefixes; defaults empty |
|
| profile | `allow_egress` | optional list of IPv4 prefixes; defaults empty |
|
||||||
| profile | `deny_egress` | optional list of IPv4 prefixes; defaults empty |
|
| profile | `deny_egress` | optional list of IPv4 prefixes; defaults empty |
|
||||||
|
| profile | `readiness` | required at load for the selected profile; closed denied, permitted, and DNS target table |
|
||||||
| profile | `env` | optional map from sandbox environment name to `env:` reference; defaults empty |
|
| profile | `env` | optional map from sandbox environment name to `env:` reference; defaults empty |
|
||||||
| profile | `volumes` | optional list of volume tables; defaults empty |
|
| profile | `volumes` | optional list of volume tables; defaults empty |
|
||||||
| profile | `escalation` | optional table; defaults to disabled with no rules |
|
| profile | `escalation` | optional table; defaults to disabled with no rules |
|
||||||
|
| `readiness` | `denied` | one to 32 unique `{ address = IPv4, port = nonzero }` targets denied by effective policy |
|
||||||
|
| `readiness` | `permitted` | one to 32 unique `{ address = IPv4, port = nonzero }` targets allowed by effective policy |
|
||||||
|
| `readiness` | `dns` | one to 32 unique `{ resolver = IPv4, query = strict DNS name }` targets |
|
||||||
| volume | `host` | required host path |
|
| volume | `host` | required host path |
|
||||||
| volume | `container` | required sandbox path |
|
| volume | `container` | required sandbox path |
|
||||||
| volume | `kind` | required: `file` or `directory` |
|
| volume | `kind` | required: `file` or `directory` |
|
||||||
|
|
@ -148,6 +152,7 @@ classDiagram
|
||||||
volumes
|
volumes
|
||||||
effective_allow_prefixes
|
effective_allow_prefixes
|
||||||
effective_deny_prefixes
|
effective_deny_prefixes
|
||||||
|
network_readiness
|
||||||
escalation_enabled
|
escalation_enabled
|
||||||
auto_approval_rules
|
auto_approval_rules
|
||||||
}
|
}
|
||||||
|
|
@ -174,6 +179,13 @@ classDiagram
|
||||||
allow_extra_args
|
allow_extra_args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ResolvedNetworkReadinessV1 {
|
||||||
|
<<serialized>>
|
||||||
|
denied
|
||||||
|
permitted
|
||||||
|
dns
|
||||||
|
}
|
||||||
|
|
||||||
class ConfinedPathPlan {
|
class ConfinedPathPlan {
|
||||||
<<runtime>>
|
<<runtime>>
|
||||||
retained_ancestor
|
retained_ancestor
|
||||||
|
|
@ -190,6 +202,7 @@ classDiagram
|
||||||
ConfigurationFingerprintRecordV1 --> EnvironmentReference
|
ConfigurationFingerprintRecordV1 --> EnvironmentReference
|
||||||
ConfigurationFingerprintRecordV1 --> ResolvedVolume
|
ConfigurationFingerprintRecordV1 --> ResolvedVolume
|
||||||
ConfigurationFingerprintRecordV1 --> ResolvedAutoApprovalRule
|
ConfigurationFingerprintRecordV1 --> ResolvedAutoApprovalRule
|
||||||
|
ConfigurationFingerprintRecordV1 --> ResolvedNetworkReadinessV1
|
||||||
ResolvedDomainConfiguration --> ConfinedPathPlan
|
ResolvedDomainConfiguration --> ConfinedPathPlan
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue