Skip to content

Commit

Permalink
feat(mTLS): adds mTLS support to dataplane api-server
Browse files Browse the repository at this point in the history
  • Loading branch information
EandrewJones committed Oct 21, 2024
1 parent 1f8d172 commit 06d22a3
Show file tree
Hide file tree
Showing 14 changed files with 584 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ UDP_SERVER_DOCKERFILE ?= build/Containerfile.udp_server

# Other testing variables
EXISTING_CLUSTER ?=
TEST_CERTS_PATH ?= test/integration/certs

# Image URL to use all building/pushing image targets
TAG ?= integration-tests
Expand Down Expand Up @@ -99,12 +100,15 @@ $(LOCALBIN):
mkdir -p $(LOCALBIN)

## Tool Binaries
CFSSL ?= $(LOCALBIN)/cfssl
CFSSLJSON ?= $(LOCALBIN)/cfssljson
KUSTOMIZE ?= $(LOCALBIN)/kustomize
ENVTEST ?= $(LOCALBIN)/setup-envtest
KIND ?= $(LOCALBIN)/kind
KTF ?= $(LOCALBIN)/ktf

## Tool Versions
CFSSL_VERSION ?= v1.6.5
KUSTOMIZE_VERSION ?= v5.3.0
CONTROLLER_TOOLS_VERSION ?= v0.14.0
KIND_VERSION ?= v0.22.0
Expand All @@ -115,6 +119,16 @@ KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/k
# Build Dependencies
# ------------------------------------------------------------------------------

.PHONY: cfssl
cfssl: $(CFSSL) ## Download cfssl locally if necessary
$(CFSSL): $(LOCALBIN)
test -s $(LOCALBIN)/cfssl || GOBIN=$(LOCALBIN) go install github.com/cloudflare/cfssl/cmd/cfssl@$(CFSSL_VERSION)

.PHONY: cfssljson
cfssljson: $(CFSSLJSON)
$(CFSSLJSON): $(LOCALBIN)
test -s $(LOCALBIN)/cfssljson || GOBIN=$(LOCALBIN) go install github.com/cloudflare/cfssl/cmd/cfssljson@$(CFSSL_VERSION)

.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
$(KUSTOMIZE): $(LOCALBIN)
Expand Down Expand Up @@ -216,6 +230,36 @@ lint: ## Lint Rust code
test: ## Run tests
cargo test -vv

.PHONY: test.gencert
test.gencert: cfssl cfssljson
$(CFSSL) gencert \
-initca $(TEST_CERTS_PATH)/ca-csr.json | $(CFSSLJSON) -bare ca
$(CFSSL) gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=$(TEST_CERTS_PATH)/ca-config.json \
-profile=server \
$(TEST_CERTS_PATH)/server-csr.json | $(CFSSLJSON) -bare server
$(CFSSL) gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=$(TEST_CERTS_PATH)/ca-config.json \
-profile=clinet \
$(TEST_CERTS_PATH)/client-csr.json | $(CFSSLJSON) -bare client
mv *.pem *.csr $(TEST_CERTS_PATH)

.PHONY: test.rmcert
test.rmcert:
rm $(TEST_CERTS_PATH)/{*.pem,*.csr}

.PHONY: test.dataplane.integration
test.dataplane.integration: test.gencert
go clean -testcache
TEST_CERTS_PATH=$(TEST_CERTS_PATH) \
BLIXT_DATAPLANE_IMAGE=$(BLIXT_DATAPLANE_IMAGE):$(TAG) \
GOFLAGS="-tags=dataplane_tests" go test -race -v ./test/integration/...
$(MAKE) test.rmcert

.PHONY: test.integration.deprecated
test.integration.deprecated: ## Run the deprecated Golang integration tests
go clean -testcache
Expand Down
5 changes: 5 additions & 0 deletions dataplane/api-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ version.workspace = true
[dependencies]
anyhow = { workspace = true }
aya = { workspace = true, features = ["async_tokio"] }
clap = { workspace = true, features = ["derive"] }
common = { workspace = true, features = ["user"] }
libc = { workspace = true }
log = { workspace = true }
Expand All @@ -27,3 +28,7 @@ tonic-health = { workspace = true }

[build-dependencies]
tonic-build = { workspace = true }

[dev-dependencies]
tempfile = "3.3.0"
rcgen = "0.9.3"
31 changes: 31 additions & 0 deletions dataplane/api-server/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright 2023 The Kubernetes Authors.
SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
*/
use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Debug, Subcommand)]
pub enum TLSConfig {
TLS(ServerOnlyTLSConfig),
MutualTLS(MutualTLSConfig),
}

#[derive(Debug, Parser, Clone)]
pub struct ServerOnlyTLSConfig {
#[clap(short, long)]
pub server_certificate_path: PathBuf,
#[clap(short, long)]
pub server_private_key_path: PathBuf,
}

#[derive(Debug, Parser, Clone)]
pub struct MutualTLSConfig {
#[clap(short, long)]
pub server_certificate_path: PathBuf,
#[clap(short, long)]
pub server_private_key_path: PathBuf,
#[clap(short, long)]
pub client_certificate_authority_root_path: PathBuf,
}
85 changes: 79 additions & 6 deletions dataplane/api-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,106 @@ 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 anyhow::{Context, Result};
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::TLSConfig;

