From d97987fda7f223048621492077d1e74bfbc4e39b Mon Sep 17 00:00:00 2001 From: Chris Staite Date: Tue, 12 Dec 2023 18:47:09 +0000 Subject: [PATCH] Add support for mTLS There is a need to secure the Action Cache for production builds which is not possible with out authentication of the store. This can be provided by mTLS and the ability to make the action cache read only for some servers. --- Cargo.Bazel.lock | 20 ++- Cargo.lock | 148 ++++++++++----------- Cargo.toml | 2 +- nativelink-config/src/cas_server.rs | 41 ++++-- nativelink-config/src/schedulers.rs | 6 +- nativelink-config/src/serde_utils.rs | 16 ++- nativelink-config/src/stores.rs | 22 ++- nativelink-scheduler/Cargo.toml | 2 +- nativelink-scheduler/src/grpc_scheduler.rs | 14 +- nativelink-service/Cargo.toml | 2 +- nativelink-service/src/ac_server.rs | 38 ++++-- nativelink-service/tests/ac_server_test.rs | 1 + nativelink-store/Cargo.toml | 2 +- nativelink-store/src/grpc_store.rs | 14 +- nativelink-util/BUILD.bazel | 2 + nativelink-util/Cargo.toml | 1 + nativelink-util/src/lib.rs | 1 + nativelink-util/src/tls_utils.rs | 66 +++++++++ nativelink-worker/Cargo.toml | 2 +- nativelink-worker/src/local_worker.rs | 18 +-- src/bin/nativelink.rs | 89 +++++++++---- 21 files changed, 354 insertions(+), 153 deletions(-) create mode 100644 nativelink-util/src/tls_utils.rs diff --git a/Cargo.Bazel.lock b/Cargo.Bazel.lock index d7c1631d9..ab628e588 100644 --- a/Cargo.Bazel.lock +++ b/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "a6b16863ad5d0a1a72b20d57a7b103696e736e78f865de8cee087e4876a07970", + "checksum": "af852655d9eacd497a80742a4a6e9066b95c0a236ee1b77f438a7a8d7fef1237", "crates": { "addr2line 0.21.0": { "name": "addr2line", @@ -9089,6 +9089,10 @@ "id": "tokio-util 0.7.10", "target": "tokio_util" }, + { + "id": "tonic 0.10.2", + "target": "tonic" + }, { "id": "tracing 0.1.40", "target": "tracing" @@ -15084,6 +15088,7 @@ ], "crate_features": { "common": [ + "default", "logging", "tls12" ], @@ -15326,6 +15331,7 @@ "default", "gzip", "prost", + "tls", "transport" ], "selects": {} @@ -15384,10 +15390,22 @@ "id": "prost 0.12.3", "target": "prost" }, + { + "id": "rustls 0.21.10", + "target": "rustls" + }, + { + "id": "rustls-pemfile 1.0.4", + "target": "rustls_pemfile" + }, { "id": "tokio 1.35.1", "target": "tokio" }, + { + "id": "tokio-rustls 0.24.1", + "target": "tokio_rustls" + }, { "id": "tokio-stream 0.1.14", "target": "tokio_stream" diff --git a/Cargo.lock b/Cargo.lock index 5ec15448e..0759d7db4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,9 +46,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" dependencies = [ "anstyle", "anstyle-parse", @@ -123,9 +123,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" dependencies = [ "event-listener", "event-listener-strategy", @@ -151,7 +151,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -162,7 +162,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -639,9 +639,9 @@ checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64-simd" @@ -741,9 +741,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.12" +version = "4.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" +checksum = "58e54881c004cec7895b0068a0a954cd5d62da01aef83fa35b1e594497bf5445" dependencies = [ "clap_builder", "clap_derive", @@ -751,9 +751,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.12" +version = "4.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" +checksum = "59cb82d7f531603d2fd1f507441cdd35184fa81beff7bd489570de7f773460bb" dependencies = [ "anstream", "anstyle", @@ -770,7 +770,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -861,9 +861,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -888,22 +888,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crypto-bigint" @@ -1046,9 +1042,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "218a870470cce1469024e9fb66b901aa983929d81304a1cdb299f28118e550d5" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" dependencies = [ "concurrent-queue", "parking", @@ -1198,7 +1194,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -1243,9 +1239,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -1271,9 +1267,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "b553656127a00601c8ae5590fcfdc118e4083a7924b6cf4ffc1ea4b99dc429d7" dependencies = [ "bytes", "fnv", @@ -1499,9 +1495,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libredox" @@ -1547,9 +1543,9 @@ dependencies = [ [[package]] name = "lz4_flex" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea9b256699eda7b0387ffbc776dd625e28bde3918446381781245b7a50349d8" +checksum = "912b45c753ff5f7f5208307e8ace7d2a2e30d024e26d3509f3dce546c044ce15" dependencies = [ "twox-hash", ] @@ -1830,6 +1826,7 @@ dependencies = [ "sha2", "tokio", "tokio-util", + "tonic", "tracing", ] @@ -2016,9 +2013,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" dependencies = [ "memchr", "thiserror", @@ -2027,9 +2024,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" dependencies = [ "pest", "pest_generator", @@ -2037,22 +2034,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] name = "pest_meta" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" dependencies = [ "once_cell", "pest", @@ -2086,7 +2083,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -2140,7 +2137,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -2169,9 +2166,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.74" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -2196,7 +2193,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -2226,7 +2223,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.46", + "syn 2.0.48", "tempfile", "which", ] @@ -2241,7 +2238,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -2413,9 +2410,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.1", "errno", @@ -2438,14 +2435,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6b63262c9fcac8659abfaa96cac103d28166d3ff3eaf8f412e19f3ae9e5a48" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.0", + "rustls-webpki 0.102.1", "subtle", "zeroize", ] @@ -2499,9 +2496,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.0" +version = "0.102.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2635c8bc2b88d367767c5de8ea1d8db9af3f6219eba28442242d9ab81d1b89" +checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b" dependencies = [ "ring", "rustls-pki-types", @@ -2590,29 +2587,29 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.110" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -2728,9 +2725,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" [[package]] name = "socket2" @@ -2789,9 +2786,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.46" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -2834,7 +2831,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -2928,7 +2925,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -2947,7 +2944,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.22.1", + "rustls 0.22.2", "rustls-pki-types", "tokio", ] @@ -2998,7 +2995,10 @@ dependencies = [ "percent-encoding", "pin-project", "prost", + "rustls 0.21.10", + "rustls-pemfile 1.0.4", "tokio", + "tokio-rustls 0.24.1", "tokio-stream", "tower", "tower-layer", @@ -3016,7 +3016,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -3071,7 +3071,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] @@ -3447,7 +3447,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.48", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e18cf0ed5..2103e255d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ scopeguard = "1.2.0" serde_json5 = "0.1.0" tokio = { version = "1.35.1", features = ["rt-multi-thread", "signal"] } tokio-rustls = "0.25.0" -tonic = { version = "0.10.2", features = ["gzip"] } +tonic = { version = "0.10.2", features = ["gzip", "tls"] } tower = "0.4.13" tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } diff --git a/nativelink-config/src/cas_server.rs b/nativelink-config/src/cas_server.rs index a771f61c3..5134079f5 100644 --- a/nativelink-config/src/cas_server.rs +++ b/nativelink-config/src/cas_server.rs @@ -18,9 +18,10 @@ use serde::Deserialize; use crate::schedulers::SchedulerConfig; use crate::serde_utils::{ - convert_numeric_with_shellexpand, convert_optinoal_numeric_with_shellexpand, convert_string_with_shellexpand, + convert_numeric_with_shellexpand, convert_optional_numeric_with_shellexpand, + convert_optional_string_with_shellexpand, convert_string_with_shellexpand, }; -use crate::stores::{ConfigDigestHashFunction, StoreConfig, StoreRefName}; +use crate::stores::{ClientTlsConfig, ConfigDigestHashFunction, StoreConfig, StoreRefName}; /// Name of the scheduler. This type will be used when referencing a /// scheduler in the `CasConfig::schedulers`'s map key. @@ -77,6 +78,11 @@ pub struct AcStoreConfig { /// This store name referenced here may be reused multiple times. #[serde(deserialize_with = "convert_string_with_shellexpand")] pub ac_store: StoreRefName, + + /// Whether the Action Cache store may be written to, this if set to false + /// it is only possible to read from the Action Cache. + #[serde(default)] + pub read_only: bool, } #[derive(Deserialize, Debug)] @@ -221,6 +227,16 @@ pub struct TlsConfig { /// Path to the private key file. #[serde(deserialize_with = "convert_string_with_shellexpand")] pub key_file: String, + + /// Path to the certificate authority for mTLS, if client authentication is + /// required for this endpoint. + #[serde(default, deserialize_with = "convert_optional_string_with_shellexpand")] + pub client_ca_file: Option, + + /// Path to the certificate revocation list for mTLS, if client + /// authentication is required for this endpoint. + #[serde(default, deserialize_with = "convert_optional_string_with_shellexpand")] + pub client_crl_file: Option, } /// Advanced Http configurations. These are generally should not be set. @@ -233,38 +249,38 @@ pub struct TlsConfig { pub struct HttpServerConfig { /// Interval to send keep-alive pings via HTTP2. /// Note: This is in seconds. - #[serde(default, deserialize_with = "convert_optinoal_numeric_with_shellexpand")] + #[serde(default, deserialize_with = "convert_optional_numeric_with_shellexpand")] pub http2_keep_alive_interval: Option, - #[serde(default, deserialize_with = "convert_optinoal_numeric_with_shellexpand")] + #[serde(default, deserialize_with = "convert_optional_numeric_with_shellexpand")] pub experimental_http2_max_pending_accept_reset_streams: Option, - #[serde(default, deserialize_with = "convert_optinoal_numeric_with_shellexpand")] + #[serde(default, deserialize_with = "convert_optional_numeric_with_shellexpand")] pub experimental_http2_initial_stream_window_size: Option, - #[serde(default, deserialize_with = "convert_optinoal_numeric_with_shellexpand")] + #[serde(default, deserialize_with = "convert_optional_numeric_with_shellexpand")] pub experimental_http2_initial_connection_window_size: Option, #[serde(default)] pub experimental_http2_adaptive_window: Option, - #[serde(default, deserialize_with = "convert_optinoal_numeric_with_shellexpand")] + #[serde(default, deserialize_with = "convert_optional_numeric_with_shellexpand")] pub experimental_http2_max_frame_size: Option, - #[serde(default, deserialize_with = "convert_optinoal_numeric_with_shellexpand")] + #[serde(default, deserialize_with = "convert_optional_numeric_with_shellexpand")] pub experimental_http2_max_concurrent_streams: Option, /// Note: This is in seconds. - #[serde(default, deserialize_with = "convert_optinoal_numeric_with_shellexpand")] + #[serde(default, deserialize_with = "convert_optional_numeric_with_shellexpand")] pub experimental_http2_keep_alive_timeout: Option, - #[serde(default, deserialize_with = "convert_optinoal_numeric_with_shellexpand")] + #[serde(default, deserialize_with = "convert_optional_numeric_with_shellexpand")] pub experimental_http2_max_send_buf_size: Option, #[serde(default)] pub experimental_http2_enable_connect_protocol: Option, - #[serde(default, deserialize_with = "convert_optinoal_numeric_with_shellexpand")] + #[serde(default, deserialize_with = "convert_optional_numeric_with_shellexpand")] pub experimental_http2_max_header_list_size: Option, } @@ -337,6 +353,9 @@ pub struct EndpointConfig { /// Timeout in seconds that a request should take. /// Default: 5 (seconds) pub timeout: Option, + + /// The TLS configuration to use to connect to the endpoint. + pub tls_config: Option, } #[allow(non_camel_case_types)] diff --git a/nativelink-config/src/schedulers.rs b/nativelink-config/src/schedulers.rs index 3dc954a0b..8aae44adc 100644 --- a/nativelink-config/src/schedulers.rs +++ b/nativelink-config/src/schedulers.rs @@ -17,7 +17,7 @@ use std::collections::HashMap; use serde::Deserialize; use crate::serde_utils::{convert_numeric_with_shellexpand, convert_string_with_shellexpand}; -use crate::stores::{Retry, StoreRefName}; +use crate::stores::{ClientTlsConfig, Retry, StoreRefName}; #[allow(non_camel_case_types)] #[derive(Deserialize, Debug)] @@ -129,9 +129,13 @@ pub struct GrpcScheduler { /// The upstream scheduler to forward requests to. #[serde(deserialize_with = "convert_string_with_shellexpand")] pub endpoint: String, + /// Retry configuration to use when a network request fails. #[serde(default)] pub retry: Retry, + + /// The TLS configuration to use to connect to the endpoint. + pub tls_config: Option, } #[derive(Deserialize, Debug)] diff --git a/nativelink-config/src/serde_utils.rs b/nativelink-config/src/serde_utils.rs index efa750d93..937825e36 100644 --- a/nativelink-config/src/serde_utils.rs +++ b/nativelink-config/src/serde_utils.rs @@ -58,7 +58,7 @@ where } /// Same as convert_numeric_with_shellexpand, but supports Option. -pub fn convert_optinoal_numeric_with_shellexpand<'de, D, T, E>(deserializer: D) -> Result, D::Error> +pub fn convert_optional_numeric_with_shellexpand<'de, D, T, E>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, E: fmt::Display, @@ -106,3 +106,17 @@ pub fn convert_string_with_shellexpand<'de, D: Deserializer<'de>>(deserializer: let value = String::deserialize(deserializer)?; Ok((*(shellexpand::env(&value).map_err(de::Error::custom)?)).to_string()) } + +/// Same as convert_string_with_shellexpand, but supports Option. +pub fn convert_optional_string_with_shellexpand<'de, D: Deserializer<'de>>( + deserializer: D, +) -> Result, D::Error> { + let value = Option::::deserialize(deserializer)?; + if let Some(value) = value { + Ok(Some( + (*(shellexpand::env(&value).map_err(de::Error::custom)?)).to_string(), + )) + } else { + Ok(None) + } +} diff --git a/nativelink-config/src/stores.rs b/nativelink-config/src/stores.rs index 6b5e5a662..9ee9a33d7 100644 --- a/nativelink-config/src/stores.rs +++ b/nativelink-config/src/stores.rs @@ -14,7 +14,9 @@ use serde::{Deserialize, Serialize}; -use crate::serde_utils::{convert_numeric_with_shellexpand, convert_string_with_shellexpand}; +use crate::serde_utils::{ + convert_numeric_with_shellexpand, convert_optional_string_with_shellexpand, convert_string_with_shellexpand, +}; /// Name of the store. This type will be used when referencing a store /// in the `CasConfig::stores`'s map key. @@ -476,6 +478,21 @@ pub enum StoreType { ac, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ClientTlsConfig { + /// Path to the certificate authority to use to validate the remote. + #[serde(deserialize_with = "convert_string_with_shellexpand")] + pub ca_file: String, + + /// Path to the certificate file for client authentication. + #[serde(deserialize_with = "convert_optional_string_with_shellexpand")] + pub cert_file: Option, + + /// Path to the private key file for client authentication. + #[serde(deserialize_with = "convert_optional_string_with_shellexpand")] + pub key_file: Option, +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GrpcStore { /// Instance name for GRPC calls. Proxy calls will have the instance_name changed to this. @@ -492,6 +509,9 @@ pub struct GrpcStore { /// Retry configuration to use when a network request fails. #[serde(default)] pub retry: Retry, + + /// The TLS configuration to use to connect to the endpoints. + pub tls_config: Option, } /// Retry configuration. This configuration is exponential and each iteration diff --git a/nativelink-scheduler/Cargo.toml b/nativelink-scheduler/Cargo.toml index e29ef1873..ed781c9c5 100644 --- a/nativelink-scheduler/Cargo.toml +++ b/nativelink-scheduler/Cargo.toml @@ -25,7 +25,7 @@ rand = "0.8.5" scopeguard = "1.2.0" tokio = { version = "1.35.1", features = ["sync", "rt", "parking_lot"] } tokio-stream = { version = "0.1.14", features = ["sync"] } -tonic = { version = "0.10.2", features = ["gzip"] } +tonic = { version = "0.10.2", features = ["gzip", "tls"] } tracing = "0.1.40" [dev-dependencies] diff --git a/nativelink-scheduler/src/grpc_scheduler.rs b/nativelink-scheduler/src/grpc_scheduler.rs index fb856bc94..50e4ae656 100644 --- a/nativelink-scheduler/src/grpc_scheduler.rs +++ b/nativelink-scheduler/src/grpc_scheduler.rs @@ -29,6 +29,7 @@ use nativelink_proto::build::bazel::remote::execution::v2::{ use nativelink_proto::google::longrunning::Operation; use nativelink_util::action_messages::{ActionInfo, ActionInfoHashKey, ActionState, DEFAULT_EXECUTION_PRIORITY}; use nativelink_util::retry::{ExponentialBackoff, Retrier, RetryResult}; +use nativelink_util::tls_utils; use parking_lot::Mutex; use rand::rngs::OsRng; use rand::Rng; @@ -70,14 +71,13 @@ impl GrpcScheduler { config: &nativelink_config::schedulers::GrpcScheduler, jitter_fn: Box Duration + Send + Sync>, ) -> Result { - let endpoint = transport::Channel::balance_list(std::iter::once( - transport::Endpoint::new(config.endpoint.clone()) - .err_tip(|| format!("Could not parse {} in GrpcScheduler", config.endpoint))?, - )); - + let channel = transport::Channel::balance_list(std::iter::once(tls_utils::endpoint_from( + &config.endpoint, + tls_utils::load_client_config(&config.tls_config)?, + )?)); Ok(Self { - capabilities_client: CapabilitiesClient::new(endpoint.clone()), - execution_client: ExecutionClient::new(endpoint), + capabilities_client: CapabilitiesClient::new(channel.clone()), + execution_client: ExecutionClient::new(channel), platform_property_managers: Mutex::new(HashMap::new()), jitter_fn, retry: config.retry.clone(), diff --git a/nativelink-service/Cargo.toml b/nativelink-service/Cargo.toml index 75ee83d99..c5c27c469 100644 --- a/nativelink-service/Cargo.toml +++ b/nativelink-service/Cargo.toml @@ -19,7 +19,7 @@ prost = "0.12.3" rand = "0.8.5" tokio = { version = "1.35.1", features = ["sync", "rt"] } tokio-stream = { version = "0.1.14", features = ["sync"] } -tonic = { version = "0.10.2", features = ["gzip"] } +tonic = { version = "0.10.2", features = ["gzip", "tls"] } tracing = "0.1.40" uuid = { version = "1.6.1", features = ["v4"] } diff --git a/nativelink-service/src/ac_server.rs b/nativelink-service/src/ac_server.rs index 5a15dfee6..bbf06dbd8 100644 --- a/nativelink-service/src/ac_server.rs +++ b/nativelink-service/src/ac_server.rs @@ -20,7 +20,7 @@ use std::time::Instant; use bytes::BytesMut; use nativelink_config::cas_server::{AcStoreConfig, InstanceName}; -use nativelink_error::{make_input_err, Code, Error, ResultExt}; +use nativelink_error::{make_err, make_input_err, Code, Error, ResultExt}; use nativelink_proto::build::bazel::remote::execution::v2::action_cache_server::{ ActionCache, ActionCacheServer as Server, }; @@ -36,8 +36,14 @@ use prost::Message; use tonic::{Request, Response, Status}; use tracing::{error, info}; +#[derive(Clone)] +pub struct AcStoreInfo { + store: Arc, + read_only: bool, +} + pub struct AcServer { - stores: HashMap>, + stores: HashMap, } impl AcServer { @@ -47,7 +53,13 @@ impl AcServer { let store = store_manager .get_store(&ac_cfg.ac_store) .ok_or_else(|| make_input_err!("'ac_store': '{}' does not exist", ac_cfg.ac_store))?; - stores.insert(instance_name.to_string(), store); + stores.insert( + instance_name.to_string(), + AcStoreInfo { + store, + read_only: ac_cfg.read_only, + }, + ); } Ok(AcServer { stores: stores.clone() }) } @@ -63,7 +75,7 @@ impl AcServer { let get_action_request = grpc_request.into_inner(); let instance_name = &get_action_request.instance_name; - let store = self + let store_info = self .stores .get(instance_name) .err_tip(|| format!("'instance_name' not configured for '{}'", instance_name))?; @@ -76,14 +88,14 @@ impl AcServer { .try_into()?; // If we are a GrpcStore we shortcut here, as this is a special store. - let any_store = store.clone().inner_store(Some(digest)).as_any(); + let any_store = store_info.store.clone().inner_store(Some(digest)).as_any(); let maybe_grpc_store = any_store.downcast_ref::>(); if let Some(grpc_store) = maybe_grpc_store { return grpc_store.get_action_result(Request::new(get_action_request)).await; } Ok(Response::new( - get_and_decode_digest::(Pin::new(store.as_ref()), &digest).await?, + get_and_decode_digest::(Pin::new(store_info.store.as_ref()), &digest).await?, )) } @@ -94,11 +106,18 @@ impl AcServer { let update_action_request = grpc_request.into_inner(); let instance_name = &update_action_request.instance_name; - let store = self + let store_info = self .stores .get(instance_name) .err_tip(|| format!("'instance_name' not configured for '{}'", instance_name))?; + if store_info.read_only { + return Err(make_err!( + Code::PermissionDenied, + "The store '{instance_name}' is read only on this endpoint", + )); + } + let digest: DigestInfo = update_action_request .action_digest .clone() @@ -106,7 +125,8 @@ impl AcServer { .try_into()?; // If we are a GrpcStore we shortcut here, as this is a special store. - let any_store = store.clone().inner_store(Some(digest)).as_any(); + let any_store = store_info.store.clone().inner_store(Some(digest)).as_any(); + let maybe_grpc_store = any_store.downcast_ref::>(); if let Some(grpc_store) = maybe_grpc_store { return grpc_store @@ -123,7 +143,7 @@ impl AcServer { .encode(&mut store_data) .err_tip(|| "Provided ActionResult could not be serialized")?; - Pin::new(store.as_ref()) + Pin::new(store_info.store.as_ref()) .update_oneshot(digest, store_data.freeze()) .await .err_tip(|| "Failed to update in action cache")?; diff --git a/nativelink-service/tests/ac_server_test.rs b/nativelink-service/tests/ac_server_test.rs index 2ebc9ea24..04a10234e 100644 --- a/nativelink-service/tests/ac_server_test.rs +++ b/nativelink-service/tests/ac_server_test.rs @@ -75,6 +75,7 @@ fn make_ac_server(store_manager: &StoreManager) -> Result { &hashmap! { "foo_instance_name".to_string() => nativelink_config::cas_server::AcStoreConfig{ ac_store: "main_ac".to_string(), + read_only: false, } }, store_manager, diff --git a/nativelink-store/Cargo.toml b/nativelink-store/Cargo.toml index 5751c0269..a1c67bdb1 100644 --- a/nativelink-store/Cargo.toml +++ b/nativelink-store/Cargo.toml @@ -33,7 +33,7 @@ shellexpand = "3.1.0" tokio = { version = "1.35.1" } tokio-stream = { version = "0.1.14", features = ["fs"] } tokio-util = { version = "0.7.10" } -tonic = { version = "0.10.2", features = ["gzip"] } +tonic = { version = "0.10.2", features = ["gzip", "tls"] } tracing = "0.1.40" uuid = { version = "1.6.1", features = ["v4"] } diff --git a/nativelink-store/src/grpc_store.rs b/nativelink-store/src/grpc_store.rs index 7ea6c47ec..107909364 100644 --- a/nativelink-store/src/grpc_store.rs +++ b/nativelink-store/src/grpc_store.rs @@ -37,6 +37,7 @@ use nativelink_util::buf_channel::{DropCloserReadHalf, DropCloserWriteHalf}; use nativelink_util::common::DigestInfo; use nativelink_util::retry::{ExponentialBackoff, Retrier, RetryResult}; use nativelink_util::store_trait::{Store, UploadSizeInfo}; +use nativelink_util::tls_utils; use nativelink_util::write_request_stream_wrapper::WriteRequestStreamWrapper; use parking_lot::Mutex; use prost::Message; @@ -86,19 +87,20 @@ impl GrpcStore { jitter_fn: Box Duration + Send + Sync>, ) -> Result { error_if!(config.endpoints.is_empty(), "Expected at least 1 endpoint in GrpcStore"); + let tls_config = tls_utils::load_client_config(&config.tls_config)?; let mut endpoints = Vec::with_capacity(config.endpoints.len()); for endpoint in &config.endpoints { // TODO(allada) This should be moved to be done in utils/serde_utils.rs like the others. // We currently don't have a way to handle those helpers with vectors. let endpoint = shellexpand::env(&endpoint) - .map_err(|e| make_input_err!("{}", e)) - .err_tip(|| "Could expand endpoint in GrpcStore")? + .map_err(|e| make_input_err!("{e}")) + .err_tip(|| "Could not expand endpoint in GrpcStore")? .to_string(); - endpoints.push( - transport::Endpoint::new(endpoint.clone()) - .err_tip(|| format!("Could not connect to {} in GrpcStore", endpoint))?, - ); + let endpoint = tls_utils::endpoint_from(&endpoint, tls_config.clone()) + .map_err(|e| make_input_err!("Invalid URI for GrpcStore endpoint : {e:?}"))?; + + endpoints.push(endpoint); } let conn = transport::Channel::balance_list(endpoints.into_iter()); diff --git a/nativelink-util/BUILD.bazel b/nativelink-util/BUILD.bazel index d30035748..9d37be3ab 100644 --- a/nativelink-util/BUILD.bazel +++ b/nativelink-util/BUILD.bazel @@ -22,6 +22,7 @@ rust_library( "src/resource_info.rs", "src/retry.rs", "src/store_trait.rs", + "src/tls_utils.rs", "src/write_counter.rs", "src/write_request_stream_wrapper.rs", ], @@ -49,6 +50,7 @@ rust_library( "@crate_index//:sha2", "@crate_index//:tokio", "@crate_index//:tokio-util", + "@crate_index//:tonic", "@crate_index//:tracing", ], ) diff --git a/nativelink-util/Cargo.toml b/nativelink-util/Cargo.toml index 37057c2e5..3bcf20bd7 100644 --- a/nativelink-util/Cargo.toml +++ b/nativelink-util/Cargo.toml @@ -25,6 +25,7 @@ serde = { version = "1.0.193", features = ["derive"] } sha2 = "0.10.8" tokio = { version = "1.35.1", features = [ "sync", "fs", "rt", "time", "io-util", "macros" ] } tokio-util = { version = "0.7.10" } +tonic = { version = "0.10.2", features = ["tls"] } tracing = "0.1.40" [dev-dependencies] diff --git a/nativelink-util/src/lib.rs b/nativelink-util/src/lib.rs index beaae5d04..3dad898e8 100644 --- a/nativelink-util/src/lib.rs +++ b/nativelink-util/src/lib.rs @@ -24,5 +24,6 @@ pub mod platform_properties; pub mod resource_info; pub mod retry; pub mod store_trait; +pub mod tls_utils; pub mod write_counter; pub mod write_request_stream_wrapper; diff --git a/nativelink-util/src/tls_utils.rs b/nativelink-util/src/tls_utils.rs new file mode 100644 index 000000000..cf9b74ab0 --- /dev/null +++ b/nativelink-util/src/tls_utils.rs @@ -0,0 +1,66 @@ +// Copyright 2024 The Native Link Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tonic::transport::Uri; + +use nativelink_config::stores::ClientTlsConfig; +use nativelink_error::{make_err, make_input_err, Code, Error}; + +pub fn load_client_config(config: &Option) -> Result, Error> { + let Some(config) = config else { + return Ok(None); + }; + + let read_config = tonic::transport::ClientTlsConfig::new().ca_certificate(tonic::transport::Certificate::from_pem( + std::fs::read_to_string(&config.ca_file)?, + )); + let config = if let Some(client_certificate) = &config.cert_file { + let Some(client_key) = &config.key_file else { + return Err(make_err!(Code::Internal, "Client certificate specified, but no key")); + }; + read_config.identity(tonic::transport::Identity::from_pem( + std::fs::read_to_string(client_certificate)?, + std::fs::read_to_string(client_key)?, + )) + } else { + if config.key_file.is_some() { + return Err(make_err!(Code::Internal, "Client key specified, but no certificate")); + } + read_config + }; + + Ok(Some(config)) +} + +pub fn endpoint_from( + endpoint: &str, + tls_config: Option, +) -> Result { + let endpoint = Uri::try_from(endpoint) + .map_err(|e| make_err!(Code::Internal, "Unable to parse endpoint {endpoint}: {e:?}"))?; + + let endpoint_transport = if let Some(tls_config) = &tls_config { + let Some(authority) = endpoint.authority() else { + return Err(make_input_err!("Unable to determine authority of endpont: {endpoint}")); + }; + let tls_config = tls_config.clone().domain_name(authority.host()); + tonic::transport::Endpoint::from(endpoint) + .tls_config(tls_config) + .map_err(|e| make_input_err!("Setting mTLS configuration: {e:?}"))? + } else { + tonic::transport::Endpoint::from(endpoint) + }; + + Ok(endpoint_transport) +} diff --git a/nativelink-worker/Cargo.toml b/nativelink-worker/Cargo.toml index af0e103db..19b577e01 100644 --- a/nativelink-worker/Cargo.toml +++ b/nativelink-worker/Cargo.toml @@ -30,7 +30,7 @@ serde_json5 = "0.1.0" shlex = "1.2.0" tokio = { version = "1.35.1", features = ["sync", "rt", "process"] } tokio-stream = { version = "0.1.14", features = ["fs"] } -tonic = { version = "0.10.2", features = ["gzip"] } +tonic = { version = "0.10.2", features = ["gzip", "tls"] } tracing = "0.1.40" uuid = { version = "1.6.1", features = ["v4"] } diff --git a/nativelink-worker/src/local_worker.rs b/nativelink-worker/src/local_worker.rs index f63d49d27..e08f28b55 100644 --- a/nativelink-worker/src/local_worker.rs +++ b/nativelink-worker/src/local_worker.rs @@ -37,11 +37,11 @@ use nativelink_util::metrics_utils::{ AsyncCounterWrapper, Collector, CollectorState, CounterWithTime, MetricsComponent, Registry, }; use nativelink_util::store_trait::Store; +use nativelink_util::tls_utils; use tokio::process; use tokio::sync::mpsc; use tokio::time::sleep; use tokio_stream::wrappers::UnboundedReceiverStream; -use tonic::transport::Channel as TonicChannel; use tonic::Streaming; use tracing::{error, warn}; @@ -381,21 +381,17 @@ pub async fn new_local_worker( let timeout = config.worker_api_endpoint.timeout.unwrap_or(DEFAULT_ENDPOINT_TIMEOUT_S); let timeout_duration = Duration::from_secs_f32(timeout); - let uri = config - .worker_api_endpoint - .uri - .clone() - .try_into() - .map_err(|e| make_input_err!("Invalid URI for worker endpoint : {:?}", e))?; - let endpoint = TonicChannel::builder(uri) + let tls_config = tls_utils::load_client_config(&config.worker_api_endpoint.tls_config)?; + let endpoint = tls_utils::endpoint_from(&config.worker_api_endpoint.uri, tls_config) + .map_err(|e| make_input_err!("Invalid URI for worker endpoint : {e:?}"))? .connect_timeout(timeout_duration) .timeout(timeout_duration); + let transport = endpoint.connect().await.map_err(|e| { make_err!( Code::Internal, - "Could not connect to endpoint {}: {:?}", - config.worker_api_endpoint.uri, - e + "Could not connect to endpoint {}: {e:?}", + config.worker_api_endpoint.uri ) })?; Ok(WorkerApiClient::new(transport).into()) diff --git a/src/bin/nativelink.rs b/src/bin/nativelink.rs index 29de99661..9c01d330b 100644 --- a/src/bin/nativelink.rs +++ b/src/bin/nativelink.rs @@ -1,4 +1,4 @@ -// Copyright 2022 The Native Link Authors. All rights reserved. +// Copyright 2024 The Native Link Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -46,13 +46,15 @@ use nativelink_util::metrics_utils::{ }; use nativelink_worker::local_worker::new_local_worker; use parking_lot::Mutex; -use rustls_pemfile::{certs as extract_certs, pkcs8_private_keys}; +use rustls_pemfile::{certs as extract_certs, crls as extract_crls}; use scopeguard::guard; use tokio::net::TcpListener; #[cfg(target_family = "unix")] use tokio::signal::unix::{signal, SignalKind}; use tokio::task::spawn_blocking; -use tokio_rustls::rustls::ServerConfig as TlsServerConfig; +use tokio_rustls::rustls::pki_types::{CertificateDer, CertificateRevocationListDer}; +use tokio_rustls::rustls::server::WebPkiClientVerifier; +use tokio_rustls::rustls::{RootCertStore, ServerConfig as TlsServerConfig}; use tokio_rustls::TlsAcceptor; use tonic::codec::CompressionEncoding; use tonic::transport::Server as TonicServer; @@ -443,37 +445,72 @@ async fn inner_main(cfg: CasConfig, server_start_timestamp: u64) -> Result<(), B // Configure our TLS acceptor if we have TLS configured. let maybe_tls_acceptor = http_config.tls.map_or(Ok(None), |tls_config| { - let mut cert_reader = std::io::BufReader::new( - std::fs::File::open(&tls_config.cert_file) - .err_tip(|| format!("Could not open cert file {}", tls_config.cert_file))?, - ); - let mut certs = vec![]; - for cert in extract_certs(&mut cert_reader) { - certs.push(cert.err_tip(|| format!("Could not extract certs from file {}", tls_config.cert_file))?); + fn read_cert(cert_file: &str) -> Result>, Error> { + let mut cert_reader = std::io::BufReader::new( + std::fs::File::open(cert_file).err_tip(|| format!("Could not open cert file {cert_file}"))?, + ); + let certs = extract_certs(&mut cert_reader) + .map(|certificate| certificate.map(CertificateDer::from)) + .collect::>, _>>() + .err_tip(|| format!("Could not extract certs from file {cert_file}"))?; + Ok(certs) } + let certs = read_cert(&tls_config.cert_file)?; let mut key_reader = std::io::BufReader::new( std::fs::File::open(&tls_config.key_file) .err_tip(|| format!("Could not open key file {}", tls_config.key_file))?, ); - let key = { - let keys = pkcs8_private_keys(&mut key_reader).collect::>(); - if keys.len() != 1 { - return Err(Box::new(make_err!( - Code::InvalidArgument, - "Expected 1 key in file {}, found {} keys", - tls_config.key_file, - keys.len() - ))); + let key = match rustls_pemfile::read_one(&mut key_reader) + .err_tip(|| format!("Could not extract key(s) from file {}", tls_config.key_file))? + { + Some(rustls_pemfile::Item::Pkcs8Key(key)) => key.into(), + Some(rustls_pemfile::Item::Sec1Key(key)) => key.into(), + Some(rustls_pemfile::Item::Pkcs1Key(key)) => key.into(), + _ => { + return Err(make_err!( + Code::Internal, + "No keys found in file {}", + tls_config.key_file + )) + } + }; + if let Ok(Some(_)) = rustls_pemfile::read_one(&mut key_reader) { + return Err(make_err!( + Code::InvalidArgument, + "Expected 1 key in file {}", + tls_config.key_file + )); + } + let verifier = if let Some(client_ca_file) = &tls_config.client_ca_file { + let mut client_auth_roots = RootCertStore::empty(); + for cert in read_cert(client_ca_file)?.into_iter() { + client_auth_roots + .add(cert) + .map_err(|e| make_err!(Code::Internal, "Could not read client CA: {e:?}"))?; } - keys.into_iter() - .next() - .unwrap() - .err_tip(|| format!("Could not extract key(s) from file {}", tls_config.key_file))? + let crls = if let Some(client_crl_file) = &tls_config.client_crl_file { + let mut crl_reader = std::io::BufReader::new( + std::fs::File::open(client_crl_file) + .err_tip(|| format!("Could not open CRL file {client_crl_file}"))?, + ); + extract_crls(&mut crl_reader) + .map(|crl| crl.map(CertificateRevocationListDer::from)) + .collect::>() + .err_tip(|| format!("Could not extract CRLs from file {client_crl_file}"))? + } else { + Vec::new() + }; + WebPkiClientVerifier::builder(Arc::new(client_auth_roots)) + .with_crls(crls) + .build() + .map_err(|e| make_err!(Code::Internal, "Could not create WebPkiClientVerifier: {e:?}"))? + } else { + WebPkiClientVerifier::no_client_auth() }; let mut config = TlsServerConfig::builder() - .with_no_client_auth() - .with_single_cert(certs, key.into()) - .map_err(|e| make_err!(Code::Internal, "Could not create TlsServerConfig : {:?}", e))?; + .with_client_cert_verifier(verifier) + .with_single_cert(certs, key) + .map_err(|e| make_err!(Code::Internal, "Could not create TlsServerConfig : {e:?}"))?; config.alpn_protocols.push("h2".into()); Ok(Some(TlsAcceptor::from(Arc::new(config))))