P6: strict seccomp allowlist + K8s sandbox driver proven on kind

- images/seccomp/agent-profile.json is now a TRUE ALLOWLIST: Docker's
  default profile (vendored from moby v27.5.1, defaultAction ERRNO) with
  18 syscalls an agent never needs stripped from the allow groups
  (unshare, ptrace, bpf, mount family, setns, module loading,
  perf_event_open, process_vm_*, reboot, quotactl, ...); arch map trimmed
  to x86_64 + aarch64. All 6 Docker kernel assertions still green.
- K8sDriver (tc-sandbox feature 'k8s', kube-rs): one hardened pod per
  sandbox — runAsUser 10001, cap-drop ALL, no-new-privs via
  allowPrivilegeEscalation=false, RuntimeDefault seccomp, read-only
  rootfs with emptyDir /tmp + /home/agent, resource limits, no service
  account token — in a PSS-restricted namespace carrying a default-deny
  NetworkPolicy (applied server-side apply, idempotent). Exec via the API
  server attach channel with exit codes parsed from v1.Status.
- Live suite (feature 'k8s-tests') against a REAL kind cluster: uid /
  CapEff==0 / NoNewPrivs / rootfs probes from inside pods, PSS label +
  deny-all policy asserted via the API, lifecycle. Honest limits in the
  rustdoc: Localhost seccomp profile and CNI-enforced egress are
  per-cluster provisioning (kindnet does not enforce NetworkPolicy).
- rustls 0.23 process provider pinned to ring at driver connect.
- CI: dedicated sandbox-k8s job (helm/kind-action) running the suite.

