diff --git a/CHANGELOG.md b/CHANGELOG.md index ac6b368..9eec7f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed +- **`container-runtime` now allows graceful container stop and restart.** The + profile claimed to support `DockerRunLauncher` lifecycle calls, but + `POST /containers/{id}/stop` was still denied, so `DockerRunLauncher.terminate()` + could not cancel runs. Stop and restart (stop+start) are now granted; kill, + pause, unpause, rename, update, resize, attach, and commit remain denied. + ## [0.3.1] — 2026-08-13 Security and correctness fixes from a full code review, plus a repaired diff --git a/README.md b/README.md index a2ad100..a153d96 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ the host. |---|---| | `default` | Read-only endpoints on GET and HEAD; mutation blocked | | `read-only` | The same reads, with every write method denied on every endpoint | -| `container-runtime` | Launching and managing containers, with create bodies inspected | +| `container-runtime` | Launching and managing containers, with create bodies inspected (graceful stop and restart included) | | `none` | Nothing — your allowlist is the whole policy | `read-only` is a standard descriptive name for Docker API consumers that need inspection only. `container-runtime` is the generic profile for trusted workload orchestrators such as Dagster's official `DockerRunLauncher`. @@ -215,7 +215,7 @@ This proxy **reduces** the blast radius of socket exposure. It does not eliminat ### Container Runtime Profile -Use the opt-in `container-runtime` profile for Docker-backed orchestrators. It supports `DockerRunLauncher` lifecycle calls, custom containers, image builds and loads, bind/volume mounts, network connections, `docker exec`, and wait/log/archive operations. Privileged mode, capability changes, host devices, and namespace overrides remain blocked. +Use the opt-in `container-runtime` profile for Docker-backed orchestrators. It supports `DockerRunLauncher` lifecycle calls, custom containers, image builds and loads, bind/volume mounts, network connections, `docker exec`, and wait/log/archive operations. Graceful termination (`POST /containers/{id}/stop`) and restart are included for orchestrator lifecycle (restart is stop+start); kill, pause, unpause, rename, update, resize, attach, and commit remain denied. Privileged mode, capability changes, host devices, and namespace overrides remain blocked. ```bash DOCKER_PROXY_PROFILE=container-runtime docker-socket-proxy diff --git a/src/security.rs b/src/security.rs index bf3d7e2..ca5476d 100644 --- a/src/security.rs +++ b/src/security.rs @@ -141,6 +141,8 @@ const MUTATING_ENDPOINTS: &[&str] = &[ const RUNTIME_ENDPOINTS: &[&str] = &[ "/containers/create", "/containers/*/start", + "/containers/*/stop", + "/containers/*/restart", "/containers/*/exec", "/containers/*/wait", "/containers/*/archive", @@ -225,6 +227,8 @@ impl SecurityFilter { endpoint.as_str(), "/containers/create" | "/containers/*/start" + | "/containers/*/stop" + | "/containers/*/restart" | "/containers/*/exec" | "/containers/*/wait" | "/containers/*" @@ -644,6 +648,33 @@ mod tests { assert!(f.check("POST", "/images/load").is_ok()); assert!(f.check("POST", "/containers/abc/wait").is_ok()); assert!(f.check("DELETE", "/containers/abc").is_ok()); + // POST /containers/{id}/stop is the DockerRunLauncher terminate path. + assert!(f.check("POST", "/containers/abc/stop").is_ok()); + // restart is stop+start, within the launcher lifecycle. + assert!(f.check("POST", "/containers/abc/restart").is_ok()); + } + + #[test] + fn container_runtime_profile_keeps_destructive_ops_denied() { + let f = SecurityFilter::for_profile(&SecurityProfile::ContainerRuntime); + for (method, path) in [ + ("POST", "/containers/abc/kill"), + ("POST", "/containers/abc/pause"), + ("POST", "/containers/abc/unpause"), + ("POST", "/containers/abc/rename"), + ("POST", "/containers/abc/update"), + ("POST", "/containers/abc/resize"), + ("POST", "/containers/abc/attach"), + ("POST", "/commit"), + ] { + assert!( + f.check(method, path).is_err(), + "{method} {path} stays denied for container-runtime" + ); + } + // stop and restart (stop+start) are granted for orchestrator lifecycle. + assert!(f.check("POST", "/containers/abc/stop").is_ok()); + assert!(f.check("POST", "/containers/abc/restart").is_ok()); } #[test]