From 836cd56d291fe6086ac0cd348aa2cea3af0ffeb5 Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Thu, 11 Sep 2025 10:10:03 +0200 Subject: [PATCH 1/2] cargo: sort deps & centralize features Signed-off-by: Jakob Naucke --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 10 +++++----- compute-pcrs/Cargo.toml | 6 +++--- manifest-gen/Cargo.toml | 6 +++--- operator/Cargo.toml | 10 +++++----- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0141b6d6..8a2bae64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,9 +214,9 @@ dependencies = [ [[package]] name = "backon" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302eaff5357a264a2c42f127ecb8bac761cf99749fc3dc95677e2743991f99e7" +checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d" dependencies = [ "fastrand", "gloo-timers", @@ -694,9 +694,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -2156,9 +2156,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "redox_syscall" -version = "0.5.15" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.9.1", ] @@ -3473,18 +3473,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 65e04898..9798cee1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["compute-pcrs", "crds", "operator", "manifest-gen"] +members = ["compute-pcrs", "crds", "manifest-gen", "operator"] resolver = "3" [workspace.package] @@ -7,11 +7,11 @@ edition = "2024" [workspace.dependencies] anyhow = "1.0.99" -clap = "4.5.41" +clap = { version = "4.5.41", features = ["derive"] } env_logger = "0.11.8" k8s-openapi = { version = "0.25.0", features = ["v1_33"] } -kube = "1.1.0" +kube = { version = "1.1.0", features = ["derive", "runtime"] } log = "0.4.27" -serde = "1.0.219" +serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.141" -tokio = "1.46.1" +tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] } diff --git a/compute-pcrs/Cargo.toml b/compute-pcrs/Cargo.toml index 2dbc78d7..39371a93 100644 --- a/compute-pcrs/Cargo.toml +++ b/compute-pcrs/Cargo.toml @@ -7,11 +7,11 @@ description = "A cocl-operator optimized compute-pcrs interface" [dependencies] anyhow.workspace = true chrono = "0.4.41" -clap = { workspace = true, features = ["derive"] } +clap.workspace = true compute-pcrs-lib = { git = "https://github.com/confidential-clusters/compute-pcrs", version = "0.1.0" } k8s-openapi.workspace = true kube.workspace = true log.workspace = true -serde = { workspace = true, features = ["derive"] } +serde.workspace = true serde_json.workspace = true -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +tokio.workspace = true diff --git a/manifest-gen/Cargo.toml b/manifest-gen/Cargo.toml index 024707c4..0ec3fd0d 100644 --- a/manifest-gen/Cargo.toml +++ b/manifest-gen/Cargo.toml @@ -5,10 +5,10 @@ edition.workspace = true [dependencies] anyhow.workspace = true -env_logger.workspace = true -clap = { workspace = true, features = ["derive"] } +clap.workspace = true crds = { path = "../crds" } +env_logger.workspace = true k8s-openapi.workspace = true -kube = { workspace = true, features = ["derive"] } +kube.workspace = true log.workspace = true serde_yaml = "0.9" diff --git a/operator/Cargo.toml b/operator/Cargo.toml index d066694b..b004bf28 100644 --- a/operator/Cargo.toml +++ b/operator/Cargo.toml @@ -7,15 +7,15 @@ edition.workspace = true anyhow.workspace = true base64 = "0.22.1" crds = { path = "../crds" } -env_logger = { workspace = true } +env_logger.workspace = true futures-util = "0.3.31" json-patch = "4.0.0" jsonptr = "0.7.1" k8s-openapi.workspace = true -kube = { workspace = true, features = ["runtime"] } +kube.workspace = true log.workspace = true openssl = "0.10.73" -thiserror = "2.0.12" -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } -serde = { workspace = true, features = ["derive"] } +serde.workspace = true serde_json.workspace = true +thiserror = "2.0.12" +tokio.workspace = true From 99b9a8931e30a34c6d57508f8fa985869712d7dd Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Thu, 11 Sep 2025 18:18:48 +0200 Subject: [PATCH 2/2] Compute PCRs, using labels and caches - Create a new config map to cache known PCR values and parts. - Fill with PCR label if present in boot image. - Recompute reference values based on config map. Technicalities: - Pivot compute-pcrs job from setting reference values directly to writing to the new config map. It is now used as a fallback. - Move reference value-specific, non-Trustee-interacting operations to a new module. Signed-off-by: Jakob Naucke --- Cargo.lock | 395 +++++++++++++++++++++++++++++-- Cargo.toml | 4 +- compute-pcrs/Cargo.toml | 7 +- compute-pcrs/Containerfile | 5 +- compute-pcrs/src/main.rs | 97 +++----- crds/Cargo.toml | 4 +- crds/src/lib.rs | 1 + manifest-gen/Cargo.toml | 2 +- manifest-gen/src/main.rs | 17 +- operator/Cargo.toml | 6 + operator/src/main.rs | 30 ++- operator/src/reference_values.rs | 291 +++++++++++++++++++++++ operator/src/trustee.rs | 217 ++++++++--------- rv-store/Cargo.toml | 10 + rv-store/src/lib.rs | 16 ++ 15 files changed, 881 insertions(+), 221 deletions(-) create mode 100644 operator/src/reference_values.rs create mode 100644 rv-store/Cargo.toml create mode 100644 rv-store/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 8a2bae64..b78a6107 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,9 +214,9 @@ dependencies = [ [[package]] name = "backon" -version = "1.5.2" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d" +checksum = "302eaff5357a264a2c42f127ecb8bac761cf99749fc3dc95677e2743991f99e7" dependencies = [ "fastrand", "gloo-timers", @@ -247,6 +247,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -384,8 +390,7 @@ dependencies = [ "compute-pcrs-lib", "k8s-openapi", "kube", - "log", - "serde", + "rv-store", "serde_json", "tokio", ] @@ -393,7 +398,7 @@ dependencies = [ [[package]] name = "compute-pcrs-lib" version = "0.1.0" -source = "git+https://github.com/confidential-clusters/compute-pcrs#1f19d45266169a7e57c7c36aa78c1ad36c4f32b9" +source = "git+https://github.com/confidential-clusters/compute-pcrs#4b73fffbd38c4a95ed2056487b4b674d42762756" dependencies = [ "anyhow", "glob", @@ -415,6 +420,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -559,6 +584,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.104", +] + [[package]] name = "derive_more" version = "2.0.1" @@ -587,6 +643,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -694,9 +751,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", @@ -875,6 +932,18 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "gimli" version = "0.31.1" @@ -1003,6 +1072,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.11" @@ -1045,6 +1123,15 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-auth" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "150fa4a9462ef926824cf4519c84ed652ca8f4fbae34cb8af045b5cbcaf98822" +dependencies = [ + "memchr", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1199,12 +1286,29 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -1212,7 +1316,9 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "hyper 1.6.0", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2 0.6.0", "tokio", @@ -1395,6 +1501,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.16" @@ -1511,6 +1627,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest", + "hmac", + "serde", + "serde_json", + "sha2", +] + [[package]] name = "k8s-openapi" version = "0.25.0" @@ -1633,6 +1764,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.174" @@ -1662,7 +1799,7 @@ checksum = "1c5164f3ff46ec4719a37853314cd1f3f91736f9d15ea3ceb79e008219624ae4" dependencies = [ "git-version", "miette", - "reqwest", + "reqwest 0.11.24", "semver", "zip", ] @@ -1803,6 +1940,23 @@ dependencies = [ "cxx", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1857,6 +2011,60 @@ dependencies = [ "memchr", ] +[[package]] +name = "oci-client" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b74df13319e08bc386d333d3dc289c774c88cc543cae31f5347db07b5ec2172" +dependencies = [ + "bytes", + "chrono", + "futures-util", + "http 1.3.1", + "http-auth", + "jwt", + "lazy_static", + "oci-spec", + "olpc-cjson", + "regex", + "reqwest 0.12.23", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.12", + "tokio", + "tracing", + "unicase", +] + +[[package]] +name = "oci-spec" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2078e2f6be932a4de9aca90a375a45590809dfb5a08d93ab1ee217107aceeb67" +dependencies = [ + "const_format", + "derive_builder", + "getset", + "regex", + "serde", + "serde_json", + "strum", + "strum_macros", + "thiserror 2.0.12", +] + +[[package]] +name = "olpc-cjson" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "696183c9b5fe81a7715d074fd632e8bd46f4ccc0231a3ed7fc580a80de5f7083" +dependencies = [ + "serde", + "serde_json", + "unicode-normalization", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1919,15 +2127,21 @@ version = "0.1.0" dependencies = [ "anyhow", "base64 0.22.1", + "chrono", + "compute-pcrs-lib", "crds", "env_logger", "futures-util", + "hex", "json-patch", "jsonptr", "k8s-openapi", "kube", "log", + "oci-client", + "oci-spec", "openssl", + "rv-store", "serde", "serde_json", "thiserror 2.0.12", @@ -2130,6 +2344,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -2156,9 +2392,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" dependencies = [ "bitflags 2.9.1", ] @@ -2233,6 +2469,45 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + [[package]] name = "ring" version = "0.17.14" @@ -2372,6 +2647,15 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "rv-store" +version = "0.1.0" +dependencies = [ + "chrono", + "compute-pcrs-lib", + "serde", +] + [[package]] name = "ryu" version = "1.0.20" @@ -2643,6 +2927,24 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "subtle" version = "2.6.1" @@ -2710,6 +3012,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -2846,6 +3151,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +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.46.1" @@ -2876,6 +3196,16 @@ dependencies = [ "syn 2.0.104", ] +[[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-rustls" version = "0.24.1" @@ -2936,10 +3266,13 @@ dependencies = [ "base64 0.22.1", "bitflags 2.9.1", "bytes", + "futures-util", "http 1.3.1", "http-body 1.0.1", + "iri-string", "mime", "pin-project-lite", + "tower", "tower-layer", "tower-service", "tracing", @@ -3007,6 +3340,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -3019,12 +3358,27 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -3177,6 +3531,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -3473,18 +3840,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 9798cee1..6ed2e399 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["compute-pcrs", "crds", "manifest-gen", "operator"] +members = ["compute-pcrs", "crds", "manifest-gen", "operator", "rv-store"] resolver = "3" [workspace.package] @@ -7,7 +7,9 @@ edition = "2024" [workspace.dependencies] anyhow = "1.0.99" +chrono = "0.4.41" clap = { version = "4.5.41", features = ["derive"] } +compute-pcrs-lib = { git = "https://github.com/confidential-clusters/compute-pcrs" } env_logger = "0.11.8" k8s-openapi = { version = "0.25.0", features = ["v1_33"] } kube = { version = "1.1.0", features = ["derive", "runtime"] } diff --git a/compute-pcrs/Cargo.toml b/compute-pcrs/Cargo.toml index 39371a93..1cd7802e 100644 --- a/compute-pcrs/Cargo.toml +++ b/compute-pcrs/Cargo.toml @@ -6,12 +6,11 @@ description = "A cocl-operator optimized compute-pcrs interface" [dependencies] anyhow.workspace = true -chrono = "0.4.41" +chrono.workspace = true clap.workspace = true -compute-pcrs-lib = { git = "https://github.com/confidential-clusters/compute-pcrs", version = "0.1.0" } +compute-pcrs-lib.workspace = true k8s-openapi.workspace = true kube.workspace = true -log.workspace = true -serde.workspace = true +rv-store = { path = "../rv-store" } serde_json.workspace = true tokio.workspace = true diff --git a/compute-pcrs/Containerfile b/compute-pcrs/Containerfile index aa3e03fe..27c69546 100644 --- a/compute-pcrs/Containerfile +++ b/compute-pcrs/Containerfile @@ -2,9 +2,10 @@ FROM ghcr.io/confidential-clusters/compute-pcrs/buildroot AS builder WORKDIR /compute-pcrs COPY Cargo.toml Cargo.lock . COPY compute-pcrs compute-pcrs -# Hack: Set compute-pcrs as sole member to avoid needing to copy other crates. +COPY rv-store rv-store +# Hack: Set required crates as only members to avoid needing to copy other crates. # In that case, a rebuild would be triggered upon any change in those crates. -RUN sed -i 's/members =.*/members = ["compute-pcrs"]/' Cargo.toml && \ +RUN sed -i 's/members =.*/members = ["compute-pcrs", "rv-store"]/' Cargo.toml && \ git clone --depth 1 https://github.com/confidential-clusters/reference-values && \ cargo build --release -p compute-pcrs diff --git a/compute-pcrs/src/main.rs b/compute-pcrs/src/main.rs index 1c6c6b39..87643c9d 100644 --- a/compute-pcrs/src/main.rs +++ b/compute-pcrs/src/main.rs @@ -1,33 +1,12 @@ -use anyhow::Result; -use chrono::{DateTime, TimeDelta, Utc}; +use anyhow::{Context, Result}; +use chrono::Utc; use clap::Parser; use compute_pcrs_lib::*; use k8s_openapi::api::core::v1::ConfigMap; -use kube::api::{ObjectMeta, PostParams}; -use kube::{Api, Client}; -use log::info; -use serde::{Serialize, Serializer}; +use kube::{Api, Client, api::PostParams}; use std::collections::BTreeMap; -fn primitive_date_time_to_str(d: &DateTime, s: S) -> Result -where - S: Serializer, -{ - s.serialize_str(&d.format("%Y-%m-%dT%H:%M:%SZ").to_string()) -} - -/// Sync with Trustee -/// reference_value_provider_service::reference_value::ReferenceValue -/// (cannot import directly because its expiration doesn't serialize -/// right) -#[derive(Serialize)] -struct ReferenceValue { - pub version: String, - pub name: String, - #[serde(serialize_with = "primitive_date_time_to_str")] - pub expiration: DateTime, - pub value: serde_json::Value, -} +use rv_store::*; #[derive(Parser)] #[command(version, about)] @@ -44,9 +23,9 @@ struct Args { /// Path to directory storing MokListRT, MokListTrustedRT and MokListXRT #[arg(short, long)] mokvars: String, - /// ConfigMap name to write to + /// Image reference #[arg(short, long)] - configmap: String, + image: String, /// Namespace to write ConfigMap to #[arg(short, long)] namespace: String, @@ -56,53 +35,35 @@ struct Args { async fn main() -> Result<()> { let args = Args::parse(); - let mut pcrs: Vec<_> = [ + let pcrs = vec![ compute_pcr4(&args.kernels, &args.esp, false, true), compute_pcr7(Some(&args.efivars), &args.esp, true), compute_pcr14(&args.mokvars), - ] - .iter() - .map(|pcr| (format!("pcr{}", pcr.id), pcr.value.clone())) - .collect(); - pcrs.push(("svn".to_string(), "1".to_string())); - - let reference_values: Vec<_> = pcrs - .iter() - .map(|(name, value)| ReferenceValue { - version: "0.1.0".to_string(), - name: format!("tpm_{name}"), - expiration: Utc::now() + TimeDelta::days(365), - value: serde_json::Value::Array(vec![serde_json::Value::String(value.to_string())]), - }) - .collect(); - let reference_values_json = serde_json::to_string(&reference_values)?; - let data = BTreeMap::from([( - "reference-values.json".to_string(), - reference_values_json.to_string(), - )]); - - let config_map = ConfigMap { - metadata: ObjectMeta { - name: Some(args.configmap.clone()), - namespace: Some(args.namespace.clone()), - ..Default::default() - }, - data: Some(data), - ..Default::default() - }; + ]; let client = Client::try_default().await?; let config_maps: Api = Api::namespaced(client, &args.namespace); - match config_maps - .create(&PostParams::default(), &config_map) - .await - { - Ok(_) => info!("Create ConfigMap {}", args.configmap), - Err(kube::Error::Api(ae)) if ae.code == 409 => { - info!("ConfigMap {} already exists", args.configmap) - } - Err(e) => return Err(e.into()), - } + let mut image_pcrs_map = config_maps.get(PCR_CONFIG_MAP).await?; + let image_pcrs_data = image_pcrs_map + .data + .context("Image PCRs map existed, but had no data")?; + let image_pcrs_str = image_pcrs_data + .get(PCR_CONFIG_FILE) + .context("Image PCRs data existed, but had no file")?; + let mut image_pcrs: ImagePcrs = serde_json::from_str(image_pcrs_str)?; + + let image_pcr = ImagePcr { + first_seen: Utc::now(), + pcrs, + }; + image_pcrs.0.insert(args.image, image_pcr); + + let image_pcrs_json = serde_json::to_string(&image_pcrs)?; + let data = BTreeMap::from([(PCR_CONFIG_FILE.to_string(), image_pcrs_json.to_string())]); + image_pcrs_map.data = Some(data); + config_maps + .replace(PCR_CONFIG_MAP, &PostParams::default(), &image_pcrs_map) + .await?; Ok(()) } diff --git a/crds/Cargo.toml b/crds/Cargo.toml index 66574277..75053ba4 100644 --- a/crds/Cargo.toml +++ b/crds/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true [dependencies] k8s-openapi.workspace = true -kube = { workspace = true, features = ["derive"] } +kube.workspace = true schemars = { version = "0.8", features = ["derive"] } -serde = { workspace = true, features = ["derive"] } +serde.workspace = true serde_json.workspace = true diff --git a/crds/src/lib.rs b/crds/src/lib.rs index c90f998f..f38db3c5 100644 --- a/crds/src/lib.rs +++ b/crds/src/lib.rs @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; namespaced, plural = "confidentialclusters" )] +#[serde(rename_all = "camelCase")] pub struct ConfidentialClusterSpec { pub trustee: Trustee, pub pcrs_compute_image: String, diff --git a/manifest-gen/Cargo.toml b/manifest-gen/Cargo.toml index 0ec3fd0d..1e365c8d 100644 --- a/manifest-gen/Cargo.toml +++ b/manifest-gen/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true [dependencies] anyhow.workspace = true -clap.workspace = true +clap = { workspace = true, features = ["derive"] } crds = { path = "../crds" } env_logger.workspace = true k8s-openapi.workspace = true diff --git a/manifest-gen/src/main.rs b/manifest-gen/src/main.rs index 8ae362f5..30852038 100644 --- a/manifest-gen/src/main.rs +++ b/manifest-gen/src/main.rs @@ -137,6 +137,19 @@ fn generate_operator(args: &Args) -> Result<()> { ], ..Default::default() }, + PolicyRule { + api_groups: Some(vec!["".to_string()]), + resources: Some(vec![ConfigMap::plural(&()).to_string()]), + verbs: vec![ + "create".to_string(), + "get".to_string(), + "list".to_string(), + "watch".to_string(), + "patch".to_string(), + "update".to_string(), + ], + ..Default::default() + }, PolicyRule { api_groups: Some(vec![ConfidentialCluster::group(&()).to_string()]), resources: Some(vec![ConfidentialCluster::plural(&()).to_string()]), @@ -192,7 +205,7 @@ fn generate_operator(args: &Args) -> Result<()> { let compute_pcrs_role = Role { metadata: ObjectMeta { name: Some(format!("{compute_pcrs_service_account_name}-role")), - namespace: Some(args.trustee_namespace.clone()), + namespace: Some(namespace.clone()), ..Default::default() }, rules: Some(vec![PolicyRule { @@ -213,7 +226,7 @@ fn generate_operator(args: &Args) -> Result<()> { let compute_pcrs_role_binding = RoleBinding { metadata: ObjectMeta { name: Some(format!("{compute_pcrs_service_account_name}-rolebinding")), - namespace: Some(args.trustee_namespace.clone()), + namespace: Some(namespace.clone()), ..Default::default() }, role_ref: RoleRef { diff --git a/operator/Cargo.toml b/operator/Cargo.toml index b004bf28..35e58e63 100644 --- a/operator/Cargo.toml +++ b/operator/Cargo.toml @@ -6,15 +6,21 @@ edition.workspace = true [dependencies] anyhow.workspace = true base64 = "0.22.1" +chrono.workspace = true +compute-pcrs-lib.workspace = true crds = { path = "../crds" } env_logger.workspace = true futures-util = "0.3.31" +hex = "0.4.3" json-patch = "4.0.0" jsonptr = "0.7.1" k8s-openapi.workspace = true kube.workspace = true log.workspace = true +oci-client = "0.15.0" +oci-spec = "0.8.2" openssl = "0.10.73" +rv-store = { path = "../rv-store" } serde.workspace = true serde_json.workspace = true thiserror = "2.0.12" diff --git a/operator/src/main.rs b/operator/src/main.rs index 2dbeab32..6cffe77a 100644 --- a/operator/src/main.rs +++ b/operator/src/main.rs @@ -14,6 +14,7 @@ use log::{error, info}; use thiserror::Error; use crds::ConfidentialCluster; +mod reference_values; mod trustee; #[derive(Debug, Error)] @@ -25,6 +26,8 @@ struct ContextData { client: Client, } +const BOOT_IMAGE: &str = "quay.io/fedora/fedora-coreos:42.20250705.3.0"; + async fn list_confidential_clusters( client: Client, namespace: &str, @@ -91,23 +94,38 @@ async fn install_trustee_configuration(client: Client, namespace: String) -> Res Err(e) => error!("Failed to create HTTPS certificates for the KBS: {e}"), } - trustee::launch_rv_job_controller(client.clone(), &namespace).await; - - match trustee::generate_reference_values( + let rv_ctx = trustee::RvContextData { + client: client.clone(), + operator_namespace: namespace.clone(), + trustee_namespace: trustee_namespace.clone(), + pcrs_compute_image: cocl.spec.pcrs_compute_image, + rv_map: cocl.spec.trustee.reference_values.clone(), + }; + reference_values::launch_rv_job_controller(rv_ctx.clone()).await; + match reference_values::create_pcrs_config_map(client.clone(), &namespace).await { + Ok(_) => info!("Created bare configmap for PCRs"), + Err(e) => error!("Failed to create the PCRs configmap: {e}"), + } + match trustee::create_reference_value_config_map( client.clone(), - &namespace, &trustee_namespace, &cocl.spec.trustee.reference_values, - &cocl.spec.pcrs_compute_image, ) .await { Ok(_) => info!( - "Generate configmap for the reference values: {}", + "Created bare configmap for the reference values: {}", cocl.spec.trustee.reference_values ), Err(e) => error!("Failed to create the reference values configmap: {e}"), } + // TODO machine config input + match reference_values::handle_new_image(rv_ctx, BOOT_IMAGE).await { + Ok(_) => info!("Computed or retrieved reference values for image: {BOOT_IMAGE}",), + Err(e) => { + error!("Failed to compute or retrieve reference values for image {BOOT_IMAGE}: {e}",) + } + } match trustee::generate_resource_policy( client.clone(), diff --git a/operator/src/reference_values.rs b/operator/src/reference_values.rs new file mode 100644 index 00000000..14a9d195 --- /dev/null +++ b/operator/src/reference_values.rs @@ -0,0 +1,291 @@ +use anyhow::Context; +use chrono::Utc; +use compute_pcrs_lib::Pcr; +use futures_util::StreamExt; +use k8s_openapi::api::{ + batch::v1::{Job, JobSpec}, + core::v1::{ + ConfigMap, ConfigMapVolumeSource, Container, ImageVolumeSource, KeyToPath, PodSpec, + PodTemplateSpec, Volume, VolumeMount, + }, +}; +use kube::api::{DeleteParams, ObjectMeta, PostParams}; +use kube::runtime::{ + controller::{Action, Controller}, + watcher, +}; +use kube::{Api, Client}; +use log::{error, info}; +use oci_client::secrets::RegistryAuth; +use oci_spec::image::ImageConfiguration; +use openssl::hash::{MessageDigest, hash}; +use serde::Deserialize; +use std::{collections::BTreeMap, path::PathBuf, sync::Arc, time::Duration}; +use thiserror::Error; + +use crate::trustee::{self, RvContextData, get_image_pcrs, info_if_exists}; +use rv_store::*; + +#[derive(Debug, Error)] +enum Error { + #[error("{0}")] + Anyhow(#[from] anyhow::Error), +} + +const JOB_LABEL_KEY: &str = "kind"; +const PCR_COMMAND_NAME: &str = "compute-pcrs"; +const PCR_LABEL: &str = "org.coreos.pcrs"; + +/// Synchronize with compute_pcrs_cli::Output +#[derive(Deserialize)] +struct ComputePcrsOutput { + pcrs: Vec, +} + +pub async fn create_pcrs_config_map(client: Client, namespace: &str) -> anyhow::Result<()> { + let empty_data = BTreeMap::from([( + PCR_CONFIG_FILE.to_string(), + serde_json::to_string(&ImagePcrs::default())?, + )]); + let config_maps: Api = Api::namespaced(client, namespace); + let config_map = ConfigMap { + metadata: ObjectMeta { + name: Some(PCR_CONFIG_MAP.to_string()), + namespace: Some(namespace.to_string()), + ..Default::default() + }, + data: Some(empty_data), + ..Default::default() + }; + let create = config_maps + .create(&PostParams::default(), &config_map) + .await; + info_if_exists!(create, "ConfigMap", PCR_CONFIG_MAP); + + Ok(()) +} + +async fn fetch_pcr_label(image_ref: &str) -> anyhow::Result>> { + let reference: oci_client::Reference = image_ref.parse()?; + let client = oci_client::Client::new(Default::default()); + let (_, _, raw_config) = client + .pull_manifest_and_config(&reference, &RegistryAuth::Anonymous) + .await?; + let config: ImageConfiguration = serde_json::from_str(&raw_config)?; + config + .labels_of_config() + .and_then(|m| m.get(PCR_LABEL)) + .map(|l| serde_json::from_str::(l).map(|o| o.pcrs)) + .transpose() + .map_err(Into::into) +} + +fn build_compute_pcrs_pod_spec( + namespace: &str, + boot_image: &str, + pcrs_compute_image: &str, +) -> PodSpec { + let image_volume_name = "image"; + let image_mountpoint = PathBuf::from(format!("/{image_volume_name}")); + let pcrs_volume_name = "pcrs"; + let pcrs_mountpoint = PathBuf::from(format!("/{pcrs_volume_name}")); + + let mut cmd = vec![PCR_COMMAND_NAME.to_string()]; + let mut add_flag = |flag: &str, value: &str| { + cmd.push(format!("--{flag}")); + cmd.push(value.to_string()); + }; + for (flag, path_suffix) in [ + ("kernels", "usr/lib/modules"), + ("esp", "usr/lib/bootupd/updates"), + ] { + let full_path = image_mountpoint.clone().join(path_suffix); + add_flag(flag, full_path.to_str().unwrap()); + } + for (flag, value) in [ + ("efivars", "/reference-values/efivars/qemu-ovmf/fedora-42"), + ("mokvars", "/reference-values/mok-variables/fedora-42"), + ("image", boot_image), + ("namespace", namespace), + ] { + add_flag(flag, value); + } + + PodSpec { + service_account_name: Some("compute-pcrs".to_string()), + containers: vec![Container { + name: PCR_COMMAND_NAME.to_string(), + image: Some(pcrs_compute_image.to_string()), + command: Some(cmd), + volume_mounts: Some(vec![ + VolumeMount { + name: image_volume_name.to_string(), + mount_path: image_mountpoint.to_str().unwrap().to_string(), + ..Default::default() + }, + VolumeMount { + name: pcrs_volume_name.to_string(), + mount_path: pcrs_mountpoint.to_str().unwrap().to_string(), + ..Default::default() + }, + ]), + ..Default::default() + }], + volumes: Some(vec![ + Volume { + name: image_volume_name.to_string(), + image: Some(ImageVolumeSource { + reference: Some(boot_image.to_string()), + ..Default::default() + }), + ..Default::default() + }, + Volume { + name: pcrs_volume_name.to_string(), + config_map: Some(ConfigMapVolumeSource { + name: PCR_CONFIG_MAP.to_string(), + items: Some(vec![KeyToPath { + key: PCR_CONFIG_FILE.to_string(), + path: PCR_CONFIG_FILE.to_string(), + ..Default::default() + }]), + ..Default::default() + }), + ..Default::default() + }, + ]), + restart_policy: Some("Never".to_string()), + ..Default::default() + } +} + +async fn job_reconcile(job: Arc, ctx: Arc) -> Result { + let err = "Job changed, but had no name"; + let name = &job.metadata.name.clone().context(err)?; + let err = format!("Job {name} changed, but had no status"); + let status = &job.status.clone().context(err)?; + if status.completion_time.is_none() { + info!("Job {name} changed, but had not completed"); + return Ok(Action::requeue(Duration::from_secs(300))); + } + let jobs: Api = Api::namespaced(ctx.client.clone(), &ctx.operator_namespace); + jobs.delete(name, &DeleteParams::default()) + .await + .map_err(Into::::into)?; + trustee::recompute_reference_values(Arc::::unwrap_or_clone(ctx)).await?; + Ok(Action::await_change()) +} + +fn job_error_policy(_obj: Arc, error: &Error, _ctx: Arc) -> Action { + error!("{error}"); + Action::requeue(Duration::from_secs(60)) +} + +pub async fn launch_rv_job_controller(ctx: RvContextData) { + let jobs: Api = Api::namespaced(ctx.client.clone(), &ctx.operator_namespace); + let watcher = watcher::Config { + label_selector: Some(format!("{JOB_LABEL_KEY}={PCR_COMMAND_NAME}")), + ..Default::default() + }; + tokio::spawn( + Controller::new(jobs, watcher) + .run(job_reconcile, job_error_policy, Arc::new(ctx)) + .for_each(|res| async move { + match res { + Ok(o) => info!("reconciled {o:?}"), + Err(e) => info!("reconcile failed: {e:?}"), + } + }), + ); +} + +async fn compute_fresh_pcrs( + client: Client, + namespace: &str, + boot_image: &str, + pcrs_compute_image: &str, +) -> anyhow::Result<()> { + // Name job by sanitized image name, plus a hash to disambiguate + // tags that differed only beyond the truncation limit + let rfc1035_boot_image = boot_image.replace(['.', ':', '/', '@', '_'], "-"); + let boot_image_hash = hash(MessageDigest::sha1(), boot_image.as_bytes())?; + let mut boot_image_hash_str = hex::encode(boot_image_hash); + boot_image_hash_str.truncate(10); + let mut job_name = format!("{PCR_COMMAND_NAME}-{boot_image_hash_str}-{rfc1035_boot_image}"); + job_name.truncate(63); + + let pod_spec = build_compute_pcrs_pod_spec(namespace, boot_image, pcrs_compute_image); + let job = Job { + metadata: ObjectMeta { + name: Some(job_name.clone()), + namespace: Some(namespace.to_string()), + labels: Some(BTreeMap::from([( + JOB_LABEL_KEY.to_string(), + PCR_COMMAND_NAME.to_string(), + )])), + ..Default::default() + }, + spec: Some(JobSpec { + template: PodTemplateSpec { + spec: Some(pod_spec), + ..Default::default() + }, + ..Default::default() + }), + ..Default::default() + }; + + let jobs: Api = Api::namespaced(client.clone(), namespace); + let create = jobs.create(&PostParams::default(), &job).await; + info_if_exists!(create, "Job", job_name); + Ok(()) +} + +pub async fn handle_new_image(ctx: RvContextData, boot_image: &str) -> anyhow::Result<()> { + let config_maps: Api = Api::namespaced(ctx.client.clone(), &ctx.operator_namespace); + let mut image_pcrs_map = config_maps.get(PCR_CONFIG_MAP).await?; + let mut image_pcrs = get_image_pcrs(image_pcrs_map.clone())?; + if image_pcrs.0.contains_key(boot_image) { + return Ok(()); + } + let label = fetch_pcr_label(boot_image).await?; + if label.is_none() { + let ns = &ctx.operator_namespace; + let comp_img = &ctx.pcrs_compute_image; + return compute_fresh_pcrs(ctx.client.clone(), ns, boot_image, comp_img).await; + } + + let image_pcr = ImagePcr { + first_seen: Utc::now(), + pcrs: label.unwrap(), + }; + // Non-goal: Support tags whose referenced versions change (e.g. `latest`). + // This would introduce hard-to-define behavior for disallowing older versions that were + // introduced as a tag that is still allowed. + image_pcrs.0.insert(boot_image.to_string(), image_pcr); + let image_pcrs_json = serde_json::to_string(&image_pcrs)?; + let data = BTreeMap::from([(PCR_CONFIG_FILE.to_string(), image_pcrs_json.to_string())]); + image_pcrs_map.data = Some(data); + config_maps + .replace(PCR_CONFIG_MAP, &PostParams::default(), &image_pcrs_map) + .await?; + trustee::recompute_reference_values(ctx).await +} + +#[allow(dead_code)] +pub async fn disallow_image(ctx: RvContextData, boot_image: &str) -> anyhow::Result<()> { + let config_maps: Api = Api::namespaced(ctx.client.clone(), &ctx.operator_namespace); + let mut image_pcrs_map = config_maps.get(PCR_CONFIG_MAP).await?; + let mut image_pcrs = get_image_pcrs(image_pcrs_map.clone())?; + if image_pcrs.0.remove(boot_image).is_none() { + info!("Image {boot_image} was to be disallowed, but already was not allowed"); + } + + let image_pcrs_json = serde_json::to_string(&image_pcrs)?; + let data = BTreeMap::from([(PCR_CONFIG_FILE.to_string(), image_pcrs_json.to_string())]); + image_pcrs_map.data = Some(data); + config_maps + .replace(PCR_CONFIG_MAP, &PostParams::default(), &image_pcrs_map) + .await?; + trustee::recompute_reference_values(ctx).await +} diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index f12e7363..323b1909 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -1,32 +1,29 @@ +use anyhow::Context; use base64::{Engine as _, engine::general_purpose}; -use crds::{KbsConfig, KbsConfigSpec, Trustee}; -use futures_util::StreamExt; +use chrono::{DateTime, TimeDelta, Utc}; use json_patch::{AddOperation, PatchOperation, TestOperation}; -use k8s_openapi::api::{ - batch::v1::{Job, JobSpec}, - core::v1::{ - ConfigMap, Container, ImageVolumeSource, PodSpec, PodTemplateSpec, Secret, Volume, - VolumeMount, - }, -}; -use kube::api::{DeleteParams, ObjectMeta, Patch, PatchParams, PostParams}; -use kube::runtime::{ - controller::{Action, Controller}, - watcher, -}; +use k8s_openapi::api::core::v1::{ConfigMap, Secret}; +use kube::api::{ObjectMeta, Patch, PatchParams, PostParams}; use kube::{Api, Client}; use log::info; use openssl::pkey::PKey; -use std::{collections::BTreeMap, fs, path::PathBuf, sync::Arc, time::Duration}; -use thiserror::Error; +use serde::{Serialize, Serializer}; +use std::{collections::BTreeMap, fs}; + +use crds::{KbsConfig, KbsConfigSpec, Trustee}; +use rv_store::*; -#[derive(Debug, Error)] -enum Error {} +const HTTPS_KEY: &str = "kbs-https-key"; +const HTTPS_CERT: &str = "kbs-https-certificate"; +const REFERENCE_VALUES_FILE: &str = "reference-values.json"; #[derive(Clone)] -struct ContextData { - #[allow(dead_code)] - client: Client, +pub struct RvContextData { + pub client: Client, + pub operator_namespace: String, + pub trustee_namespace: String, + pub pcrs_compute_image: String, + pub rv_map: String, } macro_rules! info_if_exists { @@ -40,11 +37,37 @@ macro_rules! info_if_exists { } }; } +pub(crate) use info_if_exists; -const BOOT_IMAGE: &str = "quay.io/fedora/fedora-coreos:42.20250705.3.0"; +fn primitive_date_time_to_str(d: &DateTime, s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(&d.format("%Y-%m-%dT%H:%M:%SZ").to_string()) +} -const HTTPS_KEY: &str = "kbs-https-key"; -const HTTPS_CERT: &str = "kbs-https-certificate"; +/// Sync with Trustee +/// reference_value_provider_service::reference_value::ReferenceValue +/// (cannot import directly because its expiration doesn't serialize +/// right) +#[derive(Serialize)] +struct ReferenceValue { + pub version: String, + pub name: String, + #[serde(serialize_with = "primitive_date_time_to_str")] + pub expiration: DateTime, + pub value: serde_json::Value, +} + +pub fn get_image_pcrs(image_pcrs_map: ConfigMap) -> anyhow::Result { + let image_pcrs_data = image_pcrs_map + .data + .context("Image PCRs map existed, but had no data")?; + let image_pcrs_str = image_pcrs_data + .get(PCR_CONFIG_FILE) + .context("Image PCRs data existed, but had no file")?; + serde_json::from_str(image_pcrs_str).map_err(Into::into) +} pub async fn generate_kbs_auth_public_key( client: Client, @@ -144,115 +167,67 @@ pub async fn generate_kbs_configurations( Ok(()) } -async fn reconcile(job: Arc, ctx: Arc) -> Result { - if let Some(status) = &job.status - && status.completion_time.is_some() - && let Some(ns) = &job.metadata.namespace - && let Some(name) = &job.metadata.name - { - let jobs: Api = Api::namespaced(ctx.client.clone(), ns); - if jobs.delete(name, &DeleteParams::default()).await.is_ok() { - return Ok(Action::await_change()); - } +pub async fn recompute_reference_values(ctx: RvContextData) -> anyhow::Result<()> { + let operator_config_maps: Api = + Api::namespaced(ctx.client.clone(), &ctx.operator_namespace); + let image_pcrs_map = operator_config_maps.get(PCR_CONFIG_MAP).await?; + let image_pcrs = get_image_pcrs(image_pcrs_map)?; + // TODO many grub+shim:many OS image recompute once supported + let mut reference_values_in = BTreeMap::from([( + "svn".to_string(), + vec![serde_json::Value::String("1".to_string())], + )]); + for pcr in image_pcrs.0.values().flat_map(|v| &v.pcrs) { + reference_values_in + .entry(format!("pcr{}", pcr.id)) + .or_default() + .push(serde_json::Value::String(pcr.value.clone())); } - Ok(Action::requeue(Duration::from_secs(300))) -} - -fn error_policy(_obj: Arc, _error: &Error, _ctx: Arc) -> Action { - Action::requeue(Duration::from_secs(60)) -} - -pub async fn launch_rv_job_controller(client: Client, namespace: &str) { - let jobs: Api = Api::namespaced(client.clone(), namespace); - let context = Arc::new(ContextData { client }); - // refine watcher if we handle more than one type of job - tokio::spawn( - Controller::new(jobs, watcher::Config::default()) - .run::<_, ContextData>(reconcile, error_policy, context) - .for_each(|res| async move { - match res { - Ok(o) => info!("reconciled {o:?}"), - Err(e) => info!("reconcile failed: {e:?}"), - } - }), - ); + let reference_values: Vec<_> = reference_values_in + .iter() + .map(|(name, values)| ReferenceValue { + version: "0.1.0".to_string(), + name: format!("tpm_{name}"), + expiration: Utc::now() + TimeDelta::days(365), + value: serde_json::Value::Array(values.to_vec()), + }) + .collect(); + let reference_values_json = serde_json::to_string(&reference_values)?; + let data = BTreeMap::from([( + REFERENCE_VALUES_FILE.to_string(), + reference_values_json.to_string(), + )]); + + let trustee_config_maps: Api = Api::namespaced(ctx.client, &ctx.trustee_namespace); + let mut rvs = trustee_config_maps.get(&ctx.rv_map).await?; + rvs.data = Some(data); + trustee_config_maps + .replace(&ctx.rv_map, &PostParams::default(), &rvs) + .await?; + Ok(()) } -pub async fn generate_reference_values( +pub async fn create_reference_value_config_map( client: Client, - job_namespace: &str, - trustee_namespace: &str, + namespace: &str, name: &str, - pcrs_compute_image: &str, ) -> anyhow::Result<()> { - let job_name = "compute-pcrs"; - let volume_name = "image"; - let mountpoint = PathBuf::from("/image"); - - let mut cmd = vec![job_name.to_string()]; - let mut add_flag = |flag: &str, value: &str| { - cmd.push(format!("--{flag}")); - cmd.push(value.to_string()); - }; - for (flag, path_suffix) in [ - ("kernels", "usr/lib/modules"), - ("esp", "usr/lib/bootupd/updates"), - ] { - let full_path = mountpoint.join(path_suffix); - add_flag(flag, full_path.to_str().unwrap()); - } - for (flag, value) in [ - ("configmap", name), - ("namespace", trustee_namespace), - ("efivars", "/reference-values/efivars/qemu-ovmf/fedora-42"), - ("mokvars", "/reference-values/mok-variables/fedora-42"), - ] { - add_flag(flag, value); - } - - let pod_spec = PodSpec { - service_account_name: Some("compute-pcrs".to_string()), - containers: vec![Container { - name: job_name.to_string(), - image: Some(pcrs_compute_image.to_string()), - command: Some(cmd), - volume_mounts: Some(vec![VolumeMount { - name: volume_name.to_string(), - mount_path: mountpoint.to_str().unwrap().to_string(), - ..Default::default() - }]), - ..Default::default() - }], - volumes: Some(vec![Volume { - name: volume_name.to_string(), - image: Some(ImageVolumeSource { - reference: Some(BOOT_IMAGE.to_string()), - ..Default::default() - }), - ..Default::default() - }]), - restart_policy: Some("Never".to_string()), - ..Default::default() - }; - let job = Job { + let empty_data = BTreeMap::from([(REFERENCE_VALUES_FILE.to_string(), "{}".to_string())]); + let config_maps: Api = Api::namespaced(client, namespace); + let config_map = ConfigMap { metadata: ObjectMeta { - name: Some(job_name.to_string()), - namespace: Some(job_namespace.to_string()), + name: Some(name.to_string()), + namespace: Some(namespace.to_string()), ..Default::default() }, - spec: Some(JobSpec { - template: PodTemplateSpec { - spec: Some(pod_spec), - ..Default::default() - }, - ..Default::default() - }), + data: Some(empty_data), ..Default::default() }; + let create = config_maps + .create(&PostParams::default(), &config_map) + .await; + info_if_exists!(create, "ConfigMap", name); - let jobs: Api = Api::namespaced(client.clone(), job_namespace); - let create = jobs.create(&PostParams::default(), &job).await; - info_if_exists!(create, "Job", job_name); Ok(()) } diff --git a/rv-store/Cargo.toml b/rv-store/Cargo.toml new file mode 100644 index 00000000..702d4cd8 --- /dev/null +++ b/rv-store/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rv-store" +version = "0.1.0" +description = "Reference values store definition for operator and compute-pcrs" +edition.workspace = true + +[dependencies] +chrono.workspace = true +compute-pcrs-lib.workspace = true +serde.workspace = true diff --git a/rv-store/src/lib.rs b/rv-store/src/lib.rs new file mode 100644 index 00000000..ed2a5c28 --- /dev/null +++ b/rv-store/src/lib.rs @@ -0,0 +1,16 @@ +use chrono::{DateTime, Utc}; +use compute_pcrs_lib::Pcr; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +pub const PCR_CONFIG_MAP: &str = "image-pcrs"; +pub const PCR_CONFIG_FILE: &str = "image-pcrs.json"; + +#[derive(Deserialize, Serialize)] +pub struct ImagePcr { + pub first_seen: DateTime, + pub pcrs: Vec, +} + +#[derive(Default, Deserialize, Serialize)] +pub struct ImagePcrs(pub BTreeMap);