pub async fn start(
addr: Ipv4Addr,
port: u16,
backends_map: HashMap<MapData, BackendKey, BackendList>,
gateway_indexes_map: HashMap<MapData, BackendKey, u16>,
tcp_conns_map: HashMap<MapData, ClientKey, LoadBalancerMapping>,
) -> Result<(), Error> {
tls_config: Option<TLSConfig>,
) -> Result<()> {
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(())
}

pub fn setup_tls(mut builder: Server, tls_config: &Option<TLSConfig>) -> Result<Server> {
// TLS implementation drawn from Tonic examples.
// See: https://github.com/hyperium/tonic/blob/master/examples/src/tls_client_auth/server.rs
match tls_config {
Some(TLSConfig::TLS(config)) => {
let mut tls = ServerTlsConfig::new();

let cert = fs::read_to_string(&config.server_certificate_path).with_context(|| {
format!(
"Failed to read certificate from {:?}",
config.server_certificate_path
)
})?;
let key = fs::read_to_string(&config.server_private_key_path).with_context(|| {
format!(
"Failed to read key from {:?}",
config.server_private_key_path
)
})?;
let server_identity = Identity::from_pem(cert, key);
tls = tls.identity(server_identity);

builder = builder.tls_config(tls)?;
info!("gRPC TLS enabled");
Ok(builder)
}
Some(TLSConfig::MutualTLS(config)) => {
let mut tls = ServerTlsConfig::new();

let cert =
fs::read_to_string(config.server_certificate_path.clone()).with_context(|| {
format!(
"Failed to read certificate from {:?}",
config.server_certificate_path
)
})?;
let key =
fs::read_to_string(config.server_private_key_path.clone()).with_context(|| {
format!(
"Failed to read key from {:?}",
config.server_private_key_path
)
})?;
let server_identity = Identity::from_pem(cert, key);
tls = tls.identity(server_identity);

let client_ca_cert =
fs::read_to_string(config.client_certificate_authority_root_path.clone())
.with_context(|| {
format!(
"Failed to read client CA from {:?}",
config.client_certificate_authority_root_path
)
})?;
let client_ca_root = Certificate::from_pem(client_ca_cert);
tls = tls.client_ca_root(client_ca_root);

builder = builder.tls_config(tls)?;
info!("gRPC mTLS enabled");
Ok(builder)
}
None => Ok(builder),
}
}
87 changes: 87 additions & 0 deletions dataplane/api-server/tests/test_setup_tls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use anyhow::Result;
use api_server::config::{MutualTLSConfig, ServerOnlyTLSConfig, TLSConfig};
use api_server::setup_tls;
use rcgen::{generate_simple_self_signed, Certificate, CertificateParams};
use std::fs;
use tempfile::tempdir;
use tonic::transport::Server;

#[tokio::test]
async fn test_tls_self_signed_cert() -> Result<()> {
// Create a temporary directory
let temp_dir = tempdir().unwrap();

// Generate self-signed certificate
let cert = generate_simple_self_signed(vec!["localhost".into()])?;
let cert_pem = cert.serialize_pem()?;
let key_pem = cert.serialize_private_key_pem();

// Paths for the server cert and private key
let cert_path = temp_dir.path().join("server.crt");
let key_path = temp_dir.path().join("server.key");

// Write cert and key to temp files
fs::write(&cert_path, cert_pem.as_bytes())?;
fs::write(&key_path, key_pem.as_bytes())?;

// Set up a TLS config with paths to the cert and key
let tls_config = Some(TLSConfig::TLS(ServerOnlyTLSConfig {
server_certificate_path: cert_path.clone(),
server_private_key_path: key_path.clone(),
}));

// Prepare a dummy server builder
let builder = Server::builder();

// Run the setup_tls function and ensure no error is thrown
let result = setup_tls(builder, &tls_config);
assert!(
result.is_ok(),
"setup_tls should succeed with valid self-signed certs"
);
Ok(())
}

#[tokio::test]
async fn test_mtls_self_signed_cert() -> Result<()> {
// Create a temporary directory
let temp_dir = tempdir().unwrap();

// Generate self-signed certificate
let cert = generate_simple_self_signed(vec!["localhost".into()])?;
let cert_pem = cert.serialize_pem()?;
let key_pem = cert.serialize_private_key_pem();

// Generate CA
let ca_params = CertificateParams::default();
let ca_cert = Certificate::from_params(ca_params)?;
let ca_cert_pem = ca_cert.serialize_pem()?;

// Cert file paths
let cert_path = temp_dir.path().join("server.crt");
let key_path = temp_dir.path().join("server.key");
let ca_cert_path = temp_dir.path().join("ca.crt");

// Write cert and key to temp files
fs::write(&cert_path, cert_pem.as_bytes())?;
fs::write(&key_path, key_pem.as_bytes())?;
fs::write(&ca_cert_path, ca_cert_pem.as_bytes())?;

// Set up a TLS config with paths to the cert and key
let tls_config = Some(TLSConfig::MutualTLS(MutualTLSConfig {
server_certificate_path: cert_path.clone(),
server_private_key_path: key_path.clone(),
client_certificate_authority_root_path: ca_cert_path.clone(),
}));

// Prepare a dummy server builder
let builder = Server::builder();

// Run the setup_tls function and ensure no error is thrown
let result = setup_tls(builder, &tls_config);
assert!(
result.is_ok(),
"setup_tls should succeed with valid self-signed certs"
);
Ok(())
}
Loading

0 comments on commit 06d22a3

Please sign in to comment.