149 Rust tests + 3 live kind tests; clippy clean including the k8s feature.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-10 09:00:56 -05:00
co-authored by Claude Fable 5
parent 70ec39f696
commit 84c51168be
7 changed files with 1482 additions and 51 deletions
+14
View File
@@ -38,6 +38,20 @@ jobs:
- name: Air-gapped installer verify path - name: Air-gapped installer verify path
run: ./ci/test-install.sh run: ./ci/test-install.sh
sandbox-k8s:
runs-on: ubuntu-latest
needs: gates
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Create kind cluster
uses: helm/kind-action@v1
with:
cluster_name: teamclaw-test
- name: K8s sandbox kernel assertions
run: cargo test -p tc-sandbox --features k8s-tests --test k8s_security
frontend: frontend:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: gates needs: gates
Generated
+359 -6
View File
@@ -281,7 +281,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"rand 0.9.4", "rand 0.9.4",
"rustls", "rustls",
"rustls-native-certs", "rustls-native-certs 0.8.4",
"rustls-pemfile", "rustls-pemfile",
"rustls-pki-types", "rustls-pki-types",
"serde", "serde",
@@ -463,6 +463,16 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.10.1" version = "0.10.1"
@@ -599,6 +609,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "data-encoding"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.10" version = "0.7.10"
@@ -620,6 +636,27 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "derive_more"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@@ -1036,6 +1073,30 @@ dependencies = [
"hashbrown 0.15.5", "hashbrown 0.15.5",
] ]
[[package]]
name = "headers"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
dependencies = [
"base64",
"bytes",
"headers-core",
"http",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
"http",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@@ -1148,6 +1209,26 @@ dependencies = [
"want", "want",
] ]
[[package]]
name = "hyper-http-proxy"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ad4b0a1e37510028bc4ba81d0e38d239c39671b0f0ce9e02dfa93a8133f7c08"
dependencies = [
"bytes",
"futures-util",
"headers",
"http",
"hyper",
"hyper-rustls",
"hyper-util",
"pin-project-lite",
"rustls-native-certs 0.7.3",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]] [[package]]
name = "hyper-named-pipe" name = "hyper-named-pipe"
version = "0.1.0" version = "0.1.0"
@@ -1172,8 +1253,9 @@ dependencies = [
"http", "http",
"hyper", "hyper",
"hyper-util", "hyper-util",
"log",
"rustls", "rustls",
"rustls-native-certs", "rustls-native-certs 0.8.4",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"tower-service", "tower-service",
@@ -1440,6 +1522,97 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "jsonpath-rust"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c00ae348f9f8fd2d09f82a98ca381c60df9e0820d8d79fce43e649b4dc3128b"
dependencies = [
"pest",
"pest_derive",
"regex",
"serde_json",
"thiserror",
]
[[package]]
name = "k8s-openapi"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa60a41b57ae1a0a071af77dbcf89fc9819cfe66edaf2beeb204c34459dcf0b2"
dependencies = [
"base64",
"chrono",
"serde",
"serde_json",
]
[[package]]
name = "kube"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778f98664beaf4c3c11372721e14310d1ae00f5e2d9aabcf8906c881aa4e9f51"
dependencies = [
"k8s-openapi",
"kube-client",
"kube-core",
]
[[package]]
name = "kube-client"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cb276b85b6e94ded00ac8ea2c68fcf4697ea0553cb25fddc35d4a0ab718db8d"
dependencies = [
"base64",
"bytes",
"chrono",
"either",
"futures",
"home",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-http-proxy",
"hyper-rustls",
"hyper-timeout",
"hyper-util",
"jsonpath-rust",
"k8s-openapi",
"kube-core",
"pem",
"rustls",
"secrecy",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
"tokio",
"tokio-tungstenite",
"tokio-util",
"tower",
"tower-http",
"tracing",
]
[[package]]
name = "kube-core"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c56ff45deb0031f2a476017eed60c06872251f271b8387ad8020b8fef60960"
dependencies = [
"chrono",
"derive_more",
"form_urlencoded",
"http",
"k8s-openapi",
"serde",
"serde-value",
"serde_json",
"thiserror",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@@ -1721,12 +1894,27 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "ordered-float"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "parking" name = "parking"
version = "2.2.1" version = "2.2.1"
@@ -1815,6 +2003,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "pem"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
"base64",
"serde_core",
]
[[package]] [[package]]
name = "pem-rfc7468" name = "pem-rfc7468"
version = "0.7.0" version = "0.7.0"
@@ -1830,6 +2028,49 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pest"
version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
dependencies = [
"memchr",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
dependencies = [
"pest",
"sha2",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.13" version = "1.1.13"
@@ -2263,7 +2504,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"quinn", "quinn",
"rustls", "rustls",
"rustls-native-certs", "rustls-native-certs 0.8.4",
"rustls-pki-types", "rustls-pki-types",
"serde", "serde",
"serde_json", "serde_json",
@@ -2360,16 +2601,29 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-native-certs"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
dependencies = [
"openssl-probe 0.1.6",
"rustls-pemfile",
"rustls-pki-types",
"schannel",
"security-framework 2.11.1",
]
[[package]] [[package]]
name = "rustls-native-certs" name = "rustls-native-certs"
version = "0.8.4" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
dependencies = [ dependencies = [
"openssl-probe", "openssl-probe 0.2.1",
"rustls-pki-types", "rustls-pki-types",
"schannel", "schannel",
"security-framework", "security-framework 3.7.0",
] ]
[[package]] [[package]]
@@ -2474,6 +2728,28 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "secrecy"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
dependencies = [
"zeroize",
]
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "3.7.0" version = "3.7.0"
@@ -2481,7 +2757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"core-foundation", "core-foundation 0.10.1",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
"security-framework-sys", "security-framework-sys",
@@ -2513,6 +2789,16 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-value"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
dependencies = [
"ordered-float",
"serde",
]
[[package]] [[package]]
name = "serde_core" name = "serde_core"
version = "1.0.228" version = "1.0.228"
@@ -2621,6 +2907,19 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.14.0",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@@ -3186,6 +3485,9 @@ dependencies = [
"async-trait", "async-trait",
"bollard", "bollard",
"futures", "futures",
"k8s-openapi",
"kube",
"rustls",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
@@ -3436,6 +3738,7 @@ dependencies = [
"libc", "libc",
"mio", "mio",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.61.2", "windows-sys 0.61.2",
@@ -3473,6 +3776,18 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-tungstenite"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.18" version = "0.7.18"
@@ -3592,15 +3907,18 @@ version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [ dependencies = [
"base64",
"bitflags", "bitflags",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
"http-body", "http-body",
"mime",
"pin-project-lite", "pin-project-lite",
"tower", "tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing",
"url", "url",
] ]
@@ -3654,12 +3972,35 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
dependencies = [
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"rand 0.9.4",
"sha1",
"thiserror",
"utf-8",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.20.1" version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "ucd-trie"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]] [[package]]
name = "ulid" name = "ulid"
version = "1.2.1" version = "1.2.1"
@@ -3728,6 +4069,12 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"
@@ -3780,6 +4127,12 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "utf8-zero" name = "utf8-zero"
version = "0.8.1" version = "0.8.1"
+11 -1
View File
@@ -10,12 +10,22 @@ publish.workspace = true
async-trait = "0.1" async-trait = "0.1"
bollard = "0.19" bollard = "0.19"
futures = "0.3" futures = "0.3"
k8s-openapi = { version = "0.25", features = ["latest"], optional = true }
kube = { version = "1", features = ["client", "rustls-tls", "ws"], default-features = false, optional = true }
rustls = { version = "0.23", features = ["ring"], default-features = false, optional = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tokio = { workspace = true }
[features]
# The Kubernetes driver (kube-rs is a heavy dependency tree; the Docker
# driver alone serves dev and the air-gapped target).
k8s = ["dep:kube", "dep:k8s-openapi", "dep:rustls"]
# Live kind-cluster tests for the K8s driver (dedicated CI job).
k8s-tests = ["k8s"]
[dev-dependencies] [dev-dependencies]
tokio = { workspace = true }
[lints] [lints]
workspace = true workspace = true
+247
View File
@@ -0,0 +1,247 @@
//! Kubernetes implementation of the sandbox driver (cloud target): one
//! hardened pod per sandbox in a dedicated namespace with a default-deny
//! NetworkPolicy.
//!
//! Hardening parity with the Docker driver: uid 10001, cap-drop ALL,
//! no-new-privileges, read-only rootfs with emptyDir /tmp and /home/agent,
//! resource limits. Differences, stated honestly: seccomp uses the
//! runtime's `RuntimeDefault` profile (installing our strict allowlist as
//! a `Localhost` profile is per-node provisioning, documented for managed
//! clusters), and kernel-level egress enforcement of the NetworkPolicy
//! requires a NetworkPolicy-capable CNI (kind's default kindnet does not
//! enforce; production clusters with Calico/Cilium do).
use k8s_openapi::api::core::v1::{Namespace, Pod};
use k8s_openapi::api::networking::v1::NetworkPolicy;
use kube::api::{AttachParams, DeleteParams, ObjectMeta, Patch, PatchParams, PostParams};
use kube::Api;
use serde_json::json;
use tokio::io::AsyncReadExt;
use crate::spec::{ExecResult, SandboxHandle, SandboxSpec};
use crate::{SandboxDriver, SandboxError};
fn engine_err(e: impl std::fmt::Display) -> SandboxError {
SandboxError::Engine(e.to_string())
}
pub struct K8sDriver {
client: kube::Client,
namespace: String,
}
impl K8sDriver {
/// Connects via the ambient kubeconfig and ensures the sandbox
/// namespace exists with its default-deny NetworkPolicy.
pub async fn connect(namespace: &str) -> Result<K8sDriver, SandboxError> {
// rustls 0.23 needs a process-level crypto provider when several
// are linked; first caller wins, repeats are harmless.
let _ = rustls::crypto::ring::default_provider().install_default();
let client = kube::Client::try_default().await.map_err(engine_err)?;
let driver = K8sDriver {
client,
namespace: namespace.to_owned(),
};
driver.ensure_namespace().await?;
Ok(driver)
}
async fn ensure_namespace(&self) -> Result<(), SandboxError> {
let namespaces: Api<Namespace> = Api::all(self.client.clone());
let ns = Namespace {
metadata: ObjectMeta {
name: Some(self.namespace.clone()),
labels: Some(
[
// Pod Security Standards: refuse anything that
// tries to relax the hardening below.
(
"pod-security.kubernetes.io/enforce".to_owned(),
"restricted".to_owned(),
),
("app.kubernetes.io/name".to_owned(), "teamclaw".to_owned()),
]
.into(),
),
..Default::default()
},
..Default::default()
};
namespaces
.patch(
&self.namespace,
&PatchParams::apply("teamclaw-sandbox").force(),
&Patch::Apply(&ns),
)
.await
.map_err(engine_err)?;
// Default-deny everything for sandbox pods; the orchestrator talks
// to them via the API server's exec channel, not the pod network.
let policies: Api<NetworkPolicy> = Api::namespaced(self.client.clone(), &self.namespace);
let deny: NetworkPolicy = serde_json::from_value(json!({
"apiVersion": "networking.k8s.io/v1",
"kind": "NetworkPolicy",
"metadata": { "name": "sandbox-default-deny", "namespace": self.namespace },
"spec": {
"podSelector": {},
"policyTypes": ["Ingress", "Egress"]
}
}))
.expect("static policy");
policies
.patch(
"sandbox-default-deny",
&PatchParams::apply("teamclaw-sandbox").force(),
&Patch::Apply(&deny),
)
.await
.map_err(engine_err)?;
Ok(())
}
fn pod_spec(&self, spec: &SandboxSpec) -> Pod {
serde_json::from_value(json!({
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": spec.name,
"namespace": self.namespace,
"labels": { "app.kubernetes.io/name": "teamclaw-sandbox" }
},
"spec": {
"restartPolicy": "Never",
"automountServiceAccountToken": false,
"securityContext": {
"runAsNonRoot": true,
"runAsUser": 10001,
"runAsGroup": 10001,
"seccompProfile": { "type": "RuntimeDefault" }
},
"containers": [{
"name": "sandbox",
"image": spec.image,
"imagePullPolicy": "Never",
"command": ["sleep", "infinity"],
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": { "drop": ["ALL"] },
"readOnlyRootFilesystem": true
},
"resources": {
"limits": {
"memory": format!("{}", spec.memory_bytes),
"cpu": format!("{}m", spec.nano_cpus / 1_000_000)
}
},
"volumeMounts": [
{ "name": "tmp", "mountPath": "/tmp" },
{ "name": "home", "mountPath": "/home/agent" }
]
}],
"volumes": [
{ "name": "tmp", "emptyDir": { "sizeLimit": "64Mi" } },
{ "name": "home", "emptyDir": { "sizeLimit": "256Mi" } }
]
}
}))
.expect("static pod spec")
}
fn pods(&self) -> Api<Pod> {
Api::namespaced(self.client.clone(), &self.namespace)
}
}
#[async_trait::async_trait]
impl SandboxDriver for K8sDriver {
async fn provision(&self, spec: &SandboxSpec) -> Result<SandboxHandle, SandboxError> {
let pods = self.pods();
pods.create(&PostParams::default(), &self.pod_spec(spec))
.await
.map_err(engine_err)?;
// Wait until Running (image is preloaded, so this is fast).
for _ in 0..120 {
let pod = pods.get(&spec.name).await.map_err(engine_err)?;
let phase = pod.status.as_ref().and_then(|s| s.phase.clone());
match phase.as_deref() {
Some("Running") => {
return Ok(SandboxHandle {
id: spec.name.clone(),
name: spec.name.clone(),
})
}
Some("Failed") => return Err(engine_err("sandbox pod failed to start")),
_ => tokio::time::sleep(std::time::Duration::from_millis(500)).await,
}
}
Err(engine_err("sandbox pod never reached Running"))
}
async fn exec(&self, handle: &SandboxHandle, cmd: &[&str]) -> Result<ExecResult, SandboxError> {
let pods = self.pods();
let params = AttachParams::default().stdout(true).stderr(true);
let mut attached = pods
.exec(&handle.name, cmd.to_vec(), &params)
.await
.map_err(engine_err)?;
let mut stdout = String::new();
let mut stderr = String::new();
if let Some(mut out) = attached.stdout() {
out.read_to_string(&mut stdout).await.map_err(engine_err)?;
}
if let Some(mut err) = attached.stderr() {
err.read_to_string(&mut stderr).await.map_err(engine_err)?;
}
let status = attached.take_status();
attached.join().await.map_err(engine_err)?;
// Exit code travels in the v1.Status the API server sends on close.
let exit_code = match status {
Some(rx) => match rx.await {
Some(s) if s.status.as_deref() == Some("Success") => 0,
Some(s) => s
.details
.and_then(|d| {
d.causes.unwrap_or_default().into_iter().find_map(|c| {
(c.reason.as_deref() == Some("ExitCode"))
.then(|| c.message.and_then(|m| m.parse().ok()))
.flatten()
})
})
.unwrap_or(1),
None => 0,
},
None => 0,
};
Ok(ExecResult {
exit_code,
stdout,
stderr,
})
}
async fn destroy(&self, handle: &SandboxHandle) -> Result<(), SandboxError> {
self.pods()
.delete(&handle.name, &DeleteParams::default().grace_period(0))
.await
.map_err(engine_err)?;
Ok(())
}
async fn health(&self, handle: &SandboxHandle) -> Result<bool, SandboxError> {
match self
.pods()
.get_opt(&handle.name)
.await
.map_err(engine_err)?
{
Some(pod) => Ok(pod
.status
.and_then(|s| s.phase)
.is_some_and(|phase| phase == "Running")),
None => Ok(false),
}
}
}
+6 -1
View File
@@ -1,12 +1,17 @@
//! Per-agent sandbox orchestration (spec §15): containers with no root, no //! Per-agent sandbox orchestration (spec §15): containers with no root, no
//! capabilities, a seccomp deny profile, read-only rootfs, and no network. //! capabilities, a seccomp deny profile, read-only rootfs, and no network.
//! One `SandboxDriver` trait; the Docker implementation serves dev and the //! One `SandboxDriver` trait; the Docker implementation serves dev and the
//! air-gapped compose target (the Kubernetes driver lands in P3). //! air-gapped compose target; the Kubernetes driver (feature `k8s`) runs
//! hardened pods in a PSS-restricted, default-deny namespace.
mod docker; mod docker;
#[cfg(feature = "k8s")]
mod k8s;
mod spec; mod spec;
pub use docker::DockerDriver; pub use docker::DockerDriver;
#[cfg(feature = "k8s")]
pub use k8s::K8sDriver;
pub use spec::{ExecResult, SandboxHandle, SandboxSpec}; pub use spec::{ExecResult, SandboxHandle, SandboxSpec};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
+146
View File
@@ -0,0 +1,146 @@
//! The K8s sandbox driver against a REAL kind cluster: the same kernel
//! probes as the Docker suite (uid, capabilities, no-new-privs, rootfs),
//! plus namespace policy objects. Run with `--features k8s-tests` after
//! `kind create cluster --name teamclaw-test` and a `kind load` of the
//! agent image (the harness does both image steps itself).
#![cfg(feature = "k8s-tests")]
use std::process::Command;
use tc_sandbox::{K8sDriver, SandboxDriver, SandboxSpec};
const IMAGE: &str = "teamclaw/agent-base:dev";
const CLUSTER: &str = "teamclaw-test";
const NAMESPACE: &str = "teamclaw-sandboxes-test";
fn ensure_image_in_kind() {
let exists = Command::new("docker")
.args(["image", "inspect", IMAGE])
.output()
.expect("docker available")
.status
.success();
if !exists {
let root = env!("CARGO_MANIFEST_DIR");
let status = Command::new("docker")
.args([
"build",
"-t",
IMAGE,
"-f",
&format!("{root}/../../images/agent-base/Dockerfile"),
&format!("{root}/../../images/agent-base"),
])
.status()
.expect("docker build runs");
assert!(status.success(), "agent-base image build failed");
}
let status = Command::new("kind")
.args(["load", "docker-image", IMAGE, "--name", CLUSTER])
.status()
.expect("kind available");
assert!(status.success(), "kind load failed");
}
async fn spawn(suffix: &str) -> (K8sDriver, tc_sandbox::SandboxHandle) {
ensure_image_in_kind();
let driver = K8sDriver::connect(NAMESPACE)
.await
.expect("cluster reachable");
let spec = SandboxSpec {
name: format!("tc-k8s-{suffix}-{}", std::process::id()),
image: IMAGE.into(),
memory_bytes: 256 * 1024 * 1024,
nano_cpus: 1_000_000_000,
pids_limit: 128,
};
let handle = driver.provision(&spec).await.expect("pod provisions");
(driver, handle)
}
#[tokio::test]
async fn pod_runs_hardened_with_writable_scratch_only() {
let (driver, handle) = spawn("hard").await;
let uid = driver.exec(&handle, &["id", "-u"]).await.unwrap();
assert_eq!(uid.stdout.trim(), "10001", "stderr: {}", uid.stderr);
let caps = driver
.exec(&handle, &["grep", "CapEff", "/proc/self/status"])
.await
.unwrap();
let value = caps.stdout.split_whitespace().last().unwrap_or("");
assert_eq!(u64::from_str_radix(value, 16).unwrap(), 0);
let nnp = driver
.exec(&handle, &["grep", "NoNewPrivs", "/proc/self/status"])
.await
.unwrap();
assert!(nnp.stdout.trim().ends_with('1'), "got: {}", nnp.stdout);
let write_root = driver
.exec(&handle, &["touch", "/etc/owned"])
.await
.unwrap();
assert_ne!(write_root.exit_code, 0, "rootfs must reject writes");
let write_tmp = driver
.exec(&handle, &["touch", "/tmp/scratch"])
.await
.unwrap();
assert_eq!(write_tmp.exit_code, 0, "stderr: {}", write_tmp.stderr);
let write_home = driver
.exec(&handle, &["touch", "/home/agent/file"])
.await
.unwrap();
assert_eq!(write_home.exit_code, 0, "stderr: {}", write_home.stderr);
driver.destroy(&handle).await.unwrap();
}
#[tokio::test]
async fn namespace_carries_pss_restricted_and_default_deny_policy() {
let (driver, handle) = spawn("policy").await;
// Assert through the API: PSS label + the deny-all NetworkPolicy.
// (Kernel-level egress enforcement needs a NetworkPolicy-capable CNI;
// kind's default kindnet does not enforce — production clusters do.)
let client = kube::Client::try_default().await.unwrap();
let namespaces: kube::Api<k8s_openapi::api::core::v1::Namespace> =
kube::Api::all(client.clone());
let ns = namespaces.get(NAMESPACE).await.unwrap();
assert_eq!(
ns.metadata
.labels
.unwrap()
.get("pod-security.kubernetes.io/enforce")
.map(String::as_str),
Some("restricted")
);
let policies: kube::Api<k8s_openapi::api::networking::v1::NetworkPolicy> =
kube::Api::namespaced(client, NAMESPACE);
let deny = policies.get("sandbox-default-deny").await.unwrap();
let spec = deny.spec.unwrap();
assert_eq!(
spec.policy_types.unwrap(),
vec!["Ingress".to_owned(), "Egress".to_owned()]
);
assert!(spec.ingress.is_none() && spec.egress.is_none(), "deny-all");
driver.destroy(&handle).await.unwrap();
}
#[tokio::test]
async fn destroy_removes_the_pod_and_health_reflects_it() {
let (driver, handle) = spawn("life").await;
assert!(driver.health(&handle).await.unwrap());
driver.destroy(&handle).await.unwrap();
for _ in 0..60 {
if !driver.health(&handle).await.unwrap() {
return;
}
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
panic!("pod never disappeared");
}
+699 -43
View File
@@ -1,49 +1,705 @@
{ {
"defaultAction": "SCMP_ACT_ALLOW", "_comment": "TeamClaw agent-sandbox seccomp profile: Docker's default ALLOWLIST (vendored from moby v27.5.1) minus syscalls an agent workload never needs \u2014 namespace/mount/trace/key/module/perf surface removed even where capabilities would otherwise permit them. defaultAction ERRNO.",
"archMap": [ "defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
]
},
{
"architecture": "SCMP_ARCH_AARCH64",
"subArchitectures": [
"SCMP_ARCH_ARM"
]
}
],
"syscalls": [
{
"names": [
"accept",
"accept4",
"access",
"adjtimex",
"alarm",
"bind",
"brk",
"cachestat",
"capget",
"capset",
"chdir",
"chmod",
"chown",
"chown32",
"clock_adjtime",
"clock_adjtime64",
"clock_getres",
"clock_getres_time64",
"clock_gettime",
"clock_gettime64",
"clock_nanosleep",
"clock_nanosleep_time64",
"close",
"close_range",
"connect",
"copy_file_range",
"creat",
"dup",
"dup2",
"dup3",
"epoll_create",
"epoll_create1",
"epoll_ctl",
"epoll_ctl_old",
"epoll_pwait",
"epoll_pwait2",
"epoll_wait",
"epoll_wait_old",
"eventfd",
"eventfd2",
"execve",
"execveat",
"exit",
"exit_group",
"faccessat",
"faccessat2",
"fadvise64",
"fadvise64_64",
"fallocate",
"fanotify_mark",
"fchdir",
"fchmod",
"fchmodat",
"fchmodat2",
"fchown",
"fchown32",
"fchownat",
"fcntl",
"fcntl64",
"fdatasync",
"fgetxattr",
"flistxattr",
"flock",
"fork",
"fremovexattr",
"fsetxattr",
"fstat",
"fstat64",
"fstatat64",
"fstatfs",
"fstatfs64",
"fsync",
"ftruncate",
"ftruncate64",
"futex",
"futex_requeue",
"futex_time64",
"futex_wait",
"futex_waitv",
"futex_wake",
"futimesat",
"getcpu",
"getcwd",
"getdents",
"getdents64",
"getegid",
"getegid32",
"geteuid",
"geteuid32",
"getgid",
"getgid32",
"getgroups",
"getgroups32",
"getitimer",
"getpeername",
"getpgid",
"getpgrp",
"getpid",
"getppid",
"getpriority",
"getrandom",
"getresgid",
"getresgid32",
"getresuid",
"getresuid32",
"getrlimit",
"get_robust_list",
"getrusage",
"getsid",
"getsockname",
"getsockopt",
"get_thread_area",
"gettid",
"gettimeofday",
"getuid",
"getuid32",
"getxattr",
"inotify_add_watch",
"inotify_init",
"inotify_init1",
"inotify_rm_watch",
"io_cancel",
"ioctl",
"io_destroy",
"io_getevents",
"io_pgetevents",
"io_pgetevents_time64",
"ioprio_get",
"ioprio_set",
"io_setup",
"io_submit",
"ipc",
"kill",
"landlock_add_rule",
"landlock_create_ruleset",
"landlock_restrict_self",
"lchown",
"lchown32",
"lgetxattr",
"link",
"linkat",
"listen",
"listxattr",
"llistxattr",
"_llseek",
"lremovexattr",
"lseek",
"lsetxattr",
"lstat",
"lstat64",
"madvise",
"map_shadow_stack",
"membarrier",
"memfd_create",
"memfd_secret",
"mincore",
"mkdir",
"mkdirat",
"mknod",
"mknodat",
"mlock",
"mlock2",
"mlockall",
"mmap",
"mmap2",
"mprotect",
"mq_getsetattr",
"mq_notify",
"mq_open",
"mq_timedreceive",
"mq_timedreceive_time64",
"mq_timedsend",
"mq_timedsend_time64",
"mq_unlink",
"mremap",
"msgctl",
"msgget",
"msgrcv",
"msgsnd",
"msync",
"munlock",
"munlockall",
"munmap",
"name_to_handle_at",
"nanosleep",
"newfstatat",
"_newselect",
"open",
"openat",
"openat2",
"pause",
"pidfd_open",
"pidfd_send_signal",
"pipe",
"pipe2",
"pkey_alloc",
"pkey_free",
"pkey_mprotect",
"poll",
"ppoll",
"ppoll_time64",
"prctl",
"pread64",
"preadv",
"preadv2",
"prlimit64",
"process_mrelease",
"pselect6",
"pselect6_time64",
"pwrite64",
"pwritev",
"pwritev2",
"read",
"readahead",
"readlink",
"readlinkat",
"readv",
"recv",
"recvfrom",
"recvmmsg",
"recvmmsg_time64",
"recvmsg",
"remap_file_pages",
"removexattr",
"rename",
"renameat",
"renameat2",
"restart_syscall",
"rmdir",
"rseq",
"rt_sigaction",
"rt_sigpending",
"rt_sigprocmask",
"rt_sigqueueinfo",
"rt_sigreturn",
"rt_sigsuspend",
"rt_sigtimedwait",
"rt_sigtimedwait_time64",
"rt_tgsigqueueinfo",
"sched_getaffinity",
"sched_getattr",
"sched_getparam",
"sched_get_priority_max",
"sched_get_priority_min",
"sched_getscheduler",
"sched_rr_get_interval",
"sched_rr_get_interval_time64",
"sched_setaffinity",
"sched_setattr",
"sched_setparam",
"sched_setscheduler",
"sched_yield",
"seccomp",
"select",
"semctl",
"semget",
"semop",
"semtimedop",
"semtimedop_time64",
"send",
"sendfile",
"sendfile64",
"sendmmsg",
"sendmsg",
"sendto",
"setfsgid",
"setfsgid32",
"setfsuid",
"setfsuid32",
"setgid",
"setgid32",
"setgroups",
"setgroups32",
"setitimer",
"setpgid",
"setpriority",
"setregid",
"setregid32",
"setresgid",
"setresgid32",
"setresuid",
"setresuid32",
"setreuid",
"setreuid32",
"setrlimit",
"set_robust_list",
"setsid",
"setsockopt",
"set_thread_area",
"set_tid_address",
"setuid",
"setuid32",
"setxattr",
"shmat",
"shmctl",
"shmdt",
"shmget",
"shutdown",
"sigaltstack",
"signalfd",
"signalfd4",
"sigprocmask",
"sigreturn",
"socketcall",
"socketpair",
"splice",
"stat",
"stat64",
"statfs",
"statfs64",
"statx",
"symlink",
"symlinkat",
"sync",
"sync_file_range",
"syncfs",
"sysinfo",
"tee",
"tgkill",
"time",
"timer_create",
"timer_delete",
"timer_getoverrun",
"timer_gettime",
"timer_gettime64",
"timer_settime",
"timer_settime64",
"timerfd_create",
"timerfd_gettime",
"timerfd_gettime64",
"timerfd_settime",
"timerfd_settime64",
"times",
"tkill",
"truncate",
"truncate64",
"ugetrlimit",
"umask",
"uname",
"unlink",
"unlinkat",
"utime",
"utimensat",
"utimensat_time64",
"utimes",
"vfork",
"vmsplice",
"wait4",
"waitid",
"waitpid",
"write",
"writev"
],
"action": "SCMP_ACT_ALLOW"
},
{
"names": [
"socket"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{ {
"architecture": "SCMP_ARCH_X86_64", "index": 0,
"subArchitectures": ["SCMP_ARCH_X86", "SCMP_ARCH_X32"] "value": 40,
}, "op": "SCMP_CMP_NE"
{
"architecture": "SCMP_ARCH_AARCH64",
"subArchitectures": ["SCMP_ARCH_ARM"]
} }
], ]
"syscalls": [ },
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{ {
"names": [ "index": 0,
"acct", "value": 0,
"add_key", "op": "SCMP_CMP_EQ"
"bpf",
"clone3",
"delete_module",
"finit_module",
"init_module",
"kexec_file_load",
"kexec_load",
"keyctl",
"mount",
"move_mount",
"open_by_handle_at",
"perf_event_open",
"pivot_root",
"process_vm_readv",
"process_vm_writev",
"ptrace",
"quotactl",
"reboot",
"request_key",
"setns",
"swapoff",
"swapon",
"umount2",
"unshare",
"userfaultfd"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Deny list on top of cap-drop ALL: kernel-facing syscalls an agent workload never needs. P6 hardening replaces this with a strict allowlist profile."
} }
] ]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 8,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 131072,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 131080,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 4294967295,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"sync_file_range2",
"swapcontext"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"ppc64le"
]
}
},
{
"names": [
"arm_fadvise64_64",
"arm_sync_file_range",
"sync_file_range2",
"breakpoint",
"cacheflush",
"set_tls"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"arm",
"arm64"
]
}
},
{
"names": [
"arch_prctl"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"amd64",
"x32"
]
}
},
{
"names": [
"modify_ldt"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"amd64",
"x32",
"x86"
]
}
},
{
"names": [
"s390_pci_mmio_read",
"s390_pci_mmio_write",
"s390_runtime_instr"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"s390",
"s390x"
]
}
},
{
"names": [
"riscv_flush_icache"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"arches": [
"riscv64"
]
}
},
{
"names": [
"clone",
"fanotify_init",
"fsconfig",
"fsmount",
"fsopen",
"fspick",
"lookup_dcookie",
"mount_setattr",
"open_tree",
"quotactl_fd",
"setdomainname",
"sethostname",
"syslog",
"umount"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_ADMIN"
]
}
},
{
"names": [
"clone"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 2114060288,
"op": "SCMP_CMP_MASKED_EQ"
}
],
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
],
"arches": [
"s390",
"s390x"
]
}
},
{
"names": [
"clone"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 1,
"value": 2114060288,
"op": "SCMP_CMP_MASKED_EQ"
}
],
"comment": "s390 parameter ordering for clone is different",
"includes": {
"arches": [
"s390",
"s390x"
]
},
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
]
}
},
{
"names": [
"clone3"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 38,
"excludes": {
"caps": [
"CAP_SYS_ADMIN"
]
}
},
{
"names": [
"chroot"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_CHROOT"
]
}
},
{
"names": [
"kcmp",
"pidfd_getfd",
"process_madvise"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_PTRACE"
]
}
},
{
"names": [
"iopl",
"ioperm"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_RAWIO"
]
}
},
{
"names": [
"settimeofday",
"stime",
"clock_settime",
"clock_settime64"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_TIME"
]
}
},
{
"names": [
"vhangup"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_TTY_CONFIG"
]
}
},
{
"names": [
"get_mempolicy",
"mbind",
"set_mempolicy",
"set_mempolicy_home_node"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYS_NICE"
]
}
},
{
"names": [
"syslog"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"caps": [
"CAP_SYSLOG"
]
}
}
]
} }