From 8062c00d72f1824d2b740de73b720458429d9a5f Mon Sep 17 00:00:00 2001 From: Chris Porter Date: Mon, 18 Nov 2024 09:44:11 -0500 Subject: [PATCH] draft support for encrypted mesh in guest components Signed-off-by: Chris Porter --- Cargo.lock | 97 ++++-- Cargo.toml | 1 + api-server-rest/openapi/api.json | 17 +- confidential-data-hub/hub/Cargo.toml | 5 + confidential-data-hub/hub/protos/api.proto | 15 +- confidential-data-hub/hub/src/api.rs | 7 + .../hub/src/bin/protos/api.rs | 288 +++++++++++++++++- .../hub/src/bin/protos/api_ttrpc.rs | 48 +++ .../hub/src/bin/ttrpc-cdh-tool.rs | 27 +- .../hub/src/bin/ttrpc-cdh.rs | 5 +- .../hub/src/bin/ttrpc_server/mod.rs | 31 +- confidential-data-hub/hub/src/error.rs | 3 + confidential-data-hub/hub/src/hub.rs | 12 + .../overlay-network/Cargo.toml | 32 ++ .../overlay-network/src/error.rs | 49 +++ .../overlay-network/src/lib.rs | 27 ++ .../src/overlay_network/config_templates.rs | 120 ++++++++ .../src/overlay_network/mod.rs | 6 + .../src/overlay_network/nebula.rs | 230 ++++++++++++++ 19 files changed, 970 insertions(+), 50 deletions(-) create mode 100644 confidential-data-hub/overlay-network/Cargo.toml create mode 100644 confidential-data-hub/overlay-network/src/error.rs create mode 100644 confidential-data-hub/overlay-network/src/lib.rs create mode 100644 confidential-data-hub/overlay-network/src/overlay_network/config_templates.rs create mode 100644 confidential-data-hub/overlay-network/src/overlay_network/mod.rs create mode 100644 confidential-data-hub/overlay-network/src/overlay_network/nebula.rs diff --git a/Cargo.lock b/Cargo.lock index c27f7167d..46a5f777d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1099,6 +1099,7 @@ dependencies = [ "lazy_static", "log", "nix 0.29.0", + "overlay_network", "prost 0.13.3", "protobuf 3.5.1", "rstest", @@ -2210,9 +2211,9 @@ checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" @@ -2326,7 +2327,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.2.3", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -2345,7 +2346,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.2.3", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -2827,9 +2828,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -2911,9 +2912,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" @@ -2948,12 +2949,12 @@ dependencies = [ [[package]] name = "josekit" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0953340cf63354cec4a385f1fbcb3f409a5823778cae236078892f6030ed4565" +checksum = "54b85e2125819afc4fd2ae57416207e792c7e12797858e5db2a6c6f24a166829" dependencies = [ "anyhow", - "base64 0.21.7", + "base64 0.22.1", "flate2", "once_cell", "openssl", @@ -3393,6 +3394,12 @@ dependencies = [ "redox_syscall 0.5.7", ] +[[package]] +name = "libyml" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64804cc6a5042d4f05379909ba25b503ec04e2c082151d62122d5dcaa274b961" + [[package]] name = "libz-sys" version = "1.1.20" @@ -3510,9 +3517,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" @@ -3672,6 +3679,7 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", + "memoffset 0.9.0", ] [[package]] @@ -4136,6 +4144,28 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "overlay_network" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "kms", + "log", + "nix 0.29.0", + "rand", + "rstest", + "secret", + "serde", + "serde_json", + "serde_yml", + "strum", + "tempfile", + "thiserror", + "tokio", +] + [[package]] name = "p12" version = "0.6.3" @@ -4334,7 +4364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.2.3", + "indexmap 2.5.0", ] [[package]] @@ -4735,7 +4765,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b0e9b447d099ae2c4993c0cbb03c7a9d6c937b17f2d56cfc0b1550e6fcfdb76" dependencies = [ "anyhow", - "indexmap 2.2.3", + "indexmap 2.5.0", "log", "protobuf 3.5.1", "protobuf-support", @@ -5289,9 +5319,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "ryu-js" @@ -5351,9 +5381,9 @@ dependencies = [ [[package]] name = "scc" -version = "2.1.6" +version = "2.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ccfb12511cdb770157ace92d7dda771e498445b78f9886e8cdbc5140a4eced" +checksum = "aeb7ac86243095b70a7920639507b71d51a63390d1ba26c4f60a552fbb914a37" dependencies = [ "sdd", ] @@ -5413,9 +5443,9 @@ dependencies = [ [[package]] name = "sdd" -version = "2.1.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177258b64c0faaa9ffd3c65cd3262c2bc7e2588dbbd9c1641d0346145c1bbda8" +checksum = "0495e4577c672de8254beb68d01a9b62d0e8a13c099edecdbedccce3223cd29f" [[package]] name = "sec1" @@ -5585,7 +5615,7 @@ version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.5.0", "itoa", "memchr", "ryu", @@ -5653,13 +5683,30 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.5.0", "itoa", "ryu", "serde", "unsafe-libyaml", ] +[[package]] +name = "serde_yml" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e76bab63c3fd98d27c17f9cbce177f64a91f5e69ac04cafe04e1bb25d1dc3c" +dependencies = [ + "indexmap 2.5.0", + "itoa", + "libyml", + "log", + "memchr", + "ryu", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "serial_test" version = "3.1.1" @@ -6477,7 +6524,7 @@ version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", @@ -6842,7 +6889,7 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514a48569e4e21c86d0b84b5612b5e73c0b2cf09db63260134ba426d4e8ea714" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.5.0", "serde", "serde_json", "utoipa-gen", diff --git a/Cargo.toml b/Cargo.toml index 6856b3458..a4c57db9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ rstest = "0.17" serde = { version = "1.0", features = ["derive"] } serde_with = { version = "1.11.0", features = ["base64"] } serde_json = "1.0" +serde_yml = "0.0.11" serial_test = "3" sha2 = "0.10.7" strum = { version = "0.26", features = ["derive"] } diff --git a/api-server-rest/openapi/api.json b/api-server-rest/openapi/api.json index dd0caf465..0d2f3c3f8 100644 --- a/api-server-rest/openapi/api.json +++ b/api-server-rest/openapi/api.json @@ -1,5 +1,5 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", "info": { "title": "CoCo Restful API", "description": "HTTP based API for CoCo containers to get resource/evidence/token from confidential-data-hub and attestation-agent.", @@ -20,9 +20,7 @@ "paths": { "/aa/evidence": { "get": { - "tags": [ - "crate" - ], + "tags": [], "operationId": "_evidence", "parameters": [ { @@ -67,9 +65,7 @@ }, "/aa/token": { "get": { - "tags": [ - "crate" - ], + "tags": [], "operationId": "_token", "parameters": [ { @@ -114,9 +110,7 @@ }, "/cdh/resource/{repository}/{type}/{tag}": { "get": { - "tags": [ - "crate" - ], + "tags": [], "operationId": "_resource", "responses": { "200": { @@ -144,5 +138,6 @@ } } } - } + }, + "components": {} } \ No newline at end of file diff --git a/confidential-data-hub/hub/Cargo.toml b/confidential-data-hub/hub/Cargo.toml index cf9afc49b..642f96cdf 100644 --- a/confidential-data-hub/hub/Cargo.toml +++ b/confidential-data-hub/hub/Cargo.toml @@ -40,10 +40,12 @@ image-rs = { path = "../../image-rs", default-features = false, features = ["kat kms = { path = "../kms", default-features = false } lazy_static.workspace = true log.workspace = true +nix = { workspace = true, features = ["net"] } prost = { workspace = true, optional = true } protobuf = { workspace = true, optional = true } secret.path = "../secret" storage.path = "../storage" +overlay_network.path = "../overlay-network" serde = { workspace = true, optional = true } serde_json.workspace = true thiserror.workspace = true @@ -81,3 +83,6 @@ ehsm = ["image/ehsm", "secret/ehsm"] bin = [ "anyhow", "attestation-agent", "cfg-if", "clap", "config", "env_logger", "serde" ] ttrpc = ["dep:ttrpc", "protobuf", "ttrpc-codegen", "tokio/signal"] grpc = ["prost", "tonic", "tonic-build", "tokio/signal"] + +# support overlay network +overlay-network = [] diff --git a/confidential-data-hub/hub/protos/api.proto b/confidential-data-hub/hub/protos/api.proto index 288b2ce32..4941dca5e 100644 --- a/confidential-data-hub/hub/protos/api.proto +++ b/confidential-data-hub/hub/protos/api.proto @@ -42,6 +42,15 @@ message ImagePullResponse { string manifest_digest = 1; } +message InitOverlayNetworkRequest { + string pod_name = 1; + string lighthouse_pub_ip = 2; +} + +message InitOverlayNetworkResponse { + int32 return_code = 1; +} + service SealedSecretService { rpc UnsealSecret(UnsealSecretInput) returns (UnsealSecretOutput) {}; } @@ -56,4 +65,8 @@ service SecureMountService { service ImagePullService { rpc PullImage(ImagePullRequest) returns (ImagePullResponse) {}; -} \ No newline at end of file +} + +service OverlayNetworkService { + rpc InitOverlayNetwork(InitOverlayNetworkRequest) returns (InitOverlayNetworkResponse) {}; +} diff --git a/confidential-data-hub/hub/src/api.rs b/confidential-data-hub/hub/src/api.rs index 37f941443..f1a611ca4 100644 --- a/confidential-data-hub/hub/src/api.rs +++ b/confidential-data-hub/hub/src/api.rs @@ -32,4 +32,11 @@ pub trait DataHub { /// Pull image of image url (reference), and place the merged layers in the `bundle_path/rootfs` async fn pull_image(&self, _image_url: &str, _bundle_path: &str) -> Result; + + /// Initialize the overlay network + async fn init_overlay_network( + &self, + pod_name: String, + lighthouse_pub_ip: String, + ) -> Result>; } diff --git a/confidential-data-hub/hub/src/bin/protos/api.rs b/confidential-data-hub/hub/src/bin/protos/api.rs index ec5b63ecc..d22464adf 100644 --- a/confidential-data-hub/hub/src/bin/protos/api.rs +++ b/confidential-data-hub/hub/src/bin/protos/api.rs @@ -1087,6 +1087,268 @@ impl ::protobuf::reflect::ProtobufValue for ImagePullResponse { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +// @@protoc_insertion_point(message:api.InitOverlayNetworkRequest) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct InitOverlayNetworkRequest { + // message fields + // @@protoc_insertion_point(field:api.InitOverlayNetworkRequest.pod_name) + pub pod_name: ::std::string::String, + // @@protoc_insertion_point(field:api.InitOverlayNetworkRequest.lighthouse_pub_ip) + pub lighthouse_pub_ip: ::std::string::String, + // special fields + // @@protoc_insertion_point(special_field:api.InitOverlayNetworkRequest.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a InitOverlayNetworkRequest { + fn default() -> &'a InitOverlayNetworkRequest { + ::default_instance() + } +} + +impl InitOverlayNetworkRequest { + pub fn new() -> InitOverlayNetworkRequest { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(2); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "pod_name", + |m: &InitOverlayNetworkRequest| { &m.pod_name }, + |m: &mut InitOverlayNetworkRequest| { &mut m.pod_name }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "lighthouse_pub_ip", + |m: &InitOverlayNetworkRequest| { &m.lighthouse_pub_ip }, + |m: &mut InitOverlayNetworkRequest| { &mut m.lighthouse_pub_ip }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "InitOverlayNetworkRequest", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for InitOverlayNetworkRequest { + const NAME: &'static str = "InitOverlayNetworkRequest"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.pod_name = is.read_string()?; + }, + 18 => { + self.lighthouse_pub_ip = is.read_string()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.pod_name.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.pod_name); + } + if !self.lighthouse_pub_ip.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.lighthouse_pub_ip); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.pod_name.is_empty() { + os.write_string(1, &self.pod_name)?; + } + if !self.lighthouse_pub_ip.is_empty() { + os.write_string(2, &self.lighthouse_pub_ip)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> InitOverlayNetworkRequest { + InitOverlayNetworkRequest::new() + } + + fn clear(&mut self) { + self.pod_name.clear(); + self.lighthouse_pub_ip.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static InitOverlayNetworkRequest { + static instance: InitOverlayNetworkRequest = InitOverlayNetworkRequest { + pod_name: ::std::string::String::new(), + lighthouse_pub_ip: ::std::string::String::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for InitOverlayNetworkRequest { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("InitOverlayNetworkRequest").unwrap()).clone() + } +} + +impl ::std::fmt::Display for InitOverlayNetworkRequest { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for InitOverlayNetworkRequest { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:api.InitOverlayNetworkResponse) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct InitOverlayNetworkResponse { + // message fields + // @@protoc_insertion_point(field:api.InitOverlayNetworkResponse.return_code) + pub return_code: i32, + // special fields + // @@protoc_insertion_point(special_field:api.InitOverlayNetworkResponse.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a InitOverlayNetworkResponse { + fn default() -> &'a InitOverlayNetworkResponse { + ::default_instance() + } +} + +impl InitOverlayNetworkResponse { + pub fn new() -> InitOverlayNetworkResponse { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "return_code", + |m: &InitOverlayNetworkResponse| { &m.return_code }, + |m: &mut InitOverlayNetworkResponse| { &mut m.return_code }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "InitOverlayNetworkResponse", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for InitOverlayNetworkResponse { + const NAME: &'static str = "InitOverlayNetworkResponse"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 8 => { + self.return_code = is.read_int32()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if self.return_code != 0 { + my_size += ::protobuf::rt::int32_size(1, self.return_code); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if self.return_code != 0 { + os.write_int32(1, self.return_code)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> InitOverlayNetworkResponse { + InitOverlayNetworkResponse::new() + } + + fn clear(&mut self) { + self.return_code = 0; + self.special_fields.clear(); + } + + fn default_instance() -> &'static InitOverlayNetworkResponse { + static instance: InitOverlayNetworkResponse = InitOverlayNetworkResponse { + return_code: 0, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for InitOverlayNetworkResponse { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("InitOverlayNetworkResponse").unwrap()).clone() + } +} + +impl ::std::fmt::Display for InitOverlayNetworkResponse { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for InitOverlayNetworkResponse { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + static file_descriptor_proto_data: &'static [u8] = b"\ \n\tapi.proto\x12\x03api\"+\n\x11UnsealSecretInput\x12\x16\n\x06secret\ \x18\x01\x20\x01(\x0cR\x06secret\"2\n\x12UnsealSecretOutput\x12\x1c\n\tp\ @@ -1103,14 +1365,20 @@ static file_descriptor_proto_data: &'static [u8] = b"\ ImagePullRequest\x12\x1b\n\timage_url\x18\x01\x20\x01(\tR\x08imageUrl\ \x12\x1f\n\x0bbundle_path\x18\x02\x20\x01(\tR\nbundlePath\"<\n\x11ImageP\ ullResponse\x12'\n\x0fmanifest_digest\x18\x01\x20\x01(\tR\x0emanifestDig\ - est2V\n\x13SealedSecretService\x12?\n\x0cUnsealSecret\x12\x16.api.Unseal\ - SecretInput\x1a\x17.api.UnsealSecretOutput2V\n\x12GetResourceService\x12\ - @\n\x0bGetResource\x12\x17.api.GetResourceRequest\x1a\x18.api.GetResourc\ - eResponse2V\n\x12SecureMountService\x12@\n\x0bSecureMount\x12\x17.api.Se\ - cureMountRequest\x1a\x18.api.SecureMountResponse2N\n\x10ImagePullService\ - \x12:\n\tPullImage\x12\x15.api.ImagePullRequest\x1a\x16.api.ImagePullRes\ - ponseBaZ_github.com/confidential-containers/guest-components/confidentia\ - l-data-hub/golang/pkg/api/cdhapib\x06proto3\ + est\"b\n\x19InitOverlayNetworkRequest\x12\x19\n\x08pod_name\x18\x01\x20\ + \x01(\tR\x07podName\x12*\n\x11lighthouse_pub_ip\x18\x02\x20\x01(\tR\x0fl\ + ighthousePubIp\"=\n\x1aInitOverlayNetworkResponse\x12\x1f\n\x0breturn_co\ + de\x18\x01\x20\x01(\x05R\nreturnCode2V\n\x13SealedSecretService\x12?\n\ + \x0cUnsealSecret\x12\x16.api.UnsealSecretInput\x1a\x17.api.UnsealSecretO\ + utput2V\n\x12GetResourceService\x12@\n\x0bGetResource\x12\x17.api.GetRes\ + ourceRequest\x1a\x18.api.GetResourceResponse2V\n\x12SecureMountService\ + \x12@\n\x0bSecureMount\x12\x17.api.SecureMountRequest\x1a\x18.api.Secure\ + MountResponse2N\n\x10ImagePullService\x12:\n\tPullImage\x12\x15.api.Imag\ + ePullRequest\x1a\x16.api.ImagePullResponse2n\n\x15OverlayNetworkService\ + \x12U\n\x12InitOverlayNetwork\x12\x1e.api.InitOverlayNetworkRequest\x1a\ + \x1f.api.InitOverlayNetworkResponseBaZ_github.com/confidential-container\ + s/guest-components/confidential-data-hub/golang/pkg/api/cdhapib\x06proto\ + 3\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -1128,7 +1396,7 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { file_descriptor.get(|| { let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { let mut deps = ::std::vec::Vec::with_capacity(0); - let mut messages = ::std::vec::Vec::with_capacity(8); + let mut messages = ::std::vec::Vec::with_capacity(10); messages.push(UnsealSecretInput::generated_message_descriptor_data()); messages.push(UnsealSecretOutput::generated_message_descriptor_data()); messages.push(GetResourceRequest::generated_message_descriptor_data()); @@ -1137,6 +1405,8 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { messages.push(SecureMountResponse::generated_message_descriptor_data()); messages.push(ImagePullRequest::generated_message_descriptor_data()); messages.push(ImagePullResponse::generated_message_descriptor_data()); + messages.push(InitOverlayNetworkRequest::generated_message_descriptor_data()); + messages.push(InitOverlayNetworkResponse::generated_message_descriptor_data()); let mut enums = ::std::vec::Vec::with_capacity(0); ::protobuf::reflect::GeneratedFileDescriptor::new_generated( file_descriptor_proto(), diff --git a/confidential-data-hub/hub/src/bin/protos/api_ttrpc.rs b/confidential-data-hub/hub/src/bin/protos/api_ttrpc.rs index 35d2c704d..bf2386e11 100644 --- a/confidential-data-hub/hub/src/bin/protos/api_ttrpc.rs +++ b/confidential-data-hub/hub/src/bin/protos/api_ttrpc.rs @@ -210,3 +210,51 @@ pub fn create_image_pull_service(service: Arc Self { + OverlayNetworkServiceClient { + client, + } + } + + pub async fn init_overlay_network(&self, ctx: ttrpc::context::Context, req: &super::api::InitOverlayNetworkRequest) -> ::ttrpc::Result { + let mut cres = super::api::InitOverlayNetworkResponse::new(); + ::ttrpc::async_client_request!(self, ctx, req, "api.OverlayNetworkService", "InitOverlayNetwork", cres); + } +} + +struct InitOverlayNetworkMethod { + service: Arc, +} + +#[async_trait] +impl ::ttrpc::r#async::MethodHandler for InitOverlayNetworkMethod { + async fn handler(&self, ctx: ::ttrpc::r#async::TtrpcContext, req: ::ttrpc::Request) -> ::ttrpc::Result<::ttrpc::Response> { + ::ttrpc::async_request_handler!(self, ctx, req, api, InitOverlayNetworkRequest, init_overlay_network); + } +} + +#[async_trait] +pub trait OverlayNetworkService: Sync { + async fn init_overlay_network(&self, _ctx: &::ttrpc::r#async::TtrpcContext, _: super::api::InitOverlayNetworkRequest) -> ::ttrpc::Result { + Err(::ttrpc::Error::RpcStatus(::ttrpc::get_status(::ttrpc::Code::NOT_FOUND, "/api.OverlayNetworkService/InitOverlayNetwork is not supported".to_string()))) + } +} + +pub fn create_overlay_network_service(service: Arc) -> HashMap { + let mut ret = HashMap::new(); + let mut methods = HashMap::new(); + let streams = HashMap::new(); + + methods.insert("InitOverlayNetwork".to_string(), + Box::new(InitOverlayNetworkMethod{service: service.clone()}) as Box); + + ret.insert("api.OverlayNetworkService".to_string(), ::ttrpc::r#async::Service{ methods, streams }); + ret +} diff --git a/confidential-data-hub/hub/src/bin/ttrpc-cdh-tool.rs b/confidential-data-hub/hub/src/bin/ttrpc-cdh-tool.rs index 6a7a67103..a76769281 100644 --- a/confidential-data-hub/hub/src/bin/ttrpc-cdh-tool.rs +++ b/confidential-data-hub/hub/src/bin/ttrpc-cdh-tool.rs @@ -12,7 +12,7 @@ use clap::{Args, Parser, Subcommand}; use protos::{ api::*, api_ttrpc::{ - GetResourceServiceClient, ImagePullServiceClient, SealedSecretServiceClient, + OverlayNetworkServiceClient, GetResourceServiceClient, ImagePullServiceClient, SealedSecretServiceClient, SecureMountServiceClient, }, keyprovider::*, @@ -59,6 +59,9 @@ enum Operation { /// Pull image PullImage(PullImageArgs), + + /// Initialize up an overlay network + InitOverlayNetwork(InitOverlayNetworkArgs), } #[derive(Args)] @@ -105,6 +108,15 @@ struct PullImageArgs { bundle_path: String, } +#[derive(Debug, Args)] +#[command(author, version, about, long_about = None)] +struct InitOverlayNetworkArgs { + #[arg(short, long)] + pod_name: String, + #[arg(short, long)] + lighthouse_pub_ip: String, +} + #[tokio::main] async fn main() { let args = Cli::parse(); @@ -185,5 +197,18 @@ async fn main() { .expect("request to CDH"); println!("Image pulled: {manifest_digest}") } + Operation::InitOverlayNetwork(arg) => { + let client = OverlayNetworkServiceClient::new(inner); + let req = InitOverlayNetworkRequest { + pod_name: arg.pod_name, + lighthouse_pub_ip: arg.lighthouse_pub_ip, + ..Default::default() + }; + let res = client + .init_overlay_network(context::with_timeout(args.timeout * NANO_PER_SECOND), &req) + .await + .expect("request to CDH"); + println!("{res}"); + } } } diff --git a/confidential-data-hub/hub/src/bin/ttrpc-cdh.rs b/confidential-data-hub/hub/src/bin/ttrpc-cdh.rs index 869619ee3..f85fc13a1 100644 --- a/confidential-data-hub/hub/src/bin/ttrpc-cdh.rs +++ b/confidential-data-hub/hub/src/bin/ttrpc-cdh.rs @@ -11,7 +11,7 @@ use confidential_data_hub::CdhConfig; use log::info; use protos::{ api_ttrpc::{ - create_get_resource_service, create_image_pull_service, create_sealed_secret_service, + create_overlay_network_service, create_get_resource_service, create_image_pull_service, create_sealed_secret_service, create_secure_mount_service, }, keyprovider_ttrpc::create_key_provider_service, @@ -66,7 +66,8 @@ async fn main() -> Result<()> { .register_service(create_get_resource_service(server.clone() as _)) .register_service(create_key_provider_service(server.clone() as _)) .register_service(create_secure_mount_service(server.clone() as _)) - .register_service(create_image_pull_service(server.clone() as _)); + .register_service(create_image_pull_service(server.clone() as _)) + .register_service(create_overlay_network_service(server.clone() as _)); info!( "[ttRPC] Confidential Data Hub starts to listen to request: {}", diff --git a/confidential-data-hub/hub/src/bin/ttrpc_server/mod.rs b/confidential-data-hub/hub/src/bin/ttrpc_server/mod.rs index 448c49323..e25ee0cb2 100644 --- a/confidential-data-hub/hub/src/bin/ttrpc_server/mod.rs +++ b/confidential-data-hub/hub/src/bin/ttrpc_server/mod.rs @@ -18,10 +18,13 @@ use crate::{ protos::{ api::{ GetResourceRequest, GetResourceResponse, ImagePullRequest, ImagePullResponse, + InitOverlayNetworkRequest, InitOverlayNetworkResponse, SecureMountRequest, SecureMountResponse, UnsealSecretInput, UnsealSecretOutput, + }, api_ttrpc::{ - GetResourceService, ImagePullService, SealedSecretService, SecureMountService, + OverlayNetworkService, GetResourceService, ImagePullService, + SealedSecretService, SecureMountService, }, keyprovider::{KeyProviderKeyWrapProtocolInput, KeyProviderKeyWrapProtocolOutput}, keyprovider_ttrpc::KeyProviderService, @@ -203,3 +206,29 @@ impl ImagePullService for Server { Ok(reply) } } + +#[async_trait] +impl OverlayNetworkService for Server { + async fn init_overlay_network( + &self, + _ctx: &TtrpcContext, + req: InitOverlayNetworkRequest, + ) -> ::ttrpc::Result { + debug!("[ttRPC CDH] Initialize overlay network request"); + let _resource = self.hub + .init_overlay_network(req.pod_name, req.lighthouse_pub_ip) + .await + .map_err(|e| { + let detailed_error = format_error!(e); + error!("[ttRPC CDH] Initialize Overlay Network:\n{detailed_error}"); + let mut status = Status::new(); + status.set_code(Code::INTERNAL); + status.set_message("[CDH] [ERROR]: initialize overlay network failed".to_string()); + Error::RpcStatus(status) + })?; + + let reply = InitOverlayNetworkResponse::new(); + debug!("[ttRPC CDH] initialize overlay network succeeded."); + Ok(reply) + } +} diff --git a/confidential-data-hub/hub/src/error.rs b/confidential-data-hub/hub/src/error.rs index e53a03181..468aad130 100644 --- a/confidential-data-hub/hub/src/error.rs +++ b/confidential-data-hub/hub/src/error.rs @@ -38,4 +38,7 @@ pub enum Error { #[source] source: anyhow::Error, }, + + #[error("initialize overlay network failed")] + OverlayNetworkInit(#[from] overlay_network::OverlayNetworkError), } diff --git a/confidential-data-hub/hub/src/hub.rs b/confidential-data-hub/hub/src/hub.rs index 19c483435..65eaed4de 100644 --- a/confidential-data-hub/hub/src/hub.rs +++ b/confidential-data-hub/hub/src/hub.rs @@ -93,6 +93,18 @@ impl DataHub for Hub { .map_err(|e| Error::ImagePull { source: e })?; Ok(manifest_digest) } + + async fn init_overlay_network( + &self, + pod_name: String, + lighthouse_pub_ip: String, + ) -> Result> { + info!("init overlay network called"); + overlay_network::init(pod_name, lighthouse_pub_ip).await?; + // FIXME remove return value for this interface if we don't need + // anything here. + Ok(Vec::::new()) + } } async fn initialize_image_client(config: ImageConfig) -> Result> { diff --git a/confidential-data-hub/overlay-network/Cargo.toml b/confidential-data-hub/overlay-network/Cargo.toml new file mode 100644 index 000000000..b71f73b07 --- /dev/null +++ b/confidential-data-hub/overlay-network/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "overlay_network" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +base64.workspace = true +log.workspace = true +rand = { workspace = true, optional = true } +secret = { path = "../secret" } +serde.workspace = true +serde_json.workspace = true +strum = { workspace = true, features = ["derive"] } +tempfile = { workspace = true, optional = true } +thiserror.workspace = true +tokio = { workspace = true, optional = true } +nix = { workspace = true, features = ["net"] } +kms = { path = "../kms", default-features = false } +serde_yml.workspace = true + +[dev-dependencies] +rstest.workspace = true +tokio = { workspace = true, features = ["rt", "macros" ] } + +[build-dependencies] +anyhow.workspace = true + +[features] +default = [] +overlay-network = [] diff --git a/confidential-data-hub/overlay-network/src/error.rs b/confidential-data-hub/overlay-network/src/error.rs new file mode 100644 index 000000000..af8f39c01 --- /dev/null +++ b/confidential-data-hub/overlay-network/src/error.rs @@ -0,0 +1,49 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// + +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum OverlayNetworkError { + #[error("Overlay network init failed: {0}")] + Init(String), + + #[error("Overlay network feature not enabled")] + NotEnabled(), + + #[error( + "Mesh netmask (user-configured) and worker netmask (assigned by \ + the control plane) should match: {0}" + )] + NetmaskMismatch(String), + + #[error("Unable to get iface details: {0}")] + IfaceDetails(String), + + #[error("KBS client initialization failed")] + KbsClient { + #[source] + source: kms::Error, + }, + + #[error("Get secret failed")] + GetSecret { + #[source] + source: kms::Error, + }, + + #[error("Failed to parse KBS response")] + ResponseParse(#[from] serde_json::Error), + + #[error("Error while handling yaml data")] + SerdeYmlFail(#[from] serde_yml::Error), + + #[error("I/O error")] + IoError(#[from] std::io::Error), + + #[error("Error parsing Ipv4Addr")] + AddrParseFail(#[from] std::net::AddrParseError), +} diff --git a/confidential-data-hub/overlay-network/src/lib.rs b/confidential-data-hub/overlay-network/src/lib.rs new file mode 100644 index 000000000..4152e7a46 --- /dev/null +++ b/confidential-data-hub/overlay-network/src/lib.rs @@ -0,0 +1,27 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// + +pub mod overlay_network; +pub mod error; +#[cfg(feature = "overlay-network")] +use crate::overlay_network::nebula::NebulaMesh; +pub use error::*; +use log::info; + +pub async fn init(_pod_name: String, _lighthouse_pub_ip: String) -> Result<()> { + #[cfg(feature = "overlay-network")] + { + let nm: NebulaMesh = NebulaMesh { + pod_name: pod_name, + lighthouse_ip: lighthouse_pub_ip, + }; + nm.init().await?; + Ok(()) + } + #[cfg(not(feature = "overlay-network"))] + { + info!("overlay network not supported "); + Err(OverlayNetworkError::NotEnabled()) + } +} diff --git a/confidential-data-hub/overlay-network/src/overlay_network/config_templates.rs b/confidential-data-hub/overlay-network/src/overlay_network/config_templates.rs new file mode 100644 index 000000000..e6af6fb62 --- /dev/null +++ b/confidential-data-hub/overlay-network/src/overlay_network/config_templates.rs @@ -0,0 +1,120 @@ +pub const WORKER_CONFIG_TEMPLATE: &str = +"pki: + ca: /tmp/nebula/ca.crt + cert: /tmp/nebula/pod.crt + key: /tmp/nebula/pod.key + +static_host_map: + _STATIC_HOST_MAP +lighthouse: + am_lighthouse: false + interval: 60 + serve_dns: false + hosts: + - _LIGHTHOUSE_HOST +listen: + host: 0.0.0.0 + port: 4242 + batch: 256 + read_buffer: 419430400 + write_buffer: 419430400 + +routines: 1 + +punchy: + punch: true + +relay: + am_relay: false + use_relays: true + +tun: + disabled: false + dev: nebula1 + drop_local_broadcast: false + drop_multicast: false + tx_queue: 500 + mtu: 1450 + + routes: + unsafe_routes: + +logging: + level: info + format: text + +firewall: + outbound_action: drop + inbound_action: drop + + conntrack: + tcp_timeout: 12m + udp_timeout: 3m + default_timeout: 10m + + outbound: + - port: any + proto: any + host: any + + inbound: + - port: any + proto: any + host: any"; + +pub const LIGHTHOUSE_CONFIG_TEMPLATE: &str = +"pki: + ca: /tmp/nebula/ca.crt + cert: /tmp/nebula/pod.crt + key: /tmp/nebula/pod.key +static_host_map: + \"192.168.100.100\": [\"nebula-lighthouse:4242\"] +lighthouse: + am_lighthouse: true + serve_dns: true + dns: + host: '[::]' + port: 53 + interval: 60 +listen: + host: 0.0.0.0 + port: 4242 + batch: 256 + read_buffer: 419430400 + write_buffer: 419430400 +routines: 8 +punchy: + punch: true +relay: + am_relay: false + use_relays: true +tun: + disabled: false + dev: nebula1 + drop_local_broadcast: false + drop_multicast: false + tx_queue: 30000 + mtu: 1300 + routes: + unsafe_routes: +logging: + level: info + format: text +firewall: + outbound_action: drop + inbound_action: drop + conntrack: + tcp_timeout: 12m + udp_timeout: 3m + default_timeout: 10m + outbound: + - port: any + proto: any + host: any + inbound: + - port: any + proto: any + host: any + - port: 53 + proto: udp + host: any"; diff --git a/confidential-data-hub/overlay-network/src/overlay_network/mod.rs b/confidential-data-hub/overlay-network/src/overlay_network/mod.rs new file mode 100644 index 000000000..8ea837f71 --- /dev/null +++ b/confidential-data-hub/overlay-network/src/overlay_network/mod.rs @@ -0,0 +1,6 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// + +pub mod nebula; +pub mod config_templates; diff --git a/confidential-data-hub/overlay-network/src/overlay_network/nebula.rs b/confidential-data-hub/overlay-network/src/overlay_network/nebula.rs new file mode 100644 index 000000000..5acffc770 --- /dev/null +++ b/confidential-data-hub/overlay-network/src/overlay_network/nebula.rs @@ -0,0 +1,230 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// + +use crate::{OverlayNetworkError, Result}; +use crate::overlay_network::config_templates::{WORKER_CONFIG_TEMPLATE, LIGHTHOUSE_CONFIG_TEMPLATE}; +use kms::{Annotations, ProviderSettings}; +use nix::ifaddrs::getifaddrs; +use nix::sys::socket::{AddressFamily, SockaddrLike}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use serde_yml; +use std::fs; +use std::net::Ipv4Addr; +use std::process::Command; + +pub struct NebulaMesh { + pod_name: String, + lighthouse_ip: String, +} + +// FIXME This should be a shared struct, if possible, with trustee's nebula +// plugin. It's that plugin's custom protocol. +#[derive(Debug, Serialize, Deserialize)] +pub struct NebulaPluginResponse { + pub node_crt: Vec, + pub node_key: Vec, + pub ca_crt: Vec, +} + +const NEBULA_BIN: &str = "/opt/overlay-network/nebula"; + +const CA_CERT_PATH: &str = "/tmp/nebula/ca.crt"; +const POD_CERT_PATH: &str = "/tmp/nebula/pod.crt"; +const POD_KEY_PATH: &str = "/tmp/nebula/pod.key"; +const LIGHTHOUSE_CONFIG_PATH: &str = "/tmp/nebula/lighthouse-config.yaml"; +const WORKER_CONFIG_PATH: &str = "/tmp/nebula/config.yaml"; + +// FIXME These should be configurable +const LIGHTHOUSE_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 100, 100); +const LIGHTHOUSE_MASK: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0); + +impl NebulaMesh { + /// Initialize a nebula mesh. The general approach is as follows: + /// - Calculate what the mesh IP will be for this worker. + /// - Ask trustee for its nebula credentials + /// - Start the nebula daemon. + pub async fn init(&self) -> Result<()> { + let is_lighthouse: bool = self.lighthouse_ip.is_empty(); + + let (mesh_ip, which_config); + if is_lighthouse { + mesh_ip = LIGHTHOUSE_IP; + which_config = LIGHTHOUSE_CONFIG_PATH; + } else { + mesh_ip = self.generate_mesh_ip()?; + which_config = WORKER_CONFIG_PATH; + } + + // FIXME: kbs hard-coded to localhost is wrong. This should be based on + // ResourceUri? Where does it come from? + let prefix_len: u32 = self.netmask_to_prefix_len(LIGHTHOUSE_MASK); + let neb_cred_uri: String = format!( + "kbs://127.0.0.1:8080/plugin/\ + nebula/credential\ + ?ip[ip]={}\ + &ip[netbits]={}\ + &name={}", + mesh_ip, prefix_len, self.pod_name + ) + .to_owned(); + + let client = kms::new_getter("kbs", ProviderSettings::default()) + .await + .map_err(|e| OverlayNetworkError::KbsClient { source: e })?; + let response = client + .get_secret(&neb_cred_uri, &Annotations::default()) + .await + .map_err(|e| OverlayNetworkError::GetSecret { source: e })?; + let response: NebulaPluginResponse = serde_json::from_slice(&response)?; + + fs::create_dir("/tmp/nebula")?; + fs::write(CA_CERT_PATH, response.ca_crt)?; + fs::write(POD_CERT_PATH, response.node_crt)?; + fs::write(POD_KEY_PATH, response.node_key)?; + + if is_lighthouse { + let rule_file = serde_yml::from_str::(&LIGHTHOUSE_CONFIG_TEMPLATE)?; + let fp = fs::File::create(which_config).expect("error creating lighthouse config file"); + serde_yml::to_writer(fp, &rule_file)?; + } else { + let mut rule_file = serde_yml::from_str::(&WORKER_CONFIG_TEMPLATE)?; + // FIXME ? should the 4242 port be hard-coded + let static_host_map_str = + format!("\"{}\": [\"{}:4243\"]", LIGHTHOUSE_IP, self.lighthouse_ip); + let static_host_map = serde_yml::from_str::(&static_host_map_str)?; + + // FIXME these are Option<>s + *rule_file.get_mut("static_host_map").unwrap() = static_host_map; + rule_file + .get_mut("lighthouse") + .unwrap() + .get_mut("hosts") + .unwrap()[0] = format!("{}", LIGHTHOUSE_IP).into(); + + let fp = fs::File::create(which_config).expect("error creating worker config file"); + serde_yml::to_writer(fp, &rule_file)?; + } + + Command::new(NEBULA_BIN) + .arg("-config") + .arg(which_config) + .spawn() + .expect("nebula command failed to start"); + + Ok(()) + } + + /// Read /proc/net/route to get the default gateway's interface, + /// e.g. "eth0". + /// TODO This is brittle. Is there a reason to not use procfs::net here? + fn get_iface_of_default_gateway(&self) -> Result { + let binding = fs::read_to_string("/proc/net/route")?; + let s: Vec<&str> = binding.split('\n').collect(); + let mut iface: &str = ""; + for part in s { + let tokens: Vec<&str> = part.split_whitespace().collect(); + iface = tokens[0]; + let destination: &str = tokens[1]; + // FIXME ? Should mask also be checked? + let _mask: &str = tokens[7]; + if destination == "00000000" { + break; + } + } + Ok(iface.to_string()) + } + + /// Get the IP address and netmask for some iface. This relies on the nix + /// library's getifaddrs support. + fn get_ip_and_mask(&self, iface: &String) -> Result<(Ipv4Addr, Ipv4Addr)> { + let addrs = getifaddrs() + .map_err(|e| OverlayNetworkError::IfaceDetails(format!("getifaddrs returned error: {}", e)))?; + for ifaddr in addrs { + if ifaddr.interface_name == *iface { + let Some(address) = ifaddr.address else { + continue; + }; + let Some(netmask) = ifaddr.netmask else { + continue; + }; + if let Some(AddressFamily::Inet) = address.family() { + // ipv4 + println!( + "interface {} address {} netmask {}", + ifaddr.interface_name, address, netmask + ); + let Some(address) = address.as_sockaddr_in() else { + continue; + }; + let Some(netmask) = netmask.as_sockaddr_in() else { + continue; + }; + return Ok((address.ip(), netmask.ip())); + } + } + } + Err(OverlayNetworkError::IfaceDetails(format!( + "Unable to find address and mask for {}", + iface + ))) + } + + /// Convert a netmask to its prefix length (e.g. convert 255.255.255.0 to 24) + fn netmask_to_prefix_len(&self, netmask: Ipv4Addr) -> u32 { + netmask + .octets() + .iter() + .fold(0, |count, oct| count + oct.count_ones()) + } + + /// Form the IP address that this worker will have in the mesh. This is done + /// by combining the upper bits of the (known, predetermined) lighthouse IP + /// with the lower bits of the (dynamic) k8s-assigned IP of the worker's + /// default interface. + /// TODO It is an error for the k8s netmask to be smaller than the mesh (i.e. + /// for the addressable range to be larger than the mesh's). This could + /// cause IP assignment collisions for the mesh, even when using a benign + /// k8s. + /// TODO Similar: Need to handle collisions in the case of a malicious k8s. + /// Or, at least, document behavior (e.g. DoS). + /// TODO Do not collide on the lighthouse IP address. + fn generate_mesh_ip(&self) -> Result { + let iface: String = self.get_iface_of_default_gateway()?; + let (iface_ip, iface_mask) = self.get_ip_and_mask(&iface)?; + if iface_mask != LIGHTHOUSE_MASK { + return Err(OverlayNetworkError::NetmaskMismatch(format!( + "worker netmask ({}) and lighthouse netmask ({}) do not match", + iface_mask, LIGHTHOUSE_MASK + ))); + } + let mesh_ip = (LIGHTHOUSE_IP & LIGHTHOUSE_MASK) | (iface_ip & !LIGHTHOUSE_MASK); + Ok(mesh_ip) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + + #[rstest] + #[case(255, 255, 255, 255, 32)] + #[case(255, 255, 255, 0, 24)] + #[case(0, 0, 0, 0, 0)] + #[case(255, 255, 254, 0, 23)] + #[case(255, 255, 255, 8, 25)] + fn test_netmask_to_prefix_len( + #[case] a: u8, + #[case] b: u8, + #[case] c: u8, + #[case] d: u8, + #[case] prefix_len: u32, + ) { + let ip: Ipv4Addr = Ipv4Addr::new(a, b, c, d); + let nm: NebulaMesh = NebulaMesh {pod_name: "".to_string(), lighthouse_ip: "".to_string()}; + let rv: u32 = nm.netmask_to_prefix_len(ip); + assert_eq!(rv, prefix_len); + } +}