Permit subordinate sandbox identity transitions
This commit is contained in:
parent
7aff8e542e
commit
09dae24f52
4 changed files with 66 additions and 17 deletions
|
|
@ -5,6 +5,7 @@ use std::io::{Read, Write};
|
||||||
use std::os::fd::{AsFd, OwnedFd};
|
use std::os::fd::{AsFd, OwnedFd};
|
||||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use lazaret_core::configuration::{ConfinedPathPlan, ResolvedDomainConfiguration, SandboxLayoutV1};
|
use lazaret_core::configuration::{ConfinedPathPlan, ResolvedDomainConfiguration, SandboxLayoutV1};
|
||||||
use lazaret_core::identity::{ConfigurationFingerprint, ContainerId, DomainId, NamespaceIdentity};
|
use lazaret_core::identity::{ConfigurationFingerprint, ContainerId, DomainId, NamespaceIdentity};
|
||||||
|
|
@ -17,7 +18,7 @@ pub use lazaret_sandbox_contract::{
|
||||||
SANDBOX_INIT_DESTINATION, SYS_DESTINATION, TERMINFO_ROOT_DESTINATION, WORKSPACE_DESTINATION,
|
SANDBOX_INIT_DESTINATION, SYS_DESTINATION, TERMINFO_ROOT_DESTINATION, WORKSPACE_DESTINATION,
|
||||||
};
|
};
|
||||||
use oci_spec::runtime::{
|
use oci_spec::runtime::{
|
||||||
Capabilities, LinuxBuilder, LinuxCapabilitiesBuilder, LinuxNamespaceBuilder,
|
Capabilities, Capability, LinuxBuilder, LinuxCapabilitiesBuilder, LinuxNamespaceBuilder,
|
||||||
LinuxNamespaceType, Mount, MountBuilder, ProcessBuilder, RootBuilder, Spec, SpecBuilder,
|
LinuxNamespaceType, Mount, MountBuilder, ProcessBuilder, RootBuilder, Spec, SpecBuilder,
|
||||||
UserBuilder,
|
UserBuilder,
|
||||||
};
|
};
|
||||||
|
|
@ -689,9 +690,7 @@ fn build_sandbox_bundle_from_source(
|
||||||
.linux(linux)
|
.linux(linux)
|
||||||
.build()
|
.build()
|
||||||
.map_err(oci_error)?;
|
.map_err(oci_error)?;
|
||||||
let specification_json = serde_json::to_vec(&specification).map_err(|error| {
|
let specification_json = canonical_specification_json(&specification)?;
|
||||||
SandboxBuildError::new(SandboxBuildErrorKind::JsonEncoding, error.to_string())
|
|
||||||
})?;
|
|
||||||
let bundle_authorization = Sha256::digest(&specification_json).into();
|
let bundle_authorization = Sha256::digest(&specification_json).into();
|
||||||
let command = build_runsc_command(&paths, bundle_authorization);
|
let command = build_runsc_command(&paths, bundle_authorization);
|
||||||
let runtime_sources =
|
let runtime_sources =
|
||||||
|
|
@ -709,6 +708,33 @@ fn build_sandbox_bundle_from_source(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn canonical_specification_json(specification: &Spec) -> Result<Vec<u8>, SandboxBuildError> {
|
||||||
|
let mut value = serde_json::to_value(specification).map_err(|error| {
|
||||||
|
SandboxBuildError::new(SandboxBuildErrorKind::JsonEncoding, error.to_string())
|
||||||
|
})?;
|
||||||
|
let capabilities = value
|
||||||
|
.pointer_mut("/process/capabilities")
|
||||||
|
.and_then(serde_json::Value::as_object_mut)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
SandboxBuildError::new(
|
||||||
|
SandboxBuildErrorKind::JsonEncoding,
|
||||||
|
"OCI process capabilities are missing",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
for set in capabilities.values_mut() {
|
||||||
|
let entries = set.as_array_mut().ok_or_else(|| {
|
||||||
|
SandboxBuildError::new(
|
||||||
|
SandboxBuildErrorKind::JsonEncoding,
|
||||||
|
"OCI process capability set is not an array",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
entries.sort_by(|left, right| left.as_str().cmp(&right.as_str()));
|
||||||
|
}
|
||||||
|
serde_json::to_vec(&value).map_err(|error| {
|
||||||
|
SandboxBuildError::new(SandboxBuildErrorKind::JsonEncoding, error.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
mod mounts;
|
mod mounts;
|
||||||
mod runtime_sources;
|
mod runtime_sources;
|
||||||
mod validation;
|
mod validation;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use super::{
|
use super::{
|
||||||
AtFlags, Capabilities, ContainerId, File, FileIdentity, FileType, HandoffPathType,
|
AtFlags, Capabilities, Capability, ContainerId, File, FileIdentity, FileType, HandoffPathType,
|
||||||
LinuxBuilder, LinuxCapabilitiesBuilder, LinuxNamespaceBuilder, LinuxNamespaceType, Mode, Mount,
|
LazyLock, LinuxBuilder, LinuxCapabilitiesBuilder, LinuxNamespaceBuilder, LinuxNamespaceType,
|
||||||
NETWORK_NAMESPACE_PATH, OFlags, OsString, OwnedFd, Path, PathBuf, ProcessBuilder,
|
Mode, Mount, NETWORK_NAMESPACE_PATH, OFlags, OsString, OwnedFd, Path, PathBuf, ProcessBuilder,
|
||||||
RUNTIME_FILE_DESTINATIONS, Read, ResolveFlags, ResolvedDomainConfiguration, RootProcess,
|
RUNTIME_FILE_DESTINATIONS, Read, ResolveFlags, ResolvedDomainConfiguration, RootProcess,
|
||||||
RunscCommand, RuntimeHandoffSource, SandboxBuildError, SandboxBuildErrorKind,
|
RunscCommand, RuntimeHandoffSource, SandboxBuildError, SandboxBuildErrorKind,
|
||||||
StagedHostTerminal, StatxFlags, UserBuilder, VolumeAccess, WORKSPACE_DESTINATION, bind_mount,
|
StagedHostTerminal, StatxFlags, UserBuilder, VolumeAccess, WORKSPACE_DESTINATION, bind_mount,
|
||||||
|
|
@ -335,12 +335,15 @@ pub(super) fn build_process(
|
||||||
root_process: RootProcess,
|
root_process: RootProcess,
|
||||||
_terminal: Option<&StagedHostTerminal>,
|
_terminal: Option<&StagedHostTerminal>,
|
||||||
) -> Result<oci_spec::runtime::Process, SandboxBuildError> {
|
) -> Result<oci_spec::runtime::Process, SandboxBuildError> {
|
||||||
|
static IDENTITY_TRANSITION_CAPABILITIES: LazyLock<Capabilities> =
|
||||||
|
LazyLock::new(|| Capabilities::from([Capability::Setgid, Capability::Setuid]));
|
||||||
let empty = Capabilities::new();
|
let empty = Capabilities::new();
|
||||||
|
let identity = IDENTITY_TRANSITION_CAPABILITIES.clone();
|
||||||
let capabilities = LinuxCapabilitiesBuilder::default()
|
let capabilities = LinuxCapabilitiesBuilder::default()
|
||||||
.bounding(empty.clone())
|
.bounding(identity.clone())
|
||||||
.effective(empty.clone())
|
.effective(identity.clone())
|
||||||
.inheritable(empty.clone())
|
.inheritable(empty.clone())
|
||||||
.permitted(empty.clone())
|
.permitted(identity)
|
||||||
.ambient(empty)
|
.ambient(empty)
|
||||||
.build()
|
.build()
|
||||||
.map_err(oci_error)?;
|
.map_err(oci_error)?;
|
||||||
|
|
|
||||||
|
|
@ -294,7 +294,11 @@ fn deterministic_snapshot_has_only_private_namespaces_and_fixed_runtime_shape()
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
specification["process"]["capabilities"],
|
specification["process"]["capabilities"],
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"bounding": [], "effective": [], "inheritable": [], "permitted": [], "ambient": []
|
"bounding": ["CAP_SETGID", "CAP_SETUID"],
|
||||||
|
"effective": ["CAP_SETGID", "CAP_SETUID"],
|
||||||
|
"inheritable": [],
|
||||||
|
"permitted": ["CAP_SETGID", "CAP_SETUID"],
|
||||||
|
"ambient": []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ fn run_sandbox_init() -> io::Result<()> {
|
||||||
authenticate_canary(Ipv4Addr::new(10, 137, 55, 2), &generation).is_ok(),
|
authenticate_canary(Ipv4Addr::new(10, 137, 55, 2), &generation).is_ok(),
|
||||||
authenticate_canary(Ipv4Addr::new(10, 137, 55, 3), &generation).is_err(),
|
authenticate_canary(Ipv4Addr::new(10, 137, 55, 3), &generation).is_err(),
|
||||||
ipv6_absent().unwrap_or(false),
|
ipv6_absent().unwrap_or(false),
|
||||||
network_capabilities_empty().unwrap_or(false),
|
network_admin_capability_absent().unwrap_or(false),
|
||||||
route_mutation_refused,
|
route_mutation_refused,
|
||||||
];
|
];
|
||||||
write_observation("initial", &initial)?;
|
write_observation("initial", &initial)?;
|
||||||
|
|
@ -159,15 +159,22 @@ fn read_optional_proc(path: &str) -> io::Result<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn network_capabilities_empty() -> io::Result<bool> {
|
fn network_admin_capability_absent() -> io::Result<bool> {
|
||||||
let status = std::fs::read_to_string("/proc/self/status")?;
|
let status = std::fs::read_to_string("/proc/self/status")?;
|
||||||
|
network_admin_capability_absent_from_status(&status)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn network_admin_capability_absent_from_status(status: &str) -> io::Result<bool> {
|
||||||
let names = ["CapInh:", "CapPrm:", "CapEff:", "CapBnd:", "CapAmb:"];
|
let names = ["CapInh:", "CapPrm:", "CapEff:", "CapBnd:", "CapAmb:"];
|
||||||
Ok(names.into_iter().all(|name| {
|
names.into_iter().try_fold(true, |absent, name| {
|
||||||
status
|
let value = status
|
||||||
.lines()
|
.lines()
|
||||||
.find_map(|line| line.strip_prefix(name))
|
.find_map(|line| line.strip_prefix(name))
|
||||||
.is_some_and(|value| value.trim().bytes().all(|byte| byte == b'0'))
|
.ok_or_else(|| io::Error::other("process capability status is incomplete"))?;
|
||||||
}))
|
let bits = u64::from_str_radix(value.trim(), 16)
|
||||||
|
.map_err(|_| io::Error::other("process capability status is malformed"))?;
|
||||||
|
Ok(absent && bits & (1 << 12) == 0)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route_mutation_refused() -> io::Result<bool> {
|
fn route_mutation_refused() -> io::Result<bool> {
|
||||||
|
|
@ -442,4 +449,13 @@ mod tests {
|
||||||
assert!(group_is_present_except(group, u32::MAX).unwrap());
|
assert!(group_is_present_except(group, u32::MAX).unwrap());
|
||||||
assert!(!group_is_present_except(u32::MAX, u32::MAX).unwrap());
|
assert!(!group_is_present_except(u32::MAX, u32::MAX).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn network_attestation_allows_only_non_network_capabilities() {
|
||||||
|
let setid = "CapInh:\t0000000000000000\nCapPrm:\t00000000000000c0\nCapEff:\t00000000000000c0\nCapBnd:\t00000000000000c0\nCapAmb:\t0000000000000000\n";
|
||||||
|
assert!(network_admin_capability_absent_from_status(setid).unwrap());
|
||||||
|
let network_admin = setid.replace("00000000000000c0", "00000000000010c0");
|
||||||
|
assert!(!network_admin_capability_absent_from_status(&network_admin).unwrap());
|
||||||
|
assert!(network_admin_capability_absent_from_status("CapEff:\t0\n").is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue