Rust implementation of OCI Distribution Spec with granular access control
at main 356 lines 9.0 kB view raw
1// Docker client integration tests 2// These tests require Docker to be installed and running 3// Enabled with --features docker-tests 4 5#![cfg(feature = "docker-tests")] 6 7mod common; 8 9use common::*; 10use serial_test::serial; 11use std::process::Command; 12 13fn docker_available() -> bool { 14 Command::new("docker") 15 .arg("version") 16 .output() 17 .map(|o| o.status.success()) 18 .unwrap_or(false) 19} 20 21fn docker_login(registry: &str, username: &str, password: &str) -> bool { 22 let output = Command::new("docker") 23 .args(["login", registry, "-u", username, "-p", password]) 24 .output() 25 .expect("Failed to run docker login"); 26 27 output.status.success() 28} 29 30fn docker_logout(registry: &str) { 31 let _ = Command::new("docker").args(["logout", registry]).output(); 32} 33 34fn docker_tag(source: &str, target: &str) -> bool { 35 let output = Command::new("docker") 36 .args(["tag", source, target]) 37 .output() 38 .expect("Failed to run docker tag"); 39 40 output.status.success() 41} 42 43fn docker_push(image: &str) -> bool { 44 let output = Command::new("docker") 45 .args(["push", image]) 46 .output() 47 .expect("Failed to run docker push"); 48 49 output.status.success() 50} 51 52fn docker_pull(image: &str) -> bool { 53 let output = Command::new("docker") 54 .args(["pull", image]) 55 .output() 56 .expect("Failed to run docker pull"); 57 58 output.status.success() 59} 60 61fn docker_rmi(image: &str) { 62 let _ = Command::new("docker").args(["rmi", "-f", image]).output(); 63} 64 65#[test] 66#[serial] 67fn test_docker_login_valid_credentials() { 68 if !docker_available() { 69 println!("Docker not available, skipping test"); 70 return; 71 } 72 73 let mut server = TestServer::new(); 74 server.start(); 75 76 let registry = format!("127.0.0.1:{}", server.port); 77 78 assert!(docker_login(&registry, "admin", "admin")); 79 80 docker_logout(&registry); 81} 82 83#[test] 84#[serial] 85fn test_docker_login_invalid_credentials() { 86 if !docker_available() { 87 println!("Docker not available, skipping test"); 88 return; 89 } 90 91 let mut server = TestServer::new(); 92 server.start(); 93 94 let registry = format!("127.0.0.1:{}", server.port); 95 96 assert!(!docker_login(&registry, "invalid", "invalid")); 97} 98 99#[test] 100#[serial] 101fn test_docker_push_pull_image() { 102 if !docker_available() { 103 println!("Docker not available, skipping test"); 104 return; 105 } 106 107 let mut server = TestServer::new(); 108 server.start(); 109 110 let registry = format!("127.0.0.1:{}", server.port); 111 let image_name = format!("{}/test/alpine:latest", registry); 112 113 // Login 114 assert!(docker_login(&registry, "admin", "admin")); 115 116 // Pull a small base image 117 assert!(docker_pull("alpine:latest")); 118 119 // Tag for local registry 120 assert!(docker_tag("alpine:latest", &image_name)); 121 122 // Push to local registry 123 assert!(docker_push(&image_name)); 124 125 // Remove local images 126 docker_rmi(&image_name); 127 docker_rmi("alpine:latest"); 128 129 // Pull from local registry 130 assert!(docker_pull(&image_name)); 131 132 // Cleanup 133 docker_rmi(&image_name); 134 docker_logout(&registry); 135} 136 137#[test] 138#[serial] 139fn test_docker_push_requires_authentication() { 140 if !docker_available() { 141 println!("Docker not available, skipping test"); 142 return; 143 } 144 145 let mut server = TestServer::new(); 146 server.start(); 147 148 let registry = format!("127.0.0.1:{}", server.port); 149 let image_name = format!("{}/test/alpine:latest", registry); 150 151 // Don't login - push should fail 152 docker_pull("alpine:latest"); 153 docker_tag("alpine:latest", &image_name); 154 155 assert!(!docker_push(&image_name)); 156 157 // Cleanup 158 docker_rmi(&image_name); 159 docker_rmi("alpine:latest"); 160} 161 162#[test] 163#[serial] 164fn test_docker_push_with_limited_permissions() { 165 if !docker_available() { 166 println!("Docker not available, skipping test"); 167 return; 168 } 169 170 let mut server = TestServer::new(); 171 server.start(); 172 173 let registry = format!("127.0.0.1:{}", server.port); 174 let allowed_image = format!("{}/test/allowed:latest", registry); 175 let forbidden_image = format!("{}/forbidden/denied:latest", registry); 176 177 // Login as writer (has access to test/*) 178 assert!(docker_login(&registry, "writer", "writer")); 179 180 // Pull base image 181 assert!(docker_pull("alpine:latest")); 182 183 // Push to allowed repo should succeed 184 docker_tag("alpine:latest", &allowed_image); 185 assert!(docker_push(&allowed_image)); 186 187 // Push to forbidden repo should fail 188 docker_tag("alpine:latest", &forbidden_image); 189 assert!(!docker_push(&forbidden_image)); 190 191 // Cleanup 192 docker_rmi(&allowed_image); 193 docker_rmi(&forbidden_image); 194 docker_rmi("alpine:latest"); 195 docker_logout(&registry); 196} 197 198#[test] 199#[serial] 200fn test_docker_multi_layer_image() { 201 if !docker_available() { 202 println!("Docker not available, skipping test"); 203 return; 204 } 205 206 let mut server = TestServer::new(); 207 server.start(); 208 209 let registry = format!("127.0.0.1:{}", server.port); 210 let image_name = format!("{}/test/nginx:latest", registry); 211 212 // Login 213 assert!(docker_login(&registry, "admin", "admin")); 214 215 // Pull multi-layer image 216 assert!(docker_pull("nginx:alpine")); 217 218 // Tag for local registry 219 assert!(docker_tag("nginx:alpine", &image_name)); 220 221 // Push to local registry 222 assert!(docker_push(&image_name)); 223 224 // Remove local copy 225 docker_rmi(&image_name); 226 docker_rmi("nginx:alpine"); 227 228 // Pull back from local registry 229 assert!(docker_pull(&image_name)); 230 231 // Cleanup 232 docker_rmi(&image_name); 233 docker_logout(&registry); 234} 235 236#[test] 237#[serial] 238fn test_docker_manifest_inspect() { 239 if !docker_available() { 240 println!("Docker not available, skipping test"); 241 return; 242 } 243 244 let mut server = TestServer::new(); 245 server.start(); 246 247 let registry = format!("127.0.0.1:{}", server.port); 248 let image_name = format!("{}/test/alpine:latest", registry); 249 250 // Login and push image 251 assert!(docker_login(&registry, "admin", "admin")); 252 assert!(docker_pull("alpine:latest")); 253 assert!(docker_tag("alpine:latest", &image_name)); 254 assert!(docker_push(&image_name)); 255 256 // Inspect manifest 257 let output = Command::new("docker") 258 .args(["manifest", "inspect", &image_name]) 259 .output() 260 .expect("Failed to run docker manifest inspect"); 261 262 assert!(output.status.success()); 263 let manifest_json = String::from_utf8_lossy(&output.stdout); 264 assert!(manifest_json.contains("schemaVersion")); 265 266 // Cleanup 267 docker_rmi(&image_name); 268 docker_rmi("alpine:latest"); 269 docker_logout(&registry); 270} 271 272#[test] 273#[serial] 274fn test_docker_concurrent_operations() { 275 if !docker_available() { 276 println!("Docker not available, skipping test"); 277 return; 278 } 279 280 let mut server = TestServer::new(); 281 server.start(); 282 283 let registry = format!("127.0.0.1:{}", server.port); 284 285 // Login 286 assert!(docker_login(&registry, "admin", "admin")); 287 288 // Pull base images 289 assert!(docker_pull("alpine:latest")); 290 assert!(docker_pull("busybox:latest")); 291 292 // Tag both for local registry 293 let alpine_name = format!("{}/test/alpine:latest", registry); 294 let busybox_name = format!("{}/test/busybox:latest", registry); 295 296 assert!(docker_tag("alpine:latest", &alpine_name)); 297 assert!(docker_tag("busybox:latest", &busybox_name)); 298 299 // Push both (simulates concurrent operations) 300 assert!(docker_push(&alpine_name)); 301 assert!(docker_push(&busybox_name)); 302 303 // Verify both can be pulled 304 docker_rmi(&alpine_name); 305 docker_rmi(&busybox_name); 306 307 assert!(docker_pull(&alpine_name)); 308 assert!(docker_pull(&busybox_name)); 309 310 // Cleanup 311 docker_rmi(&alpine_name); 312 docker_rmi(&busybox_name); 313 docker_rmi("alpine:latest"); 314 docker_rmi("busybox:latest"); 315 docker_logout(&registry); 316} 317 318#[test] 319#[serial] 320fn test_docker_reader_can_pull_only() { 321 if !docker_available() { 322 println!("Docker not available, skipping test"); 323 return; 324 } 325 326 let mut server = TestServer::new(); 327 server.start(); 328 329 let registry = format!("127.0.0.1:{}", server.port); 330 let image_name = format!("{}/test/alpine:latest", registry); 331 332 // First push as admin 333 assert!(docker_login(&registry, "admin", "admin")); 334 assert!(docker_pull("alpine:latest")); 335 assert!(docker_tag("alpine:latest", &image_name)); 336 assert!(docker_push(&image_name)); 337 docker_logout(&registry); 338 339 // Login as reader 340 assert!(docker_login(&registry, "reader", "reader")); 341 342 // Should be able to pull 343 docker_rmi(&image_name); 344 assert!(docker_pull(&image_name)); 345 346 // Should NOT be able to push 347 let new_tag = format!("{}/test/alpine:newtag", registry); 348 docker_tag(&image_name, &new_tag); 349 assert!(!docker_push(&new_tag)); 350 351 // Cleanup 352 docker_rmi(&image_name); 353 docker_rmi(&new_tag); 354 docker_rmi("alpine:latest"); 355 docker_logout(&registry); 356}