From dab1f866bd7324ba90366b5b9e1f472301083961 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Sun, 8 Sep 2024 13:08:43 -0700 Subject: [PATCH] feat(mTLS): adds mTLS support to dataplane api-server --- Cargo.lock | 1 + dataplane/api-server/Cargo.toml | 1 + dataplane/api-server/src/config.rs | 61 ++++++++++++++++ dataplane/api-server/src/lib.rs | 53 ++++++++++++-- dataplane/loader/src/main.rs | 110 +++++++++++++++-------------- 5 files changed, 169 insertions(+), 57 deletions(-) create mode 100644 dataplane/api-server/src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 2040214c..9c014031 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,6 +104,7 @@ version = "0.3.0" dependencies = [ "anyhow", "aya", + "clap", "common", "libc", "log", diff --git a/dataplane/api-server/Cargo.toml b/dataplane/api-server/Cargo.toml index 3447eeb2..c40daf54 100644 --- a/dataplane/api-server/Cargo.toml +++ b/dataplane/api-server/Cargo.toml @@ -8,6 +8,7 @@ version.workspace = true [dependencies] anyhow = { workspace = true } aya = { workspace = true, features = ["async_tokio"] } +clap = { workspace = true } common = { workspace = true, features = ["user"] } libc = { workspace = true } log = { workspace = true } diff --git a/dataplane/api-server/src/config.rs b/dataplane/api-server/src/config.rs new file mode 100644 index 00000000..61ca44dc --- /dev/null +++ b/dataplane/api-server/src/config.rs @@ -0,0 +1,61 @@ +/* +Copyright 2023 The Kubernetes Authors. + +SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +*/ +use clap::Parser; +use std::path::PathBuf; + +#[derive(Debug, Parser, Clone)] +pub struct GrpcConfig { + #[clap(long, default_value = "false")] + pub enable_tls: bool, + #[clap(long, default_value = "false")] + pub enable_mtls: bool, + pub certificate_authority_root_path: Option, + pub server_certificate_path: Option, + pub server_private_key_path: Option, + pub client_certificate_authority_root_path: Option, + pub client_certificate_path: Option, + pub client_private_key_path: Option, +} + +impl GrpcConfig { + pub fn validate(&self) -> Result<(), String> { + fn validate_paths(paths: &[(&Option, &str)]) -> Result<(), String> { + for (path, name) in paths { + if path.is_none() { + return Err(format!("Missing required path: {}", name)); + } + } + Ok(()) + } + + let tls_paths = &[ + ( + &self.certificate_authority_root_path, + "certificate_authority_root_path", + ), + (&self.server_certificate_path, "server_certificate_path"), + (&self.server_private_key_path, "server_private_key_path"), + ]; + + let mtls_paths = &[ + ( + &self.client_certificate_authority_root_path, + "client_certificate_authority_root_path", + ), + (&self.client_certificate_path, "client_certificate_path"), + (&self.client_private_key_path, "client_private_key_path"), + ]; + + if self.enable_mtls { + validate_paths(tls_paths)?; + validate_paths(mtls_paths)?; + } else if self.enable_tls { + validate_paths(tls_paths)?; + } + + Ok(()) + } +} diff --git a/dataplane/api-server/src/lib.rs b/dataplane/api-server/src/lib.rs index 2fd7dfd1..575286aa 100644 --- a/dataplane/api-server/src/lib.rs +++ b/dataplane/api-server/src/lib.rs @@ -5,17 +5,23 @@ SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ pub mod backends; +pub mod config; pub mod netutils; pub mod server; -use std::net::{Ipv4Addr, SocketAddrV4}; +use std::{ + fs, + net::{Ipv4Addr, SocketAddrV4}, +}; use anyhow::Error; use aya::maps::{HashMap, MapData}; -use tonic::transport::Server; +use log::info; +use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig}; use backends::backends_server::BackendsServer; use common::{BackendKey, BackendList, ClientKey, LoadBalancerMapping}; +use config::GrpcConfig; pub async fn start( addr: Ipv4Addr, @@ -23,15 +29,54 @@ pub async fn start( backends_map: HashMap, gateway_indexes_map: HashMap, tcp_conns_map: HashMap, + tls_config: GrpcConfig, ) -> Result<(), Error> { let (_, health_service) = tonic_health::server::health_reporter(); let server = server::BackendService::new(backends_map, gateway_indexes_map, tcp_conns_map); - // TODO: mTLS https://github.com/Kong/blixt/issues/50 - Server::builder() + let mut server_builder = Server::builder(); + server_builder = setup_tls(server_builder, &tls_config); + server_builder .add_service(health_service) .add_service(BackendsServer::new(server)) .serve(SocketAddrV4::new(addr, port).into()) .await?; Ok(()) } + +fn setup_tls(mut builder: Server, tls_config: &GrpcConfig) -> Server { + if tls_config.enable_tls || tls_config.enable_mtls { + let mut tls = ServerTlsConfig::new(); + + if let Some(cert_path) = &tls_config.server_certificate_path { + let cert = fs::read_to_string(cert_path).expect("Error reading server certificate"); + let key = fs::read_to_string( + tls_config + .server_private_key_path + .as_ref() + .expect("Missing private key path"), + ) + .expect("Error reading server private key"); + let server_identity = Identity::from_pem(cert, key); + tls = tls.identity(server_identity); + } + + if tls_config.enable_mtls { + if let Some(client_ca_cert_path) = &tls_config.client_certificate_authority_root_path { + let client_ca_cert = + fs::read_to_string(client_ca_cert_path).expect("Error reading CA certificate"); + let client_ca_root = Certificate::from_pem(client_ca_cert); + tls = tls.client_ca_root(client_ca_root); + + builder = builder + .tls_config(tls) + .expect("Error adding mTLS to server"); + info!("gRPC mTLS enabled"); + return builder; + } + } + builder = builder.tls_config(tls).expect("Error adding TLS to server"); + info!("gRPC TLS enabled"); + } + builder +} diff --git a/dataplane/loader/src/main.rs b/dataplane/loader/src/main.rs index 2ec5ffe8..0a1eefcb 100644 --- a/dataplane/loader/src/main.rs +++ b/dataplane/loader/src/main.rs @@ -7,6 +7,7 @@ SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) use std::net::Ipv4Addr; use anyhow::Context; +use api_server::config::GrpcConfig; use api_server::start as start_api_server; use aya::maps::HashMap; use aya::programs::{tc, SchedClassifier, TcAttachType}; @@ -20,6 +21,8 @@ use log::{info, warn}; struct Opt { #[clap(short, long, default_value = "lo")] iface: String, + #[clap(flatten)] + tls_config: GrpcConfig, } #[tokio::main] @@ -28,59 +31,60 @@ async fn main() -> Result<(), anyhow::Error> { env_logger::init(); - info!("loading ebpf programs"); - - #[cfg(debug_assertions)] - let mut bpf = Bpf::load(include_bytes_aligned!( - "../../target/bpfel-unknown-none/debug/loader" - ))?; - #[cfg(not(debug_assertions))] - let mut bpf = Bpf::load(include_bytes_aligned!( - "../../target/bpfel-unknown-none/release/loader" - ))?; - if let Err(e) = BpfLogger::init(&mut bpf) { - warn!("failed to initialize eBPF logger: {}", e); - } - - info!("attaching tc_ingress program to {}", &opt.iface); - - let _ = tc::qdisc_add_clsact(&opt.iface); - let ingress_program: &mut SchedClassifier = - bpf.program_mut("tc_ingress").unwrap().try_into()?; - ingress_program.load()?; - ingress_program - .attach(&opt.iface, TcAttachType::Ingress) - .context("failed to attach the ingress TC program")?; - - info!("attaching tc_egress program to {}", &opt.iface); - - let egress_program: &mut SchedClassifier = - bpf.program_mut("tc_egress").unwrap().try_into()?; - egress_program.load()?; - egress_program - .attach(&opt.iface, TcAttachType::Egress) - .context("failed to attach the egress TC program")?; - - info!("starting api server"); - let backends: HashMap<_, BackendKey, BackendList> = - HashMap::try_from(bpf.take_map("BACKENDS").expect("no maps named BACKENDS"))?; - let gateway_indexes: HashMap<_, BackendKey, u16> = HashMap::try_from( - bpf.take_map("GATEWAY_INDEXES") - .expect("no maps named GATEWAY_INDEXES"), - )?; - let tcp_conns: HashMap<_, ClientKey, LoadBalancerMapping> = HashMap::try_from( - bpf.take_map("LB_CONNECTIONS") - .expect("no maps named LB_CONNECTIONS"), - )?; - - start_api_server( - Ipv4Addr::new(0, 0, 0, 0), - 9874, - backends, - gateway_indexes, - tcp_conns, - ) - .await?; + info!("loading ebpf programs"); + + #[cfg(debug_assertions)] + let mut bpf = Bpf::load(include_bytes_aligned!( + "../../target/bpfel-unknown-none/debug/loader" + ))?; + #[cfg(not(debug_assertions))] + let mut bpf = Bpf::load(include_bytes_aligned!( + "../../target/bpfel-unknown-none/release/loader" + ))?; + if let Err(e) = BpfLogger::init(&mut bpf) { + warn!("failed to initialize eBPF logger: {}", e); + } + + info!("attaching tc_ingress program to {}", &opt.iface); + + let _ = tc::qdisc_add_clsact(&opt.iface); + let ingress_program: &mut SchedClassifier = + bpf.program_mut("tc_ingress").unwrap().try_into()?; + ingress_program.load()?; + ingress_program + .attach(&opt.iface, TcAttachType::Ingress) + .context("failed to attach the ingress TC program")?; + + info!("attaching tc_egress program to {}", &opt.iface); + + let egress_program: &mut SchedClassifier = bpf.program_mut("tc_egress").unwrap().try_into()?; + egress_program.load()?; + egress_program + .attach(&opt.iface, TcAttachType::Egress) + .context("failed to attach the egress TC program")?; + + info!("starting api server"); + info!("Using tls config: {:?}", &opt.tls_config); + let backends: HashMap<_, BackendKey, BackendList> = + HashMap::try_from(bpf.take_map("BACKENDS").expect("no maps named BACKENDS"))?; + let gateway_indexes: HashMap<_, BackendKey, u16> = HashMap::try_from( + bpf.take_map("GATEWAY_INDEXES") + .expect("no maps named GATEWAY_INDEXES"), + )?; + let tcp_conns: HashMap<_, ClientKey, LoadBalancerMapping> = HashMap::try_from( + bpf.take_map("LB_CONNECTIONS") + .expect("no maps named LB_CONNECTIONS"), + )?; + + start_api_server( + Ipv4Addr::new(0, 0, 0, 0), + 9874, + backends, + gateway_indexes, + tcp_conns, + opt.tls_config, + ) + .await?; info!("Exiting...");