Refuse inexact production selector authorities
This commit is contained in:
parent
bc04112c77
commit
4cf7a5e595
4 changed files with 230 additions and 34 deletions
|
|
@ -13,6 +13,7 @@ use lazaret_core::identity::{
|
||||||
use lazaret_core::network::{DnsQueryExpectation, ReachabilityExpectation};
|
use lazaret_core::network::{DnsQueryExpectation, ReachabilityExpectation};
|
||||||
use lazaret_core::records::{DurableDecision, Generation, IpAddress, PreparedArtifact};
|
use lazaret_core::records::{DurableDecision, Generation, IpAddress, PreparedArtifact};
|
||||||
use lazaret_linux::filesystem::namespace_identity;
|
use lazaret_linux::filesystem::namespace_identity;
|
||||||
|
use lazaret_linux::platform::{PlatformObservation, check_platform};
|
||||||
use lazaret_prepared_lower::{
|
use lazaret_prepared_lower::{
|
||||||
IdMap, IdMapRange, OciRegistryTransport, Platform, PreparedLowerStore, RegistryCredentials,
|
IdMap, IdMapRange, OciRegistryTransport, Platform, PreparedLowerStore, RegistryCredentials,
|
||||||
};
|
};
|
||||||
|
|
@ -48,6 +49,7 @@ pub struct InstalledProductPaths<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProductionSelection {
|
pub struct ProductionSelection {
|
||||||
|
store: Arc<HostCoordinatorDomainStore>,
|
||||||
domain_id: DomainId,
|
domain_id: DomainId,
|
||||||
configuration_fingerprint: ConfigurationFingerprint,
|
configuration_fingerprint: ConfigurationFingerprint,
|
||||||
catalog: Catalog,
|
catalog: Catalog,
|
||||||
|
|
@ -68,7 +70,6 @@ impl ProductionSelection {
|
||||||
/// Refuses a configuration other than the exact one used for selection.
|
/// Refuses a configuration other than the exact one used for selection.
|
||||||
pub fn cold_start_inputs<'a>(
|
pub fn cold_start_inputs<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
store: Arc<HostCoordinatorDomainStore>,
|
|
||||||
configuration: &'a ResolvedDomainConfiguration,
|
configuration: &'a ResolvedDomainConfiguration,
|
||||||
) -> Result<ProductionColdStartInputs<'a>, ProductionSelectionError> {
|
) -> Result<ProductionColdStartInputs<'a>, ProductionSelectionError> {
|
||||||
if configuration.domain_id() != self.domain_id
|
if configuration.domain_id() != self.domain_id
|
||||||
|
|
@ -77,7 +78,7 @@ impl ProductionSelection {
|
||||||
return Err(failure("selected cold start does not match configuration"));
|
return Err(failure("selected cold start does not match configuration"));
|
||||||
}
|
}
|
||||||
Ok(ProductionColdStartInputs {
|
Ok(ProductionColdStartInputs {
|
||||||
store,
|
store: Arc::clone(&self.store),
|
||||||
catalog: &self.catalog,
|
catalog: &self.catalog,
|
||||||
configuration,
|
configuration,
|
||||||
generation: self.generation.clone(),
|
generation: self.generation.clone(),
|
||||||
|
|
@ -105,9 +106,30 @@ impl ProductionSelection {
|
||||||
/// changed Storage Root authority, exhausted identities, and configuration-policy mismatch.
|
/// changed Storage Root authority, exhausted identities, and configuration-policy mismatch.
|
||||||
pub async fn select_production_cold_start(
|
pub async fn select_production_cold_start(
|
||||||
configuration: &ResolvedDomainConfiguration,
|
configuration: &ResolvedDomainConfiguration,
|
||||||
store: &HostCoordinatorDomainStore,
|
store: Arc<HostCoordinatorDomainStore>,
|
||||||
product_paths: InstalledProductPaths<'_>,
|
product_paths: InstalledProductPaths<'_>,
|
||||||
) -> Result<ProductionSelection, ProductionSelectionError> {
|
) -> Result<ProductionSelection, ProductionSelectionError> {
|
||||||
|
let kernel_release = std::fs::read_to_string("/proc/sys/kernel/osrelease").map_err(failure)?;
|
||||||
|
select_production_cold_start_for_platform(
|
||||||
|
configuration,
|
||||||
|
store,
|
||||||
|
product_paths,
|
||||||
|
&PlatformObservation {
|
||||||
|
operating_system: std::env::consts::OS,
|
||||||
|
architecture: std::env::consts::ARCH,
|
||||||
|
kernel_release: kernel_release.trim(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn select_production_cold_start_for_platform(
|
||||||
|
configuration: &ResolvedDomainConfiguration,
|
||||||
|
store: Arc<HostCoordinatorDomainStore>,
|
||||||
|
product_paths: InstalledProductPaths<'_>,
|
||||||
|
observation: &PlatformObservation<'_>,
|
||||||
|
) -> Result<ProductionSelection, ProductionSelectionError> {
|
||||||
|
check_platform(observation).map_err(failure)?;
|
||||||
let escalation_client = open_installed_executable(product_paths.escalation_client)?;
|
let escalation_client = open_installed_executable(product_paths.escalation_client)?;
|
||||||
let sandbox_init = open_installed_executable(product_paths.sandbox_init)?;
|
let sandbox_init = open_installed_executable(product_paths.sandbox_init)?;
|
||||||
let catalog = Catalog::under_storage_root(configuration.storage_root()).map_err(failure)?;
|
let catalog = Catalog::under_storage_root(configuration.storage_root()).map_err(failure)?;
|
||||||
|
|
@ -122,7 +144,7 @@ pub async fn select_production_cold_start(
|
||||||
.pull_confined(
|
.pull_confined(
|
||||||
&configuration.fingerprint_record().image,
|
&configuration.fingerprint_record().image,
|
||||||
&RegistryCredentials::Anonymous,
|
&RegistryCredentials::Anonymous,
|
||||||
&host_platform()?,
|
&Platform::linux("amd64"),
|
||||||
configuration.storage_root(),
|
configuration.storage_root(),
|
||||||
random_id()?,
|
random_id()?,
|
||||||
)
|
)
|
||||||
|
|
@ -137,16 +159,7 @@ pub async fn select_production_cold_start(
|
||||||
.map_err(failure)?;
|
.map_err(failure)?;
|
||||||
image.cleanup().map_err(failure)?;
|
image.cleanup().map_err(failure)?;
|
||||||
|
|
||||||
let next_generation = store
|
let next_generation = next_generation(&store)?;
|
||||||
.snapshot()
|
|
||||||
.map_err(failure)?
|
|
||||||
.generations
|
|
||||||
.iter()
|
|
||||||
.map(|generation| generation.generation_id.get())
|
|
||||||
.max()
|
|
||||||
.unwrap_or(0)
|
|
||||||
.checked_add(1)
|
|
||||||
.ok_or_else(|| failure("generation identity exhausted"))?;
|
|
||||||
let (coordinator_id, network_sidecar_id, sandbox_id) = distinct_generation_ids()?;
|
let (coordinator_id, network_sidecar_id, sandbox_id) = distinct_generation_ids()?;
|
||||||
let generation = Generation {
|
let generation = Generation {
|
||||||
domain_id: configuration.domain_id(),
|
domain_id: configuration.domain_id(),
|
||||||
|
|
@ -161,6 +174,7 @@ pub async fn select_production_cold_start(
|
||||||
let (resolver_addresses, network_probe, reachability) =
|
let (resolver_addresses, network_probe, reachability) =
|
||||||
select_network_readiness(configuration, &generation, namespace);
|
select_network_readiness(configuration, &generation, namespace);
|
||||||
Ok(ProductionSelection {
|
Ok(ProductionSelection {
|
||||||
|
store,
|
||||||
domain_id: configuration.domain_id(),
|
domain_id: configuration.domain_id(),
|
||||||
configuration_fingerprint: configuration.configuration_fingerprint(),
|
configuration_fingerprint: configuration.configuration_fingerprint(),
|
||||||
catalog,
|
catalog,
|
||||||
|
|
@ -175,6 +189,19 @@ pub async fn select_production_cold_start(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_generation(store: &HostCoordinatorDomainStore) -> Result<u64, ProductionSelectionError> {
|
||||||
|
store
|
||||||
|
.snapshot()
|
||||||
|
.map_err(failure)?
|
||||||
|
.generations
|
||||||
|
.iter()
|
||||||
|
.map(|generation| generation.generation_id.get())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.checked_add(1)
|
||||||
|
.ok_or_else(|| failure("generation identity exhausted"))
|
||||||
|
}
|
||||||
|
|
||||||
fn select_network_readiness(
|
fn select_network_readiness(
|
||||||
configuration: &ResolvedDomainConfiguration,
|
configuration: &ResolvedDomainConfiguration,
|
||||||
generation: &Generation,
|
generation: &Generation,
|
||||||
|
|
@ -270,16 +297,6 @@ fn rootless_id_map() -> IdMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn host_platform() -> Result<Platform, ProductionSelectionError> {
|
|
||||||
match std::env::consts::ARCH {
|
|
||||||
"x86_64" => Ok(Platform::linux("amd64")),
|
|
||||||
"aarch64" => Ok(Platform::linux("arm64")),
|
|
||||||
architecture => Err(failure(format!(
|
|
||||||
"host architecture {architecture} has no selected OCI platform mapping"
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn distinct_generation_ids()
|
fn distinct_generation_ids()
|
||||||
-> Result<(CoordinatorId, NetworkSidecarId, SandboxId), ProductionSelectionError> {
|
-> Result<(CoordinatorId, NetworkSidecarId, SandboxId), ProductionSelectionError> {
|
||||||
for _ in 0..8 {
|
for _ in 0..8 {
|
||||||
|
|
@ -307,12 +324,20 @@ fn random_id() -> Result<[u8; 32], ProductionSelectionError> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fs;
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::future::Future;
|
||||||
|
use std::os::unix::ffi::OsStringExt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::task::{Context, Poll, Waker};
|
||||||
|
|
||||||
use lazaret_core::configuration::{ConfigurationLoadRequest, load_trusted_configuration};
|
use lazaret_core::configuration::{ConfigurationLoadRequest, load_trusted_configuration};
|
||||||
use lazaret_core::identity::{DeviceId, InodeId, NamespaceIdentity};
|
use lazaret_core::identity::{DeviceId, InodeId, NamespaceIdentity};
|
||||||
|
use lazaret_core::records::Domain;
|
||||||
|
use lazaret_linux::filesystem::try_exclusive_flock;
|
||||||
use lazaret_runtime_supervisor::sandbox::production_sandbox_layout;
|
use lazaret_runtime_supervisor::sandbox::production_sandbox_layout;
|
||||||
|
|
||||||
|
use crate::domain_store::DomainStoreMutation;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -370,7 +395,108 @@ mod tests {
|
||||||
assert_ne!(coordinator.as_bytes(), network.as_bytes());
|
assert_ne!(coordinator.as_bytes(), network.as_bytes());
|
||||||
assert_ne!(coordinator.as_bytes(), sandbox.as_bytes());
|
assert_ne!(coordinator.as_bytes(), sandbox.as_bytes());
|
||||||
assert_ne!(network.as_bytes(), sandbox.as_bytes());
|
assert_ne!(network.as_bytes(), sandbox.as_bytes());
|
||||||
assert!(host_platform().is_ok());
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unsupported_platforms_refuse_before_installed_or_storage_side_effects() {
|
||||||
|
for observation in [
|
||||||
|
PlatformObservation {
|
||||||
|
operating_system: "linux",
|
||||||
|
architecture: "x86_64",
|
||||||
|
kernel_release: "6.8.14",
|
||||||
|
},
|
||||||
|
PlatformObservation {
|
||||||
|
operating_system: "linux",
|
||||||
|
architecture: "aarch64",
|
||||||
|
kernel_release: "6.9.0",
|
||||||
|
},
|
||||||
|
] {
|
||||||
|
let tree = tempfile::tempdir().unwrap();
|
||||||
|
let configuration = configuration(tree.path());
|
||||||
|
let store = domain_store(&configuration);
|
||||||
|
let mut future = Box::pin(select_production_cold_start_for_platform(
|
||||||
|
&configuration,
|
||||||
|
store,
|
||||||
|
InstalledProductPaths {
|
||||||
|
escalation_client: Path::new("/missing-escalation-client"),
|
||||||
|
sandbox_init: Path::new("/missing-sandbox-init"),
|
||||||
|
},
|
||||||
|
&observation,
|
||||||
|
));
|
||||||
|
let mut context = Context::from_waker(Waker::noop());
|
||||||
|
let Poll::Ready(result) = future.as_mut().poll(&mut context) else {
|
||||||
|
panic!("platform refusal performed asynchronous work");
|
||||||
|
};
|
||||||
|
assert!(result.is_err());
|
||||||
|
let storage = tree.path().join("data");
|
||||||
|
assert!(!storage.join("storage-catalog.lock").exists());
|
||||||
|
assert!(!storage.join("catalog.redb").exists());
|
||||||
|
assert!(!storage.join("prepared-lower").exists());
|
||||||
|
assert!(!storage.join("image-transports").exists());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generation_selection_and_cold_start_retain_one_exact_domain_store() {
|
||||||
|
let tree = tempfile::tempdir().unwrap();
|
||||||
|
let configuration = configuration(tree.path());
|
||||||
|
let first_root = tree.path().join("store-a");
|
||||||
|
let second_root = tree.path().join("store-b");
|
||||||
|
fs::create_dir(&first_root).unwrap();
|
||||||
|
fs::create_dir(&second_root).unwrap();
|
||||||
|
let first = domain_store_at(&configuration, &first_root);
|
||||||
|
let second = domain_store_at(&configuration, &second_root);
|
||||||
|
second
|
||||||
|
.apply(&[DomainStoreMutation::PutGeneration(Generation {
|
||||||
|
domain_id: configuration.domain_id(),
|
||||||
|
generation_id: GenerationId::new(1),
|
||||||
|
configuration_fingerprint: configuration.configuration_fingerprint(),
|
||||||
|
coordinator_id: CoordinatorId::new([1; 32]),
|
||||||
|
network_sidecar_id: NetworkSidecarId::new([2; 32]),
|
||||||
|
sandbox_id: SandboxId::new([3; 32]),
|
||||||
|
durable_decision: DurableDecision::None,
|
||||||
|
})])
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(next_generation(&first).unwrap(), 1);
|
||||||
|
assert_eq!(next_generation(&second).unwrap(), 2);
|
||||||
|
assert!(!Arc::ptr_eq(&first, &second));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn domain_store(
|
||||||
|
configuration: &ResolvedDomainConfiguration,
|
||||||
|
) -> Arc<HostCoordinatorDomainStore> {
|
||||||
|
let root = PathBuf::from(std::ffi::OsString::from_vec(
|
||||||
|
configuration
|
||||||
|
.persistent_domain_root()
|
||||||
|
.canonical_label()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
));
|
||||||
|
domain_store_at(configuration, &root)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn domain_store_at(
|
||||||
|
configuration: &ResolvedDomainConfiguration,
|
||||||
|
root: &Path,
|
||||||
|
) -> Arc<HostCoordinatorDomainStore> {
|
||||||
|
let lock = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.open(root.join("domain.lock"))
|
||||||
|
.unwrap();
|
||||||
|
Arc::new(
|
||||||
|
HostCoordinatorDomainStore::open(
|
||||||
|
try_exclusive_flock(lock).unwrap(),
|
||||||
|
&root.join("domain.redb"),
|
||||||
|
&Domain {
|
||||||
|
domain_id: configuration.domain_id(),
|
||||||
|
profile_name: configuration.selected_profile().to_owned(),
|
||||||
|
canonical_workspace: configuration.workspace().canonical_label().clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configuration(root: &Path) -> ResolvedDomainConfiguration {
|
fn configuration(root: &Path) -> ResolvedDomainConfiguration {
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ impl PreparedLowerStore {
|
||||||
Err(error) => return Err(error.into()),
|
Err(error) => return Err(error.into()),
|
||||||
}
|
}
|
||||||
let root = open_directory_at(&storage_root, Path::new("prepared-lower"))?;
|
let root = open_directory_at(&storage_root, Path::new("prepared-lower"))?;
|
||||||
|
require_owned_directory(&root)?;
|
||||||
Self::initialize_retained(root, catalog)
|
Self::initialize_retained(root, catalog)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,7 +102,8 @@ impl PreparedLowerStore {
|
||||||
Ok(()) | Err(rustix::io::Errno::EXIST) => {}
|
Ok(()) | Err(rustix::io::Errno::EXIST) => {}
|
||||||
Err(error) => return Err(error.into()),
|
Err(error) => return Err(error.into()),
|
||||||
}
|
}
|
||||||
open_directory_at(&store.root, Path::new(name))?;
|
let directory = open_directory_at(&store.root, Path::new(name))?;
|
||||||
|
require_owned_directory(&directory)?;
|
||||||
}
|
}
|
||||||
open_lock_at(&store.root)?;
|
open_lock_at(&store.root)?;
|
||||||
Ok(store)
|
Ok(store)
|
||||||
|
|
@ -472,6 +474,14 @@ impl PreparedLowerStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn require_owned_directory(directory: &File) -> Result<()> {
|
||||||
|
let stat = rustix::fs::fstat(directory)?;
|
||||||
|
if stat.st_uid != rustix::process::getuid().as_raw() || stat.st_mode & 0o7777 != 0o700 {
|
||||||
|
bail!("fixed prepared-lower directory must be owned by the invoking user with mode 0700");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
struct Identities {
|
struct Identities {
|
||||||
artifact_id: PreparedArtifactId,
|
artifact_id: PreparedArtifactId,
|
||||||
source: SourceIdentity,
|
source: SourceIdentity,
|
||||||
|
|
@ -685,3 +695,20 @@ fn hex(bytes: &[u8]) -> String {
|
||||||
fn secure_resolve() -> ResolveFlags {
|
fn secure_resolve() -> ResolveFlags {
|
||||||
ResolveFlags::BENEATH | ResolveFlags::NO_SYMLINKS | ResolveFlags::NO_MAGICLINKS
|
ResolveFlags::BENEATH | ResolveFlags::NO_SYMLINKS | ResolveFlags::NO_MAGICLINKS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod fixed_directory_tests {
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prepared_lower_root_requires_invoking_owner_and_exact_private_mode() {
|
||||||
|
let directory = tempfile::tempdir().unwrap();
|
||||||
|
std::fs::set_permissions(directory.path(), std::fs::Permissions::from_mode(0o777)).unwrap();
|
||||||
|
let root = File::open(directory.path()).unwrap();
|
||||||
|
assert!(require_owned_directory(&root).is_err());
|
||||||
|
std::fs::set_permissions(directory.path(), std::fs::Permissions::from_mode(0o700)).unwrap();
|
||||||
|
assert!(require_owned_directory(&root).is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -355,15 +355,14 @@ impl LayoutWriter {
|
||||||
staging_id,
|
staging_id,
|
||||||
} => {
|
} => {
|
||||||
let storage = File::from(storage_root.open_authority()?);
|
let storage = File::from(storage_root.open_authority()?);
|
||||||
create_directory(&storage, "image-transports")?;
|
let parent = Arc::new(create_directory(&storage, "image-transports")?);
|
||||||
let parent = Arc::new(open_directory(&storage, Path::new("image-transports"))?);
|
|
||||||
let name = hex(&staging_id);
|
let name = hex(&staging_id);
|
||||||
mkdirat(&parent, name.as_str(), Mode::from_raw_mode(0o700))?;
|
mkdirat(&parent, name.as_str(), Mode::from_raw_mode(0o700))?;
|
||||||
let root = Arc::new(open_directory(&parent, Path::new(&name))?);
|
let root = Arc::new(open_directory(&parent, Path::new(&name))?);
|
||||||
|
require_owned_directory(&root)?;
|
||||||
let stat = fstat(&root)?;
|
let stat = fstat(&root)?;
|
||||||
create_directory(&root, "blobs")?;
|
let blobs = create_directory(&root, "blobs")?;
|
||||||
let blobs = open_directory(&root, Path::new("blobs"))?;
|
drop(create_directory(&blobs, "sha256")?);
|
||||||
create_directory(&blobs, "sha256")?;
|
|
||||||
write_relative_atomic(
|
write_relative_atomic(
|
||||||
&root,
|
&root,
|
||||||
Path::new("oci-layout"),
|
Path::new("oci-layout"),
|
||||||
|
|
@ -522,12 +521,21 @@ impl LayoutWriter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_directory(parent: &File, path: &str) -> Result<()> {
|
fn create_directory(parent: &File, path: &str) -> Result<File> {
|
||||||
match mkdirat(parent, path, Mode::from_raw_mode(0o700)) {
|
match mkdirat(parent, path, Mode::from_raw_mode(0o700)) {
|
||||||
Ok(()) | Err(rustix::io::Errno::EXIST) => {}
|
Ok(()) | Err(rustix::io::Errno::EXIST) => {}
|
||||||
Err(error) => return Err(error.into()),
|
Err(error) => return Err(error.into()),
|
||||||
}
|
}
|
||||||
drop(open_directory(parent, Path::new(path))?);
|
let directory = open_directory(parent, Path::new(path))?;
|
||||||
|
require_owned_directory(&directory)?;
|
||||||
|
Ok(directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn require_owned_directory(directory: &File) -> Result<()> {
|
||||||
|
let stat = fstat(directory)?;
|
||||||
|
if stat.st_uid != rustix::process::getuid().as_raw() || stat.st_mode & 0o7777 != 0o700 {
|
||||||
|
bail!("fixed image-transport directory must be owned by the invoking user with mode 0700");
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -695,11 +703,30 @@ fn hex(bytes: &[u8]) -> String {
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
use oci_client::client::ClientProtocol;
|
use oci_client::client::ClientProtocol;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preexisting_transport_parents_require_exact_private_mode() {
|
||||||
|
for name in ["image-transports", "blobs", "sha256"] {
|
||||||
|
let tree = tempfile::tempdir().unwrap();
|
||||||
|
std::fs::create_dir(tree.path().join(name)).unwrap();
|
||||||
|
std::fs::set_permissions(
|
||||||
|
tree.path().join(name),
|
||||||
|
std::fs::Permissions::from_mode(0o777),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let parent = File::open(tree.path()).unwrap();
|
||||||
|
assert!(
|
||||||
|
create_directory(&parent, name).is_err(),
|
||||||
|
"accepted wrong mode for {name}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn refuses_mismatched_and_incomplete_platforms() {
|
fn refuses_mismatched_and_incomplete_platforms() {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,22 @@ impl Fixture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preexisting_fixed_children_require_exact_private_mode() {
|
||||||
|
for name in ["artifacts", "stages", "quarantine"] {
|
||||||
|
let fixture = Fixture::new();
|
||||||
|
std::fs::set_permissions(
|
||||||
|
fixture.root.join(name),
|
||||||
|
std::fs::Permissions::from_mode(0o777),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(
|
||||||
|
PreparedLowerStore::initialize(&fixture.root, fixture.catalog.clone()).is_err(),
|
||||||
|
"accepted wrong mode for {name}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Entry<'a> {
|
struct Entry<'a> {
|
||||||
path: &'a str,
|
path: &'a str,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue