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
128 changes: 128 additions & 0 deletions crates/secure-container-runtime/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,4 +677,132 @@ mod tests {
assert_eq!(config.resources.memory_bytes, 2 * 1024 * 1024 * 1024);
assert_eq!(config.network.ports.get(&8080), Some(&0));
}

#[test]
fn test_config_builder_all_methods() {
let mut env_vars = HashMap::new();
env_vars.insert("VAR1".into(), "val1".into());
env_vars.insert("VAR2".into(), "val2".into());

let config = ContainerConfigBuilder::new("alpine:latest", "ch1", "owner1")
.name("test")
.cmd(vec!["sh".into(), "-c".into(), "echo hello".into()])
.env("KEY", "value")
.envs(env_vars)
.working_dir("/app")
.memory(1024 * 1024 * 1024) // 1GB
.cpu(0.5)
.pids(128)
.network_mode(NetworkMode::Bridge)
.port(8080, 8080)
.allow_internet(true)
.mount("/tmp/data", "/data", false)
.mount_readonly("/tmp/config", "/config")
.label("version", "1.0")
.build();

assert_eq!(config.name, Some("test".into()));
assert_eq!(config.cmd, Some(vec!["sh".into(), "-c".into(), "echo hello".into()]));
assert_eq!(config.env.get("KEY"), Some(&"value".into()));
assert_eq!(config.env.get("VAR1"), Some(&"val1".into()));
assert_eq!(config.working_dir, Some("/app".into()));
assert_eq!(config.resources.memory_bytes, 1024 * 1024 * 1024);
assert_eq!(config.resources.cpu_cores, 0.5);
assert_eq!(config.resources.pids_limit, 128);
assert_eq!(config.network.mode, NetworkMode::Bridge);
assert_eq!(config.network.ports.get(&8080), Some(&8080));
assert_eq!(config.network.allow_internet, true);
assert_eq!(config.mounts.len(), 2);
assert_eq!(config.mounts[0].read_only, false);
assert_eq!(config.mounts[1].read_only, true);
assert_eq!(config.labels.get("version"), Some(&"1.0".into()));
}

#[test]
fn test_config_builder_memory_gb() {
let config = ContainerConfigBuilder::new("test:1", "ch1", "owner1")
.memory_gb(4.5)
.build();

assert_eq!(config.resources.memory_bytes, (4.5 * 1024.0 * 1024.0 * 1024.0) as i64);
}

#[test]
fn test_secure_container_client_new() {
let client = SecureContainerClient::new("/custom/path.sock");
assert_eq!(client.socket_path, "/custom/path.sock");
}

#[test]
fn test_secure_container_client_default_path() {
let client = SecureContainerClient::default_path();
assert_eq!(client.socket_path, "/var/run/platform/container-broker.sock");
}

#[test]
fn test_cleanup_result_success() {
let result = CleanupResult {
total: 5,
stopped: 5,
removed: 5,
errors: vec![],
};
assert!(result.success());

let result_with_errors = CleanupResult {
total: 5,
stopped: 4,
removed: 4,
errors: vec!["Failed to stop container".into()],
};
assert!(!result_with_errors.success());
}

#[test]
fn test_oneshot_result_fields() {
let result = OneshotResult {
success: true,
logs: "test output".into(),
duration_secs: 1.5,
timed_out: false,
};

assert!(result.success);
assert_eq!(result.logs, "test output");
assert_eq!(result.duration_secs, 1.5);
assert!(!result.timed_out);
}

#[test]
fn test_challenge_stats_fields() {
let stats = ChallengeStats {
challenge_id: "challenge-123".into(),
total_containers: 10,
running_containers: 7,
stopped_containers: 3,
container_ids: vec!["c1".into(), "c2".into()],
};

assert_eq!(stats.challenge_id, "challenge-123");
assert_eq!(stats.total_containers, 10);
assert_eq!(stats.running_containers, 7);
assert_eq!(stats.stopped_containers, 3);
assert_eq!(stats.container_ids.len(), 2);
}

#[test]
fn test_container_start_result_fields() {
let mut ports = HashMap::new();
ports.insert(8080, 38080);

let result = ContainerStartResult {
container_id: "container-123".into(),
ports: ports.clone(),
endpoint: Some("http://test-container:8080".into()),
};

assert_eq!(result.container_id, "container-123");
assert_eq!(result.ports, ports);
assert_eq!(result.endpoint, Some("http://test-container:8080".into()));
}
}
196 changes: 196 additions & 0 deletions crates/secure-container-runtime/src/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ impl SecurityPolicy {
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;

#[test]
fn test_policy_default() {
Expand Down Expand Up @@ -388,4 +389,199 @@ mod tests {

assert!(policy.validate(&config).is_err());
}

#[test]
fn test_strict_policy() {
let policy = SecurityPolicy::strict();
assert_eq!(policy.allowed_image_prefixes.len(), 1);
assert_eq!(policy.allowed_image_prefixes[0], "ghcr.io/platformnetwork/");
assert!(policy.validate_image("ghcr.io/platformnetwork/test:latest").is_ok());
assert!(policy.validate_image("docker.io/test:latest").is_err());
}

#[test]
fn test_development_policy() {
let policy = SecurityPolicy::development();
assert!(policy.allowed_mount_prefixes.contains(&"/workspace/".to_string()));
}

#[test]
fn test_allow_all_images() {
let mut policy = SecurityPolicy::strict();
assert!(!policy.allowed_image_prefixes.is_empty());

policy = policy.allow_all_images();
assert!(policy.allowed_image_prefixes.is_empty());
assert!(policy.validate_image("any:image").is_ok());
}

#[test]
fn test_with_image_prefix() {
let policy = SecurityPolicy::default()
.with_image_prefix("docker.io/myorg/");

assert!(policy.allowed_image_prefixes.contains(&"docker.io/myorg/".to_string()));
}

#[test]
fn test_validate_missing_owner_id() {
let policy = SecurityPolicy::default();
let config = ContainerConfig {
image: "test:latest".to_string(),
challenge_id: "challenge-1".to_string(),
owner_id: "".to_string(),
..Default::default()
};

assert!(policy.validate(&config).is_err());
}

#[test]
fn test_validate_resources_excessive_cpu() {
let policy = SecurityPolicy::default();
let resources = ResourceLimits {
memory_bytes: 1024 * 1024 * 1024,
cpu_cores: 10.0, // Exceeds max_cpu_cores (4.0)
pids_limit: 100,
disk_quota_bytes: 0,
};

assert!(policy.validate_resources(&resources).is_err());
}

#[test]
fn test_validate_resources_excessive_pids() {
let policy = SecurityPolicy::default();
let resources = ResourceLimits {
memory_bytes: 1024 * 1024 * 1024,
cpu_cores: 1.0,
pids_limit: 10000, // Exceeds max_pids (512)
disk_quota_bytes: 0,
};

assert!(policy.validate_resources(&resources).is_err());
}

#[test]
fn test_validate_mounts_run_docker_sock() {
let policy = SecurityPolicy::default();
let mounts = vec![MountConfig {
source: "/run/docker.sock".to_string(),
target: "/var/run/docker.sock".to_string(),
read_only: true,
}];

assert!(policy.validate_mounts(&mounts).is_err());
}

#[test]
fn test_validate_mounts_forbidden_paths() {
let policy = SecurityPolicy::default();

let forbidden_mounts = vec![
"/etc/passwd",
"/etc/shadow",
"/etc/sudoers",
"/root",
"/home",
"/proc",
"/sys",
"/dev",
];

for path in forbidden_mounts {
let mounts = vec![MountConfig {
source: path.to_string(),
target: "/mnt/test".to_string(),
read_only: true,
}];
assert!(policy.validate_mounts(&mounts).is_err(), "Should block mount: {}", path);
}
}

#[test]
fn test_validate_mounts_allowed_prefixes() {
let policy = SecurityPolicy::default();

let allowed_mounts = vec![
"/tmp/data",
"/var/lib/platform/challenge-1",
"/var/lib/docker/volumes/vol1",
];

for path in allowed_mounts {
let mounts = vec![MountConfig {
source: path.to_string(),
target: "/mnt/test".to_string(),
read_only: true,
}];
assert!(policy.validate_mounts(&mounts).is_ok(), "Should allow mount: {}", path);
}
}

#[test]
fn test_validate_mounts_docker_sock_in_path() {
let policy = SecurityPolicy::default();
let mounts = vec![MountConfig {
source: "/tmp/docker.sock".to_string(),
target: "/app/docker.sock".to_string(),
read_only: true,
}];

// Should be blocked due to containing "docker.sock"
assert!(policy.validate_mounts(&mounts).is_err());
}

#[test]
fn test_validate_mounts_empty_list() {
let policy = SecurityPolicy::default();
assert!(policy.validate_mounts(&vec![]).is_ok());
}

#[test]
fn test_validate_network() {
let policy = SecurityPolicy::default();

let network = NetworkConfig {
mode: NetworkMode::Isolated,
ports: HashMap::new(),
allow_internet: false,
};
assert!(policy.validate_network(&network).is_ok());

let network_with_internet = NetworkConfig {
mode: NetworkMode::Bridge,
ports: HashMap::new(),
allow_internet: true,
};
assert!(policy.validate_network(&network_with_internet).is_ok());
}

#[test]
fn test_check_container_limit() {
let policy = SecurityPolicy::default();

assert!(policy.check_container_limit("challenge-1", 50).is_ok());
assert!(policy.check_container_limit("challenge-1", 99).is_ok());
assert!(policy.check_container_limit("challenge-1", 100).is_err());
assert!(policy.check_container_limit("challenge-1", 150).is_err());
}

#[test]
fn test_check_owner_limit() {
let policy = SecurityPolicy::default();

assert!(policy.check_owner_limit("owner-1", 100).is_ok());
assert!(policy.check_owner_limit("owner-1", 199).is_ok());
assert!(policy.check_owner_limit("owner-1", 200).is_err());
assert!(policy.check_owner_limit("owner-1", 300).is_err());
}

#[test]
fn test_forbidden_mount_paths_default() {
let policy = SecurityPolicy::default();
assert!(policy.forbidden_mount_paths.contains("/var/run/docker.sock"));
assert!(policy.forbidden_mount_paths.contains("/run/docker.sock"));
assert!(policy.forbidden_mount_paths.contains("/etc/passwd"));
}
}
Loading