Merge pull request #7574 from microsoft/danmihai1/policy

agent: runtime: add Agent Policy feature
This commit is contained in:
Fabiano Fidêncio 2023-08-15 11:29:13 +02:00 committed by GitHub
commit e107d1d94e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1529 additions and 317 deletions

461
src/agent/Cargo.lock generated
View File

@ -163,6 +163,12 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "bincode"
version = "1.3.3"
@ -258,9 +264,12 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.73"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
@ -356,6 +365,16 @@ dependencies = [
"cache-padded",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
@ -460,6 +479,15 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "encoding_rs"
version = "0.8.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "enumflags2"
version = "0.7.5"
@ -550,6 +578,31 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.21"
@ -671,6 +724,25 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "h2"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049"
dependencies = [
"bytes 1.1.0",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util 0.7.8",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.1"
@ -707,6 +779,77 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "http"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
dependencies = [
"bytes 1.1.0",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
"bytes 1.1.0",
"http",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
dependencies = [
"bytes 1.1.0",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes 1.1.0",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.46"
@ -720,6 +863,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.9.1"
@ -770,6 +924,12 @@ dependencies = [
"libc",
]
[[package]]
name = "ipnet"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "ipnetwork"
version = "0.17.0"
@ -815,6 +975,7 @@ dependencies = [
"cgroups-rs",
"clap",
"futures",
"http",
"ipnetwork",
"kata-sys-util",
"kata-types",
@ -826,12 +987,14 @@ dependencies = [
"netlink-sys",
"nix 0.24.2",
"oci",
"openssl",
"opentelemetry",
"procfs",
"prometheus",
"protobuf 3.2.0",
"protocols",
"regex",
"reqwest",
"rtnetlink",
"rustjail",
"scan_fmt",
@ -886,7 +1049,7 @@ name = "kata-types"
version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"base64 0.13.0",
"bitmask-enum",
"byte-unit",
"glob",
@ -973,6 +1136,12 @@ dependencies = [
"regex-automata",
]
[[package]]
name = "matches"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "memchr"
version = "2.5.0"
@ -988,6 +1157,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miniz_oxide"
version = "0.5.3"
@ -1015,6 +1190,24 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "native-tls"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "netlink-packet-core"
version = "0.2.4"
@ -1065,7 +1258,7 @@ dependencies = [
"netlink-packet-core",
"netlink-sys",
"tokio",
"tokio-util",
"tokio-util 0.6.10",
]
[[package]]
@ -1184,6 +1377,60 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
[[package]]
name = "openssl"
version = "0.10.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.16",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "111.27.0+1.1.1v"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
[[package]]
name = "opentelemetry"
version = "0.14.0"
@ -1573,6 +1820,8 @@ dependencies = [
"async-trait",
"oci",
"protobuf 3.2.0",
"serde",
"serde_json",
"ttrpc",
"ttrpc-codegen",
]
@ -1671,6 +1920,43 @@ dependencies = [
"winapi",
]
[[package]]
name = "reqwest"
version = "0.11.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
dependencies = [
"base64 0.21.2",
"bytes 1.1.0",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
]
[[package]]
name = "rlimit"
version = "0.5.4"
@ -1762,12 +2048,44 @@ dependencies = [
"regex",
]
[[package]]
name = "schannel"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.137"
@ -1810,6 +2128,18 @@ dependencies = [
"syn 1.0.98",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serial_test"
version = "0.5.1"
@ -2113,6 +2443,21 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.28.1"
@ -2143,6 +2488,16 @@ dependencies = [
"syn 2.0.16",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.9"
@ -2168,6 +2523,20 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
dependencies = [
"bytes 1.1.0",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
name = "tokio-vsock"
version = "0.3.1"
@ -2190,6 +2559,12 @@ dependencies = [
"serde",
]
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.35"
@ -2279,6 +2654,12 @@ dependencies = [
"tracing-serde",
]
[[package]]
name = "try-lock"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "ttrpc"
version = "0.7.1"
@ -2335,24 +2716,56 @@ dependencies = [
"winapi",
]
[[package]]
name = "unicode-bidi"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-ident"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "url"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.4"
@ -2392,6 +2805,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
@ -2429,6 +2851,18 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.81"
@ -2458,6 +2892,16 @@ version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
[[package]]
name = "web-sys"
version = "0.3.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wepoll-ffi"
version = "0.1.2"
@ -2618,6 +3062,15 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winreg"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [
"winapi",
]
[[package]]
name = "xattr"
version = "0.2.3"

View File

@ -8,7 +8,7 @@ license = "Apache-2.0"
[dependencies]
oci = { path = "../libs/oci" }
rustjail = { path = "rustjail" }
protocols = { path = "../libs/protocols", features = ["async"] }
protocols = { path = "../libs/protocols", features = ["async", "with-serde"] }
lazy_static = "1.3.0"
ttrpc = { version = "0.7.1", features = ["async"], default-features = false }
protobuf = "3.2.0"
@ -67,6 +67,12 @@ serde = { version = "1.0.129", features = ["derive"] }
toml = "0.5.8"
clap = { version = "3.0.1", features = ["derive"] }
# Communication with the OPA service
http = { version = "0.2.8", optional = true }
reqwest = { version = "0.11.14", optional = true }
# The "vendored" feature for openssl is required for musl build
openssl = { version = "0.10.54", features = ["vendored"], optional = true }
[dev-dependencies]
tempfile = "3.1.0"
test-utils = { path = "../libs/test-utils" }
@ -83,6 +89,7 @@ lto = true
[features]
seccomp = ["rustjail/seccomp"]
standard-oci-runtime = ["rustjail/standard-oci-runtime"]
agent-policy = ["http", "openssl", "reqwest"]
[[bin]]
name = "kata-agent"

View File

@ -33,6 +33,14 @@ ifeq ($(SECCOMP),yes)
override EXTRA_RUSTFEATURES += seccomp
endif
##VAR AGENT_POLICY=yes|no define if agent enables the policy feature
AGENT_POLICY := no
# Enable the policy feature of rust build
ifeq ($(AGENT_POLICY),yes)
override EXTRA_RUSTFEATURES += agent-policy
endif
include ../../utils.mk
ifeq ($(ARCH), ppc64le)

View File

@ -73,6 +73,9 @@ use tokio::{
mod rpc;
mod tracer;
#[cfg(feature = "agent-policy")]
mod policy;
cfg_if! {
if #[cfg(target_arch = "s390x")] {
mod ap;
@ -90,6 +93,11 @@ lazy_static! {
AgentConfig::from_cmdline("/proc/cmdline", env::args().collect()).unwrap();
}
#[cfg(feature = "agent-policy")]
lazy_static! {
static ref AGENT_POLICY: Mutex<policy::AgentPolicy> = Mutex::new(AgentPolicy::new());
}
#[derive(Parser)]
// The default clap version info doesn't match our form, so we need to override it
#[clap(global_setting(AppSettings::DisableVersionFlag))]
@ -221,6 +229,27 @@ async fn real_main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let root_span = span!(tracing::Level::TRACE, "root-span");
#[cfg(feature = "agent-policy")]
{
let debug_policy =
config.log_level == slog::Level::Debug || config.log_level == slog::Level::Trace;
if let Err(e) = AGENT_POLICY
.lock()
.await
.initialize(
debug_policy,
"http://localhost:8181/v1",
"/agent_policy",
"/etc/kata-opa/default-policy.rego",
)
.await
{
error!(logger, "Failed to initialize agent policy: {:?}", e);
// Continuing execution without a security policy could be dangerous.
std::process::abort();
}
}
// XXX: Start the root trace transaction.
//
// XXX: Note that *ALL* spans needs to start after this point!!
@ -401,6 +430,9 @@ fn reset_sigpipe() {
use crate::config::AgentConfig;
use std::os::unix::io::{FromRawFd, RawFd};
#[cfg(feature = "agent-policy")]
use crate::policy::AgentPolicy;
#[cfg(test)]
mod tests {
use super::*;

240
src/agent/src/policy.rs Normal file
View File

@ -0,0 +1,240 @@
// Copyright (c) 2023 Microsoft Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use tokio::io::AsyncWriteExt;
use tokio::time::{sleep, Duration};
static EMPTY_JSON_INPUT: &str = "{\"input\":{}}";
static OPA_DATA_PATH: &str = "/data";
static OPA_POLICIES_PATH: &str = "/policies";
static POLICY_LOG_FILE: &str = "/tmp/policy.txt";
/// Convenience macro to obtain the scope logger
macro_rules! sl {
() => {
slog_scope::logger()
};
}
/// Example of HTTP response from OPA: {"result":true}
#[derive(Debug, Serialize, Deserialize)]
struct AllowResponse {
result: bool,
}
/// Singleton policy object.
#[derive(Debug, Default)]
pub struct AgentPolicy {
/// When true policy errors are ignored, for debug purposes.
allow_failures: bool,
/// OPA path used to query if an Agent gRPC request should be allowed.
/// The request name (e.g., CreateContainerRequest) must be added to
/// this path.
query_path: String,
/// OPA path used to add or delete a rego format Policy.
policy_path: String,
/// Client used to connect a single time to the OPA service and reused
/// for all the future communication with OPA.
opa_client: Option<reqwest::Client>,
/// "/tmp/policy.txt" log file for policy activity.
log_file: Option<tokio::fs::File>,
}
impl AgentPolicy {
/// Create AgentPolicy object.
pub fn new() -> Self {
Self {
allow_failures: false,
..Default::default()
}
}
/// Wait for OPA to start and connect to it.
pub async fn initialize(
&mut self,
debug_enabled: bool,
opa_uri: &str,
policy_name: &str,
default_policy: &str,
) -> Result<()> {
if debug_enabled {
self.log_file = Some(
tokio::fs::OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(POLICY_LOG_FILE)
.await?,
);
debug!(sl!(), "policy: log file: {}", POLICY_LOG_FILE);
}
self.query_path = format!("{opa_uri}{OPA_DATA_PATH}{policy_name}/");
self.policy_path = format!("{opa_uri}{OPA_POLICIES_PATH}{policy_name}");
let opa_client = reqwest::Client::builder().http1_only().build()?;
let policy = tokio::fs::read_to_string(default_policy).await?;
// This loop is necessary to get the opa_client connected to the
// OPA service while that service is starting. Future requests to
// OPA are expected to work without retrying, after connecting
// successfully for the first time.
for i in 0..50 {
if i > 0 {
sleep(Duration::from_millis(100)).await;
debug!(sl!(), "policy initialize: PUT failed, retrying");
}
// Set-up the default policy.
if opa_client
.put(&self.policy_path)
.body(policy.clone())
.send()
.await
.is_ok()
{
// Check if requests causing policy errors should actually
// be allowed. That is an insecure configuration but is
// useful for allowing insecure pods to start, then connect to
// them and inspect Guest logs for the root cause of a failure.
if let Ok(allow_failures) = self
.post_query("AllowRequestsFailingPolicy", EMPTY_JSON_INPUT)
.await
{
self.allow_failures = allow_failures;
} else {
// post_query failed so the the default, secure, value
// allow_failures: false will be used.
}
self.opa_client = Some(opa_client);
return Ok(());
}
}
bail!("Failed to connect to OPA")
}
/// Ask OPA to check if an API call should be allowed or not.
pub async fn is_allowed_endpoint(&mut self, ep: &str, request: &str) -> bool {
let post_input = format!("{{\"input\":{request}}}");
self.log_opa_input(ep, &post_input).await;
match self.post_query(ep, &post_input).await {
Err(e) => {
debug!(
sl!(),
"policy: failed to query endpoint {}: {:?}. Returning false.", ep, e
);
false
}
Ok(allowed) => allowed,
}
}
/// Replace the Policy in OPA.
pub async fn set_policy(&mut self, policy: &str) -> Result<()> {
if let Some(opa_client) = &mut self.opa_client {
// Delete the old rules.
opa_client.delete(&self.policy_path).send().await?;
// Put the new rules.
opa_client
.put(&self.policy_path)
.body(policy.to_string())
.send()
.await?;
// Check if requests causing policy errors should actually be allowed.
// That is an insecure configuration but is useful for allowing insecure
// pods to start, then connect to them and inspect Guest logs for the
// root cause of a failure.
self.allow_failures = self
.post_query("AllowRequestsFailingPolicy", EMPTY_JSON_INPUT)
.await?;
Ok(())
} else {
bail!("Agent Policy is not initialized")
}
}
// Post query to OPA.
async fn post_query(&mut self, ep: &str, post_input: &str) -> Result<bool> {
debug!(sl!(), "policy check: {ep}");
if let Some(opa_client) = &mut self.opa_client {
let uri = format!("{}{ep}", &self.query_path);
let response = opa_client
.post(uri)
.body(post_input.to_string())
.send()
.await?;
if response.status() != http::StatusCode::OK {
bail!("policy: POST {} response status {}", ep, response.status());
}
let http_response = response.text().await?;
let opa_response: serde_json::Result<AllowResponse> =
serde_json::from_str(&http_response);
match opa_response {
Ok(resp) => {
if !resp.result {
if self.allow_failures {
warn!(
sl!(),
"policy: POST {} response <{}>. Ignoring error!", ep, http_response
);
return Ok(true);
} else {
error!(sl!(), "policy: POST {} response <{}>", ep, http_response);
}
}
Ok(resp.result)
}
Err(_) => {
warn!(
sl!(),
"policy: endpoint {} not found in policy. Returning false.", ep,
);
Ok(false)
}
}
} else {
bail!("Agent Policy is not initialized")
}
}
async fn log_opa_input(&mut self, ep: &str, input: &str) {
if let Some(log_file) = &mut self.log_file {
match ep {
"StatsContainerRequest" | "ReadStreamRequest" | "SetPolicyRequest" => {
// - StatsContainerRequest and ReadStreamRequest are called
// relatively often, so we're not logging them, to avoid
// growing this log file too much.
// - Confidential Containers Policy documents are relatively
// large, so we're not logging them here, for SetPolicyRequest.
// The Policy text can be obtained directly from the pod YAML.
}
_ => {
let log_entry = format!("[\"ep\":\"{ep}\",{input}],\n\n");
if let Err(e) = log_file.write_all(log_entry.as_bytes()).await {
warn!(sl!(), "policy: log_opa_input: write_all failed: {}", e);
} else if let Err(e) = log_file.flush().await {
warn!(sl!(), "policy: log_opa_input: flush failed: {}", e);
}
}
}
}
}
}

View File

@ -67,6 +67,10 @@ use crate::AGENT_CONFIG;
use crate::trace_rpc_call;
use crate::tracer::extract_carrier_from_ttrpc;
#[cfg(feature = "agent-policy")]
use crate::AGENT_POLICY;
use opentelemetry::global;
use tracing::span;
use tracing_opentelemetry::OpenTelemetrySpanExt;
@ -121,7 +125,7 @@ fn ttrpc_error(code: ttrpc::Code, err: impl std::fmt::Debug) -> ttrpc::Error {
get_rpc_status(code, format!("{:?}", err))
}
fn is_allowed(req: &impl MessageDyn) -> ttrpc::Result<()> {
fn config_allows(req: &impl MessageDyn) -> ttrpc::Result<()> {
if !AGENT_CONFIG.is_allowed_endpoint(req.descriptor_dyn().name()) {
Err(ttrpc_error(
ttrpc::Code::UNIMPLEMENTED,
@ -132,6 +136,35 @@ fn is_allowed(req: &impl MessageDyn) -> ttrpc::Result<()> {
}
}
#[cfg(feature = "agent-policy")]
async fn policy_allows(req: &(impl MessageDyn + serde::Serialize)) -> ttrpc::Result<()> {
let request = serde_json::to_string(req).unwrap();
let mut policy = AGENT_POLICY.lock().await;
if !policy
.is_allowed_endpoint(req.descriptor_dyn().name(), &request)
.await
{
warn!(sl(), "{} is blocked by policy", req.descriptor_dyn().name());
Err(ttrpc_error(
ttrpc::Code::PERMISSION_DENIED,
format!("{} is blocked by policy", req.descriptor_dyn().name()),
))
} else {
Ok(())
}
}
async fn is_allowed(req: &(impl MessageDyn + serde::Serialize)) -> ttrpc::Result<()> {
let res = config_allows(req);
#[cfg(feature = "agent-policy")]
if res.is_ok() {
return policy_allows(req).await;
}
res
}
#[derive(Clone, Debug)]
pub struct AgentService {
sandbox: Arc<Mutex<Sandbox>>,
@ -608,7 +641,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::CreateContainerRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "create_container", req);
is_allowed(&req)?;
is_allowed(&req).await?;
match self.do_create_container(req).await {
Err(e) => Err(ttrpc_error(ttrpc::Code::INTERNAL, e)),
Ok(_) => Ok(Empty::new()),
@ -621,7 +654,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::StartContainerRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "start_container", req);
is_allowed(&req)?;
is_allowed(&req).await?;
match self.do_start_container(req).await {
Err(e) => Err(ttrpc_error(ttrpc::Code::INTERNAL, e)),
Ok(_) => Ok(Empty::new()),
@ -634,7 +667,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::RemoveContainerRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "remove_container", req);
is_allowed(&req)?;
is_allowed(&req).await?;
match self.do_remove_container(req).await {
Err(e) => Err(ttrpc_error(ttrpc::Code::INTERNAL, e)),
Ok(_) => Ok(Empty::new()),
@ -647,7 +680,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::ExecProcessRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "exec_process", req);
is_allowed(&req)?;
is_allowed(&req).await?;
match self.do_exec_process(req).await {
Err(e) => Err(ttrpc_error(ttrpc::Code::INTERNAL, e)),
Ok(_) => Ok(Empty::new()),
@ -660,7 +693,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::SignalProcessRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "signal_process", req);
is_allowed(&req)?;
is_allowed(&req).await?;
match self.do_signal_process(req).await {
Err(e) => Err(ttrpc_error(ttrpc::Code::INTERNAL, e)),
Ok(_) => Ok(Empty::new()),
@ -673,7 +706,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::WaitProcessRequest,
) -> ttrpc::Result<WaitProcessResponse> {
trace_rpc_call!(ctx, "wait_process", req);
is_allowed(&req)?;
is_allowed(&req).await?;
self.do_wait_process(req)
.await
.map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e))
@ -685,7 +718,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::UpdateContainerRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "update_container", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let mut sandbox = self.sandbox.lock().await;
let ctr = sandbox.get_container(&req.container_id).ok_or_else(|| {
@ -711,7 +744,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::StatsContainerRequest,
) -> ttrpc::Result<StatsContainerResponse> {
trace_rpc_call!(ctx, "stats_container", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let mut sandbox = self.sandbox.lock().await;
let ctr = sandbox.get_container(&req.container_id).ok_or_else(|| {
@ -731,7 +764,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::PauseContainerRequest,
) -> ttrpc::Result<protocols::empty::Empty> {
trace_rpc_call!(ctx, "pause_container", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let mut sandbox = self.sandbox.lock().await;
let ctr = sandbox.get_container(req.container_id()).ok_or_else(|| {
@ -753,7 +786,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::ResumeContainerRequest,
) -> ttrpc::Result<protocols::empty::Empty> {
trace_rpc_call!(ctx, "resume_container", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let mut sandbox = self.sandbox.lock().await;
let ctr = sandbox.get_container(req.container_id()).ok_or_else(|| {
@ -775,7 +808,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::RemoveStaleVirtiofsShareMountsRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "remove_stale_virtiofs_share_mounts", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let mount_infos = parse_mount_table("/proc/self/mountinfo")
.map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e))?;
for m in &mount_infos {
@ -797,7 +830,7 @@ impl agent_ttrpc::AgentService for AgentService {
_ctx: &TtrpcContext,
req: protocols::agent::WriteStreamRequest,
) -> ttrpc::Result<WriteStreamResponse> {
is_allowed(&req)?;
is_allowed(&req).await?;
self.do_write_stream(req)
.await
.map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e))
@ -808,7 +841,7 @@ impl agent_ttrpc::AgentService for AgentService {
_ctx: &TtrpcContext,
req: protocols::agent::ReadStreamRequest,
) -> ttrpc::Result<ReadStreamResponse> {
is_allowed(&req)?;
is_allowed(&req).await?;
self.do_read_stream(req, true)
.await
.map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e))
@ -819,7 +852,7 @@ impl agent_ttrpc::AgentService for AgentService {
_ctx: &TtrpcContext,
req: protocols::agent::ReadStreamRequest,
) -> ttrpc::Result<ReadStreamResponse> {
is_allowed(&req)?;
is_allowed(&req).await?;
self.do_read_stream(req, false)
.await
.map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e))
@ -831,7 +864,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::CloseStdinRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "close_stdin", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let cid = req.container_id;
let eid = req.exec_id;
@ -857,7 +890,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::TtyWinResizeRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "tty_win_resize", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let mut sandbox = self.sandbox.lock().await;
let p = sandbox
@ -896,7 +929,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::UpdateInterfaceRequest,
) -> ttrpc::Result<Interface> {
trace_rpc_call!(ctx, "update_interface", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let interface = req.interface.into_option().ok_or_else(|| {
ttrpc_error(
@ -924,7 +957,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::UpdateRoutesRequest,
) -> ttrpc::Result<Routes> {
trace_rpc_call!(ctx, "update_routes", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let new_routes = req.routes.into_option().map(|r| r.Routes).ok_or_else(|| {
ttrpc_error(
@ -961,7 +994,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::UpdateEphemeralMountsRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "update_mounts", req);
is_allowed(&req)?;
is_allowed(&req).await?;
match update_ephemeral_mounts(sl(), &req.storages, &self.sandbox).await {
Ok(_) => Ok(Empty::new()),
@ -978,7 +1011,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: GetIPTablesRequest,
) -> ttrpc::Result<GetIPTablesResponse> {
trace_rpc_call!(ctx, "get_iptables", req);
is_allowed(&req)?;
is_allowed(&req).await?;
info!(sl(), "get_ip_tables: request received");
@ -1017,7 +1050,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: SetIPTablesRequest,
) -> ttrpc::Result<SetIPTablesResponse> {
trace_rpc_call!(ctx, "set_iptables", req);
is_allowed(&req)?;
is_allowed(&req).await?;
info!(sl(), "set_ip_tables request received");
@ -1132,7 +1165,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::ListInterfacesRequest,
) -> ttrpc::Result<Interfaces> {
trace_rpc_call!(ctx, "list_interfaces", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let list = self
.sandbox
@ -1160,7 +1193,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::ListRoutesRequest,
) -> ttrpc::Result<Routes> {
trace_rpc_call!(ctx, "list_routes", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let list = self
.sandbox
@ -1183,7 +1216,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::CreateSandboxRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "create_sandbox", req);
is_allowed(&req)?;
is_allowed(&req).await?;
{
let mut s = self.sandbox.lock().await;
@ -1242,7 +1275,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::DestroySandboxRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "destroy_sandbox", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let mut sandbox = self.sandbox.lock().await;
// destroy all containers, clean up, notify agent to exit etc.
@ -1275,7 +1308,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::AddARPNeighborsRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "add_arp_neighbors", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let neighs = req
.neighbors
@ -1310,7 +1343,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::OnlineCPUMemRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "online_cpu_mem", req);
is_allowed(&req)?;
is_allowed(&req).await?;
let sandbox = self.sandbox.lock().await;
sandbox
@ -1326,7 +1359,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::ReseedRandomDevRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "reseed_random_dev", req);
is_allowed(&req)?;
is_allowed(&req).await?;
random::reseed_rng(req.data.as_slice())
.map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e))?;
@ -1340,7 +1373,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::GuestDetailsRequest,
) -> ttrpc::Result<GuestDetailsResponse> {
trace_rpc_call!(ctx, "get_guest_details", req);
is_allowed(&req)?;
is_allowed(&req).await?;
info!(sl(), "get guest details!");
let mut resp = GuestDetailsResponse::new();
@ -1374,7 +1407,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::MemHotplugByProbeRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "mem_hotplug_by_probe", req);
is_allowed(&req)?;
is_allowed(&req).await?;
do_mem_hotplug_by_probe(&req.memHotplugProbeAddr)
.map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e))?;
@ -1388,7 +1421,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::SetGuestDateTimeRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "set_guest_date_time", req);
is_allowed(&req)?;
is_allowed(&req).await?;
do_set_guest_date_time(req.Sec, req.Usec)
.map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e))?;
@ -1402,7 +1435,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::CopyFileRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "copy_file", req);
is_allowed(&req)?;
is_allowed(&req).await?;
do_copy_file(&req).map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e))?;
@ -1415,7 +1448,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::GetMetricsRequest,
) -> ttrpc::Result<Metrics> {
trace_rpc_call!(ctx, "get_metrics", req);
is_allowed(&req)?;
is_allowed(&req).await?;
match get_metrics(&req) {
Err(e) => Err(ttrpc_error(ttrpc::Code::INTERNAL, e)),
@ -1432,7 +1465,7 @@ impl agent_ttrpc::AgentService for AgentService {
_ctx: &TtrpcContext,
req: protocols::agent::GetOOMEventRequest,
) -> ttrpc::Result<OOMEvent> {
is_allowed(&req)?;
is_allowed(&req).await?;
let s = self.sandbox.lock().await;
let event_rx = &s.event_rx.clone();
let mut event_rx = event_rx.lock().await;
@ -1456,7 +1489,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: VolumeStatsRequest,
) -> ttrpc::Result<VolumeStatsResponse> {
trace_rpc_call!(ctx, "get_volume_stats", req);
is_allowed(&req)?;
is_allowed(&req).await?;
info!(sl(), "get volume stats!");
let mut resp = VolumeStatsResponse::new();
@ -1496,7 +1529,7 @@ impl agent_ttrpc::AgentService for AgentService {
req: protocols::agent::AddSwapRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "add_swap", req);
is_allowed(&req)?;
is_allowed(&req).await?;
do_add_swap(&self.sandbox, &req)
.await
@ -1504,6 +1537,25 @@ impl agent_ttrpc::AgentService for AgentService {
Ok(Empty::new())
}
#[cfg(feature = "agent-policy")]
async fn set_policy(
&self,
ctx: &TtrpcContext,
req: protocols::agent::SetPolicyRequest,
) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "set_policy", req);
is_allowed(&req).await?;
AGENT_POLICY
.lock()
.await
.set_policy(&req.policy)
.await
.map_err(|e| ttrpc_error(ttrpc::Code::INTERNAL, e))?;
Ok(Empty::new())
}
}
#[derive(Clone)]

View File

@ -0,0 +1,38 @@
package agent_policy
default AddARPNeighborsRequest := true
default AddSwapRequest := true
default CloseStdinRequest := true
default CopyFileRequest := true
default CreateContainerRequest := true
default CreateSandboxRequest := true
default DestroySandboxRequest := true
default ExecProcessRequest = true
default GetMetricsRequest := true
default GetOOMEventRequest := true
default GuestDetailsRequest := true
default ListInterfacesRequest := true
default ListRoutesRequest := true
default MemHotplugByProbeRequest := true
default OnlineCPUMemRequest := true
default PauseContainerRequest := true
default PullImageRequest := true
default ReadStreamRequest := true
default RemoveContainerRequest := true
default RemoveStaleVirtiofsShareMountsRequest := true
default ReseedRandomDevRequest := false
default ResumeContainerRequest := true
default SetGuestDateTimeRequest := true
default SetPolicyRequest := true
default SignalProcessRequest := true
default StartContainerRequest := true
default StartTracingRequest := true
default StatsContainerRequest := true
default StopTracingRequest := true
default TtyWinResizeRequest := true
default UpdateContainerRequest := true
default UpdateEphemeralMountsRequest := true
default UpdateInterfaceRequest := true
default UpdateRoutesRequest := true
default WaitProcessRequest := true
default WriteStreamRequest := true

View File

@ -0,0 +1,29 @@
#
# Copyright (c) 2023 Microsoft Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
[Unit]
Description=Open Policy Agent for Kata Containers
Documentation=https://github.com/kata-containers
ConditionPathExists=@SETTINGSDIR@/default-policy.rego
# kata-agent connects to OPA while starting up.
Before=kata-agent.service
[Service]
Type=simple
ExecStart=@BINDIR@/opa run --server --disable-telemetry --addr 127.0.0.1:8181 --log-level info
DynamicUser=yes
RuntimeDirectory=kata-opa
LimitNOFILE=1048576
# Don't restart because there may be an active policy that would be lost.
Restart=no
# Send log output to tty to allow capturing debug logs from a VM vsock port.
StandardError=tty
# Discourage OOM-killer from touching the policy service.
OOMScoreAdjust=-997

View File

@ -72,6 +72,7 @@ service AgentService {
rpc AddSwap(AddSwapRequest) returns (google.protobuf.Empty);
rpc GetVolumeStats(VolumeStatsRequest) returns (VolumeStatsResponse);
rpc ResizeVolume(ResizeVolumeRequest) returns (google.protobuf.Empty);
rpc SetPolicy(SetPolicyRequest) returns (google.protobuf.Empty);
}
message CreateContainerRequest {
@ -566,3 +567,7 @@ message ResizeVolumeRequest {
string volume_guest_path = 1;
uint64 size = 2;
}
message SetPolicyRequest {
string policy = 1;
}

View File

@ -18,6 +18,7 @@ import (
"github.com/kata-containers/kata-containers/src/runtime/pkg/oci"
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
vf "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/factory"
vcAnnotations "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/annotations"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@ -162,6 +163,10 @@ func CreateSandbox(ctx context.Context, vci vc.VC, ociSpec specs.Spec, runtimeCo
ociSpec.Annotations["nerdctl/network-namespace"] = sandboxConfig.NetworkConfig.NetworkID
sandboxConfig.Annotations["nerdctl/network-namespace"] = ociSpec.Annotations["nerdctl/network-namespace"]
// The value of this annotation is sent to the sandbox using SetPolicy.
delete(ociSpec.Annotations, vcAnnotations.Policy)
delete(sandboxConfig.Annotations, vcAnnotations.Policy)
sandbox, err := vci.CreateSandbox(ctx, sandboxConfig, func(ctx context.Context) error {
// Run pre-start OCI hooks, in the runtime namespace.
if err := PreStartHooks(ctx, ociSpec, containerID, bundlePath); err != nil {
@ -228,6 +233,9 @@ func CreateContainer(ctx context.Context, sandbox vc.VCSandbox, ociSpec specs.Sp
katatrace.AddTags(span, "container_id", containerID)
defer span.End()
// The value of this annotation is sent to the sandbox using SetPolicy.
delete(ociSpec.Annotations, vcAnnotations.Policy)
ociSpec = SetEphemeralStorageType(ociSpec, disableGuestEmptyDir)
contConfig, err := oci.ContainerConfig(ociSpec, bundlePath, containerID, disableOutput)

View File

@ -8,6 +8,7 @@ package oci
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@ -908,10 +909,24 @@ func addRuntimeConfigOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig, r
func addAgentConfigOverrides(ocispec specs.Spec, config *vc.SandboxConfig) error {
c := config.AgentConfig
updateConfig := false
if value, ok := ocispec.Annotations[vcAnnotations.KernelModules]; ok {
modules := strings.Split(value, KernelModulesSeparator)
c.KernelModules = modules
updateConfig = true
}
if value, ok := ocispec.Annotations[vcAnnotations.Policy]; ok {
if decoded_rules, err := base64.StdEncoding.DecodeString(value); err == nil {
c.Policy = string(decoded_rules)
updateConfig = true
} else {
return err
}
}
if updateConfig {
config.AgentConfig = c
}

View File

@ -208,4 +208,7 @@ type agent interface {
// setIPTables sets the iptables from the guest
setIPTables(ctx context.Context, isIPv6 bool, data []byte) error
// setPolicy sends a new policy to the guest agent
setPolicy(ctx context.Context, policy string) error
}

View File

@ -150,6 +150,7 @@ const (
grpcResizeVolumeRequest = "grpc.ResizeVolumeRequest"
grpcGetIPTablesRequest = "grpc.GetIPTablesRequest"
grpcSetIPTablesRequest = "grpc.SetIPTablesRequest"
grpcSetPolicyRequest = "grpc.SetPolicyRequest"
)
// newKataAgent returns an agent from an agent type.
@ -275,6 +276,7 @@ type KataAgentConfig struct {
Debug bool
Trace bool
EnableDebugConsole bool
Policy string
}
// KataAgentState is the structure describing the data stored from this
@ -747,6 +749,13 @@ func (k *kataAgent) startSandbox(ctx context.Context, sandbox *Sandbox) error {
return err
}
// If a Policy has been specified, send it to the agent.
if len(sandbox.config.AgentConfig.Policy) > 0 {
if err := sandbox.agent.setPolicy(ctx, sandbox.config.AgentConfig.Policy); err != nil {
return err
}
}
// Setup network interfaces and routes
interfaces, routes, neighs, err := generateVCNetworkStructures(ctx, sandbox.network)
if err != nil {
@ -2081,6 +2090,9 @@ func (k *kataAgent) installReqFunc(c *kataclient.AgentClient) {
k.reqHandlers[grpcRemoveStaleVirtiofsShareMountsRequest] = func(ctx context.Context, req interface{}) (interface{}, error) {
return k.client.AgentServiceClient.RemoveStaleVirtiofsShareMounts(ctx, req.(*grpc.RemoveStaleVirtiofsShareMountsRequest))
}
k.reqHandlers[grpcSetPolicyRequest] = func(ctx context.Context, req interface{}) (interface{}, error) {
return k.client.AgentServiceClient.SetPolicy(ctx, req.(*grpc.SetPolicyRequest))
}
}
func (k *kataAgent) getReqContext(ctx context.Context, reqName string) (newCtx context.Context, cancel context.CancelFunc) {
@ -2356,3 +2368,8 @@ func (k *kataAgent) resizeGuestVolume(ctx context.Context, volumeGuestPath strin
_, err := k.sendReq(ctx, &grpc.ResizeVolumeRequest{VolumeGuestPath: volumeGuestPath, Size_: size})
return err
}
func (k *kataAgent) setPolicy(ctx context.Context, policy string) error {
_, err := k.sendReq(ctx, &grpc.SetPolicyRequest{Policy: policy})
return err
}

View File

@ -267,3 +267,7 @@ func (k *mockAgent) getIPTables(ctx context.Context, isIPv6 bool) ([]byte, error
func (k *mockAgent) setIPTables(ctx context.Context, isIPv6 bool, data []byte) error {
return nil
}
func (k *mockAgent) setPolicy(ctx context.Context, policy string) error {
return nil
}

View File

@ -297,6 +297,9 @@ const (
AgentContainerPipeSize = kataAnnotAgentPrefix + ContainerPipeSizeOption
ContainerPipeSizeOption = "container_pipe_size"
ContainerPipeSizeKernelParam = "agent." + ContainerPipeSizeOption
// Policy is an annotation containing the contents of an agent policy file, base64 encoded.
Policy = kataAnnotAgentPrefix + "policy"
)
// Container resource related annotations

View File

@ -256,3 +256,7 @@ func (p *HybridVSockTTRPCMockImp) GetIPTables(ctx context.Context, req *pb.GetIP
func (p *HybridVSockTTRPCMockImp) SetIPTables(ctx context.Context, req *pb.SetIPTablesRequest) (*pb.SetIPTablesResponse, error) {
return &pb.SetIPTablesResponse{}, nil
}
func (p *HybridVSockTTRPCMockImp) SetPolicy(ctx context.Context, req *pb.SetPolicyRequest) (*gpb.Empty, error) {
return &gpb.Empty{}, nil
}

View File

@ -8,3 +8,4 @@ LIBC="gnu"
PACKAGES="core-packages-base-image ca-certificates"
[ "$AGENT_INIT" = no ] && PACKAGES+=" systemd"
[ "$SECCOMP" = yes ] && PACKAGES+=" libseccomp"
[ "$AGENT_POLICY" = yes ] && PACKAGES+=" opa" || true

View File

@ -27,6 +27,7 @@ LIBC=${LIBC:-musl}
# However, it is not enforced by default: you need to enable that in the main configuration file.
SECCOMP=${SECCOMP:-"yes"}
SELINUX=${SELINUX:-"no"}
AGENT_POLICY=${AGENT_POLICY:-no}
lib_file="${script_dir}/../scripts/lib.sh"
source "$lib_file"
@ -315,6 +316,9 @@ check_env_variables()
[ "$AGENT_INIT" == "yes" -o "$AGENT_INIT" == "no" ] || die "AGENT_INIT($AGENT_INIT) is invalid (must be yes or no)"
[ "$AGENT_POLICY" == "yes" -o "$AGENT_POLICY" == "no" ] || die "AGENT_POLICY($AGENT_POLICY) is invalid (must be yes or no)"
[ "$AGENT_POLICY" == "no" -o "$AGENT_INIT" == "no" ] || die "AGENT_POLICY($AGENT_POLICY) and AGENT_INIT($AGENT_INIT) is an invalid combination (at least one must be no)"
[ -n "${KERNEL_MODULES_DIR}" ] && [ ! -d "${KERNEL_MODULES_DIR}" ] && die "KERNEL_MODULES_DIR defined but is not an existing directory"
[ -n "${OSBUILDER_VERSION}" ] || die "need osbuilder version"
@ -456,6 +460,7 @@ build_rootfs_distro()
--env SELINUX="${SELINUX}" \
--env DEBUG="${DEBUG}" \
--env HOME="/root" \
--env AGENT_POLICY="${AGENT_POLICY}" \
-v "${repo_dir}":"/kata-containers" \
-v "${ROOTFS_DIR}":"/rootfs" \
-v "${script_dir}/../scripts":"/scripts" \
@ -614,7 +619,7 @@ EOF
git checkout "${AGENT_VERSION}" && OK "git checkout successful" || die "checkout agent ${AGENT_VERSION} failed!"
fi
make clean
make LIBC=${LIBC} INIT=${AGENT_INIT} SECCOMP=${SECCOMP}
make LIBC=${LIBC} INIT=${AGENT_INIT} SECCOMP=${SECCOMP} AGENT_POLICY=${AGENT_POLICY}
make install DESTDIR="${ROOTFS_DIR}" LIBC=${LIBC} INIT=${AGENT_INIT}
if [ "${SECCOMP}" == "yes" ]; then
rm -rf "${libseccomp_install_dir}" "${gperf_install_dir}"
@ -640,6 +645,55 @@ EOF
chmod g+rx,o+x "${ROOTFS_DIR}"
fi
if [ "${AGENT_POLICY}" == "yes" ]; then
# Setup systemd-based environment for kata-opa.
local opa_bin_dir="$(get_opa_bin_dir "${ROOTFS_DIR}")"
if [ -z "${opa_bin_dir}" ]; then
# OPA was not installed already, so download it here.
#
# TODO: if an OPA package is not available for the Guest image distro,
# Kata should cache the OPA source code, toolchain information, etc.
# OPA should be built from the cached source code instead of downloading
# this binary.
#
opa_bin_url="$(get_package_version_from_kata_yaml externals.open-policy-agent.meta.binary)"
info "Downloading OPA binary from ${opa_bin_url}"
curl --fail -L "${opa_bin_url}" -o opa || die "Failed to download OPA"
# Install the OPA binary.
opa_bin_dir="/usr/local/bin"
local opa_bin="${ROOTFS_DIR}${opa_bin_dir}/opa"
info "Installing OPA binary to ${opa_bin}"
install -D -o root -g root -m 0755 opa -T "${opa_bin}"
else
info "OPA binary already exists in ${opa_bin_dir}"
fi
# Install default settings for the kata-opa service.
local kata_opa_in_dir="${script_dir}/../../../src/kata-opa"
local opa_settings_dir="/etc/kata-opa"
local policy_file="allow-all.rego"
local policy_dir="${ROOTFS_DIR}/${opa_settings_dir}"
mkdir -p "${policy_dir}"
install -D -o root -g root -m 0644 "${kata_opa_in_dir}/${policy_file}" -T "${policy_dir}/${policy_file}"
ln -sf "${policy_file}" "${policy_dir}/default-policy.rego"
# Install the unit file for the kata-opa service.
local kata_opa_unit="kata-opa.service"
local kata_opa_unit_path="${ROOTFS_DIR}/usr/lib/systemd/system/${kata_opa_unit}"
local kata_containers_wants="${ROOTFS_DIR}/etc/systemd/system/kata-containers.target.wants"
opa_settings_dir="${opa_settings_dir//\//\\/}"
sed -e "s/@SETTINGSDIR@/${opa_settings_dir}/g" "${kata_opa_in_dir}/${kata_opa_unit}.in" > "${kata_opa_unit}"
opa_bin_dir="${opa_bin_dir//\//\\/}"
sed -i -e "s/@BINDIR@/${opa_bin_dir}/g" "${kata_opa_unit}"
install -D -o root -g root -m 0644 "${kata_opa_unit}" -T "${kata_opa_unit_path}"
mkdir -p "${kata_containers_wants}"
ln -sf "${kata_opa_unit_path}" "${kata_containers_wants}/${kata_opa_unit}"
fi
info "Check init is installed"
[ -x "${init}" ] || [ -L "${init}" ] || die "/sbin/init is not installed in ${ROOTFS_DIR}"
OK "init is installed"
@ -657,6 +711,24 @@ EOF
create_summary_file "${ROOTFS_DIR}"
}
get_opa_bin_dir()
{
local rootfs_dir="$1"
local -a bin_dirs=(
"/bin"
"/usr/bin"
"/usr/local/bin"
)
for bin_dir in "${bin_dirs[@]}"
do
local opa_bin="${rootfs_dir}${bin_dir}/opa"
if [ -f "${opa_bin}" ]; then
echo "${bin_dir}"
return 0
fi
done
}
parse_arguments()
{
[ "$#" -eq 0 ] && usage && return 0

View File

@ -284,6 +284,22 @@ externals:
url: "https://github.com/containerd/nydus-snapshotter"
version: "v0.3.3"
open-policy-agent:
description: "Open Policy Agent"
url: "https://github.com/open-policy-agent/opa"
version: "v0.55.0"
meta:
# - If an OPA package is available for the Guest image distro, that
# package is used instead of the binary below.
#
# - TODO: if an OPA package is not available for the Guest image distro,
# Kata should cache the OPA source code, toolchain information, etc.
# OPA should be built from the cached source code instead of downloading
# this binary.
#
# yamllint disable-line rule:line-length
binary: "https://github.com/open-policy-agent/opa/releases/download/v0.55.0/opa_linux_amd64_static"
ovmf:
description: "Firmware, implementation of UEFI for virtual machines."
url: "https://github.com/tianocore/edk2"