diff --git a/.github/workflows/agdb_server.yaml b/.github/workflows/agdb_server.yaml index fa5a3ae1..4b69cddf 100644 --- a/.github/workflows/agdb_server.yaml +++ b/.github/workflows/agdb_server.yaml @@ -24,7 +24,7 @@ jobs: - run: cargo clippy -p agdb_server -p agdb_api --all-targets --all-features -- -D warnings - uses: taiki-e/install-action@cargo-llvm-cov - run: rustup component add llvm-tools-preview - - run: cargo llvm-cov -p agdb_server -p agdb_api --all-features --ignore-filename-regex "agdb(.|..)src|agdb_derive" --fail-uncovered-functions 42 --fail-uncovered-lines 231 --show-missing-lines + - run: cargo llvm-cov -p agdb_server -p agdb_api --all-features --ignore-filename-regex "agdb(.|..)src|agdb_derive" --fail-uncovered-functions 43 --fail-uncovered-lines 236 --show-missing-lines agdb_server_image: runs-on: ubuntu-latest @@ -33,10 +33,10 @@ jobs: - run: docker build --pull -t agnesoft/agdb:dev -f agdb_server/containerfile . - run: docker compose -f agdb_server/compose.yaml up --wait - run: sleep 5 - - run: curl -s http://localhost:3000/studio - - run: if [[ "$(curl -s http://localhost:3000/api/v1/cluster/status | grep "\"leader\":true")" == "" ]]; then exit 1; fi + - run: curl -s -k https://localhost:3000/studio + - run: if [[ "$(curl -s -k https://localhost:3000/api/v1/cluster/status | grep "\"leader\":true")" == "" ]]; then exit 1; fi - run: | - token=$(curl -X POST http://localhost:3002/api/v1/cluster/user/login -H "Content-Type: application/json" -d '{"username":"admin","password":"admin"}') - curl -H "Authorization: Bearer $token" -X POST http://localhost:3002/api/v1/admin/shutdown - curl -H "Authorization: Bearer $token" -X POST http://localhost:3000/api/v1/admin/shutdown - curl -H "Authorization: Bearer $token" -X POST http://localhost:3001/api/v1/admin/shutdown + token=$(curl -X POST -k https://localhost:3002/api/v1/cluster/user/login -H "Content-Type: application/json" -d '{"username":"admin","password":"admin"}') + curl -k -H "Authorization: Bearer $token" -X POST https://localhost:3002/api/v1/admin/shutdown + curl -k -H "Authorization: Bearer $token" -X POST https://localhost:3000/api/v1/admin/shutdown + curl -k -H "Authorization: Bearer $token" -X POST https://localhost:3001/api/v1/admin/shutdown diff --git a/README.md b/README.md index 33bf32a4..b95a0b4e 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Technical features: - Memory mapped for fast querying - [Server mode](https://agdb.agnesoft.com/docs/references/server) - [Cluster mode](https://agdb.agnesoft.com/docs/references/server#cluster) +- In-built TLS support - [OpenAPI clients](https://agdb.agnesoft.com/api-docs/openapi) in any programming language - [Cloud](https://agdb.agnesoft.com/enterprise/cloud) hosted SaaS database - _Db itself has no dependencies_ @@ -169,9 +170,17 @@ For database concepts and primitive data types see [concepts](https://agdb.agnes ### agdb_api -| Feature | Default | Description | -| ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| reqwest | no | Enables referential implementation of the `HttpClient` trait for agdb API client using [`reqwest`](https://github.com/seanmonstar/reqwest). | +| Feature | Default | Description | +| ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| reqwest | no | Enables referential implementation of the `HttpClient` trait for agdb API client using [`reqwest`](https://github.com/seanmonstar/reqwest). | +| rust-tls | no | Enables rust-tls for [`reqwest`](https://github.com/seanmonstar/reqwest). | +| native-tls | no | Enables native-tls for [`reqwest`](https://github.com/seanmonstar/reqwest). | + +### agdb_server + +| Feature | Default | Description | +| ------- | ------- | ------------------------------------------------------------------------------ | +| tls | no | Enables TLS support via `rustls`. On Windows requires MSVC and CMake to build. | ## agdb logo  Decision Tree diff --git a/agdb_api/php/ci.sh b/agdb_api/php/ci.sh index 6ed6ffe3..d1307d79 100755 --- a/agdb_api/php/ci.sh +++ b/agdb_api/php/ci.sh @@ -2,8 +2,8 @@ function coverage() { rm -f agdb_server.yaml rm -rf agdb_server_data - cargo build --release -p agdb_server - cargo run --release -p agdb_server & + cargo build -r -p agdb_server + cargo run -r -p agdb_server & sleep 3 local output diff --git a/agdb_api/rust/Cargo.toml b/agdb_api/rust/Cargo.toml index ab856668..cbf88b0c 100644 --- a/agdb_api/rust/Cargo.toml +++ b/agdb_api/rust/Cargo.toml @@ -14,11 +14,14 @@ categories = ["database", "api-bindings"] [lib] [features] +default = [] reqwest = ["dep:reqwest"] +rust-tls = ["reqwest/rustls-tls"] +native-tls = ["reqwest/native-tls"] [dependencies] agdb = { version = "0.10.0", path = "../../agdb", features = ["serde", "openapi"] } -reqwest = { version = "0.12", features = ["json"], optional = true } +reqwest = { version = "0.12", default-features = false, features = ["charset", "http2", "macos-system-configuration", "json"], optional = true } serde = { version = "1", features = ["derive"] } serde_json = "1" utoipa = "5" diff --git a/agdb_api/typescript/test.sh b/agdb_api/typescript/test.sh index e257ccd4..27a6df3c 100644 --- a/agdb_api/typescript/test.sh +++ b/agdb_api/typescript/test.sh @@ -1,7 +1,7 @@ rm -f agdb_server.yaml rm -rf agdb_server_data -cargo build --release -p agdb_server -cargo run --release -p agdb_server & +cargo build -r -p agdb_server +cargo run -r -p agdb_server & npx vitest run --coverage error_code=$? diff --git a/agdb_derive/Cargo.toml b/agdb_derive/Cargo.toml index d6aa30ed..68455ddf 100644 --- a/agdb_derive/Cargo.toml +++ b/agdb_derive/Cargo.toml @@ -15,6 +15,6 @@ categories = ["database", "database-implementations"] proc-macro = true [dependencies] -proc-macro2 = "1.0.92" +proc-macro2 = "1" quote = "1" syn = "2" diff --git a/agdb_server/Cargo.toml b/agdb_server/Cargo.toml index fb365539..16f4cb9d 100644 --- a/agdb_server/Cargo.toml +++ b/agdb_server/Cargo.toml @@ -11,15 +11,21 @@ description = "Agnesoft Graph Database Server" keywords = ["graph", "database", "api"] categories = ["database", "database-implementations"] +[features] +default = [] +tls = ["dep:axum-server", "dep:rustls", "reqwest/rustls-tls", "agdb_api/rust-tls"] + [dependencies] agdb = { version = "0.10.0", path = "../agdb", features = ["serde", "openapi"] } agdb_api = { version = "0.10.0", path = "../agdb_api/rust", features = ["reqwest"] } axum = { version = "0.8", features = ["http2"] } axum-extra = { version = "0.10", features = ["typed-header"] } +axum-server = { version = "0.7", features = ["tls-rustls"], optional = true } include_dir = "0.7" http-body-util = "0.1" -reqwest = { version = "0.12", features = ["json", "stream"] } +reqwest = { version = "0.12", default-features = false, features = ["charset", "http2", "macos-system-configuration", "json", "stream"] } ring = "0.17" +rustls = { version = "0.23", optional = true } serde = { version = "1", features = ["derive"] } serde_json = "1" serde_yml = "0.0.12" diff --git a/agdb_server/compose.yaml b/agdb_server/compose.yaml index 029b8a23..b2c81d3b 100644 --- a/agdb_server/compose.yaml +++ b/agdb_server/compose.yaml @@ -12,6 +12,12 @@ services: target: /agdb/agdb_server.yaml - source: pepper target: /agdb/pepper + - source: cert + target: /agdb/cert.pem + - source: cert_key + target: /agdb/cert.key.pem + - source: root_ca + target: /agdb/root_ca.pem agdb1: image: agnesoft/agdb:dev hostname: agdb1 @@ -25,6 +31,12 @@ services: target: /agdb/agdb_server.yaml - source: pepper target: /agdb/pepper + - source: cert + target: /agdb/cert.pem + - source: cert_key + target: /agdb/cert.key.pem + - source: root_ca + target: /agdb/root_ca.pem agdb2: image: agnesoft/agdb:dev hostname: agdb2 @@ -38,7 +50,12 @@ services: target: /agdb/agdb_server.yaml - source: pepper target: /agdb/pepper - + - source: cert + target: /agdb/cert.pem + - source: cert_key + target: /agdb/cert.key.pem + - source: root_ca + target: /agdb/root_ca.pem volumes: agdb0_data: agdb1_data: @@ -51,39 +68,54 @@ configs: agdb0_config: content: | bind: :::3000 - address: http://agdb0:3000 + address: https://agdb0:3000 basepath: "" admin: admin log_level: INFO data_dir: /agdb/data pepper_path: /agdb/pepper + tls_certificate: /agdb/cert.pem + tls_key: /agdb/cert.key.pem + tls_root: /agdb/root_ca.pem cluster_token: cluster cluster_heartbeat_timeout_ms: 1000 cluster_term_timeout_ms: 3000 - cluster: [http://agdb0:3000, http://agdb1:3001, http://agdb2:3002] + cluster: [https://agdb0:3000, https://agdb1:3001, https://agdb2:3002] agdb1_config: content: | bind: :::3001 - address: http://agdb1:3001 + address: https://agdb1:3001 basepath: "" admin: admin log_level: INFO data_dir: /agdb/data pepper_path: /agdb/pepper + tls_certificate: /agdb/cert.pem + tls_key: /agdb/cert.key.pem + tls_root: /agdb/root_ca.pem cluster_token: cluster cluster_heartbeat_timeout_ms: 1000 cluster_term_timeout_ms: 3000 - cluster: [http://agdb0:3000, http://agdb1:3001, http://agdb2:3002] + cluster: [https://agdb0:3000, https://agdb1:3001, https://agdb2:3002] agdb2_config: content: | bind: :::3002 - address: http://agdb2:3002 + address: https://agdb2:3002 basepath: "" admin: admin log_level: INFO data_dir: /agdb/data pepper_path: /agdb/pepper + tls_certificate: /agdb/cert.pem + tls_key: /agdb/cert.key.pem + tls_root: /agdb/root_ca.pem cluster_token: cluster cluster_heartbeat_timeout_ms: 1000 cluster_term_timeout_ms: 3000 - cluster: [http://agdb0:3000, http://agdb1:3001, http://agdb2:3002] + cluster: [https://agdb0:3000, https://agdb1:3001, https://agdb2:3002] + cert: + file: ./test_certs/test_cert.pem + cert_key: + file: ./test_certs/test_cert.key.pem + root_ca: + file: ./test_certs/test_root_ca.pem diff --git a/agdb_server/containerfile b/agdb_server/containerfile index 90411c12..d8fca00b 100644 --- a/agdb_server/containerfile +++ b/agdb_server/containerfile @@ -10,8 +10,8 @@ FROM rust:alpine AS builder_server WORKDIR /usr/src/agdb_server COPY . . COPY --from=builder_studio /usr/src/agdb_studio/agdb_studio/dist /usr/src/agdb_server/agdb_studio/dist -RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static -RUN cargo build --package agdb_server --release +RUN apk add --no-cache musl-dev +RUN cargo build -r -p agdb_server --all-features FROM alpine:latest COPY --from=builder_server /usr/src/agdb_server/target/release/agdb_server /usr/local/bin/agdb_server diff --git a/agdb_server/src/cluster.rs b/agdb_server/src/cluster.rs index 184a6e40..ee3475d6 100644 --- a/agdb_server/src/cluster.rs +++ b/agdb_server/src/cluster.rs @@ -82,6 +82,7 @@ impl ClusterNodeImpl { address: &str, token: &str, responses: UnboundedSender<(Request, Response)>, + config: &Config, ) -> ServerResult { let base = if address.starts_with("http") || address.starts_with("https") { address.to_string() @@ -93,11 +94,7 @@ impl ClusterNodeImpl { let base_url = base.trim_end_matches("/").to_string(); Ok(Self { - client: ReqwestClient::with_client( - reqwest::Client::builder() - .connect_timeout(Duration::from_secs(60)) - .build()?, - ), + client: ReqwestClient::with_client(reqwest_client(config)?), url: format!("{base_url}/api/v1/cluster"), base_url, token: Some(token.to_string()), @@ -199,6 +196,7 @@ pub(crate) async fn new(config: &Config, db: &ServerDb, db_pool: &DbPool) -> Ser node.as_str(), &config.cluster_token, requests.clone(), + config, )?)); } @@ -422,3 +420,40 @@ impl Storage for ClusterStorage { self.db.logs_since(from_index).await } } + +#[cfg(feature = "tls")] +pub(crate) fn root_ca(config: &Config) -> ServerResult> { + static ROOT_CA: std::sync::OnceLock> = std::sync::OnceLock::new(); + + Ok(ROOT_CA + .get_or_init(|| { + if config.tls_root.is_empty() { + return None; + } + + let cert_data = std::fs::read(std::path::Path::new(&config.tls_root)) + .expect("root certificate could not be read"); + let cert = reqwest::Certificate::from_pem(&cert_data) + .expect("root certificate data is invalid"); + Some(cert) + }) + .clone()) +} + +#[cfg(feature = "tls")] +pub(crate) fn reqwest_client(config: &Config) -> ServerResult { + let mut builder = reqwest::Client::builder().timeout(Duration::from_secs(60)); + + if let Some(root_ca) = root_ca(config)? { + builder = builder.add_root_certificate(root_ca).use_rustls_tls(); + } + + Ok(builder.build()?) +} + +#[cfg(not(feature = "tls"))] +pub(crate) fn reqwest_client(_config: &Config) -> ServerResult { + Ok(reqwest::Client::builder() + .timeout(Duration::from_secs(60)) + .build()?) +} diff --git a/agdb_server/src/config.rs b/agdb_server/src/config.rs index d0a2bde9..8c45de43 100644 --- a/agdb_server/src/config.rs +++ b/agdb_server/src/config.rs @@ -22,6 +22,9 @@ pub(crate) struct ConfigImpl { pub(crate) log_level: LogLevel, pub(crate) data_dir: String, pub(crate) pepper_path: String, + pub(crate) tls_certificate: String, + pub(crate) tls_key: String, + pub(crate) tls_root: String, pub(crate) cluster_token: String, pub(crate) cluster_heartbeat_timeout_ms: u64, pub(crate) cluster_term_timeout_ms: u64, @@ -82,6 +85,9 @@ pub(crate) fn new(config_file: &str) -> ServerResult { log_level: LogLevel(LevelFilter::INFO), data_dir: "agdb_server_data".to_string(), pepper_path: String::new(), + tls_certificate: String::new(), + tls_key: String::new(), + tls_root: String::new(), cluster_token: "cluster".to_string(), cluster_heartbeat_timeout_ms: 1000, cluster_term_timeout_ms: 3000, @@ -172,6 +178,9 @@ mod tests { log_level: LogLevel(LevelFilter::INFO), data_dir: "agdb_server_data".to_string(), pepper_path: String::new(), + tls_certificate: String::new(), + tls_key: String::new(), + tls_root: String::new(), cluster_token: "cluster".to_string(), cluster_heartbeat_timeout_ms: 1000, cluster_term_timeout_ms: 3000, @@ -201,6 +210,9 @@ mod tests { log_level: LogLevel(LevelFilter::INFO), data_dir: "agdb_server_data".to_string(), pepper_path: pepper_file.filename.to_string(), + tls_certificate: String::new(), + tls_key: String::new(), + tls_root: String::new(), cluster_token: "cluster".to_string(), cluster_heartbeat_timeout_ms: 1000, cluster_term_timeout_ms: 3000, @@ -228,6 +240,9 @@ mod tests { log_level: LogLevel(LevelFilter::INFO), data_dir: "agdb_server_data".to_string(), pepper_path: "missing_file".to_string(), + tls_certificate: String::new(), + tls_key: String::new(), + tls_root: String::new(), cluster_token: "cluster".to_string(), cluster_heartbeat_timeout_ms: 1000, cluster_term_timeout_ms: 3000, @@ -254,6 +269,9 @@ mod tests { log_level: LogLevel(LevelFilter::INFO), data_dir: "agdb_server_data".to_string(), pepper_path: pepper_file.filename.to_string(), + tls_certificate: String::new(), + tls_key: String::new(), + tls_root: String::new(), cluster_token: "cluster".to_string(), cluster_heartbeat_timeout_ms: 1000, cluster_term_timeout_ms: 3000, diff --git a/agdb_server/src/main.rs b/agdb_server/src/main.rs index 54c26c7d..5893024f 100644 --- a/agdb_server/src/main.rs +++ b/agdb_server/src/main.rs @@ -34,7 +34,6 @@ async fn main() -> ServerResult { let server_db = server_db::new(&config).await?; let db_pool = db_pool::new(config.clone(), &server_db).await?; let cluster = cluster::new(&config, &server_db, &db_pool).await?; - let app = app::app( cluster.clone(), config.clone(), @@ -42,15 +41,49 @@ async fn main() -> ServerResult { server_db, shutdown_sender.clone(), ); + let cluster_handle = cluster::start_with_shutdown(cluster, shutdown_receiver); + tracing::info!("Process id: {}", std::process::id()); tracing::info!( "Data directory: {}", std::env::current_dir()?.join(&config.data_dir).display() ); + + #[cfg(feature = "tls")] + if !config.tls_certificate.is_empty() && !config.tls_key.is_empty() { + rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .expect("Default Crypto Provider failed to install"); + + let tls_config = axum_server::tls_rustls::RustlsConfig::from_pem_file( + std::path::PathBuf::from(config.tls_certificate.clone()), + std::path::PathBuf::from(config.tls_key.clone()), + ) + .await?; + let handle = axum_server::Handle::new(); + let shutdown_handle = handle.clone(); + + tracing::info!("TLS enabled"); + + tokio::spawn(async move { + cluster_handle.await; + shutdown_handle.graceful_shutdown(Some(std::time::Duration::from_secs(5))); + }); + + tracing::info!("Address: {}", config.address); + tracing::info!("Listening at {}", config.bind); + let listener = std::net::TcpListener::bind(&config.bind)?; + return Ok(axum_server::from_tcp_rustls(listener, tls_config) + .handle(handle) + .serve(app.into_make_service()) + .await?); + } + + tracing::info!("Address: {}", config.address); tracing::info!("Listening at {}", config.bind); let listener = tokio::net::TcpListener::bind(&config.bind).await?; axum::serve(listener, app) - .with_graceful_shutdown(cluster::start_with_shutdown(cluster, shutdown_receiver)) + .with_graceful_shutdown(cluster_handle) .await?; Ok(()) diff --git a/agdb_server/src/routes/cluster.rs b/agdb_server/src/routes/cluster.rs index a01a4f94..971dd3b2 100644 --- a/agdb_server/src/routes/cluster.rs +++ b/agdb_server/src/routes/cluster.rs @@ -1,5 +1,6 @@ use crate::action::cluster_login::ClusterLogin; use crate::action::ClusterAction; +use crate::cluster; use crate::cluster::Cluster; use crate::config::Config; use crate::raft::Request; @@ -147,10 +148,9 @@ pub(crate) async fn status( if index != cluster.index { let address = node.as_str().to_string(); let url = format!("{}/api/v1/status", node.trim_end_matches("/")); + let client = cluster::reqwest_client(&config)?; tasks.push(tokio::spawn(async move { - let client = reqwest::Client::new(); - let response = client .get(&url) .timeout(std::time::Duration::from_secs(5)) diff --git a/agdb_server/test_certs/test_cert.key.pem b/agdb_server/test_certs/test_cert.key.pem new file mode 100644 index 00000000..a099f1eb --- /dev/null +++ b/agdb_server/test_certs/test_cert.key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEPpEprqQwhV7Swny +e4L5sOoJY2bgtOGUe/57pqrdYmWhRANCAAR6UWQt5Ry0JzVyV5xubHQNZS15Z2K/ +wmQhCKq2NR0skkbi+mKOAJRyXZNIYwyDovttm4jLJBivf7pzT7MELqk1 +-----END PRIVATE KEY----- diff --git a/agdb_server/test_certs/test_cert.pem b/agdb_server/test_certs/test_cert.pem new file mode 100644 index 00000000..40a3577b --- /dev/null +++ b/agdb_server/test_certs/test_cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBkjCCATigAwIBAgIUWn53f4/6yjv4D0IN6wbuVZT6TnUwCgYIKoZIzj0EAwIw +IDELMAkGA1UEBhMCQ1oxETAPBgNVBAoMCEFnbmVzb2Z0MCAXDTc1MDEwMTAwMDAw +MFoYDzQwOTYwMTAxMDAwMDAwWjAPMQ0wCwYDVQQDDARhZ2RiMFkwEwYHKoZIzj0C +AQYIKoZIzj0DAQcDQgAEelFkLeUctCc1clecbmx0DWUteWdiv8JkIQiqtjUdLJJG +4vpijgCUcl2TSGMMg6L7bZuIyyQYr3+6c0+zBC6pNaNfMF0wHwYDVR0jBBgwFoAU +mR05gP+oUhbf8UtS7CptAPI6pP8wKQYDVR0RBCIwIIIJbG9jYWxob3N0ggVhZ2Ri +MIIFYWdkYjGCBWFnZGIyMA8GA1UdDwEB/wQFAwMHgAAwCgYIKoZIzj0EAwIDSAAw +RQIhANHGxb1oxa1uAAU2Uj5imdhgM0Mn2t8ktuCoWgdQKeWsAiBur3oFUJtKjv+N +9iDIRC+SNSg9fp0SAgj4OZ7ia0Ogeg== +-----END CERTIFICATE----- diff --git a/agdb_server/test_certs/test_root_ca.key.pem b/agdb_server/test_certs/test_root_ca.key.pem new file mode 100644 index 00000000..8a2835b2 --- /dev/null +++ b/agdb_server/test_certs/test_root_ca.key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfVzyO/hrjy9gr2IQ +5IHDZF5zTzRC9Yd/VSJLiUuaNfShRANCAATQ4SjGOMwZ3p/MovVGMVOcUncFU2Qd +c3/PVmse33S+GgE4X85r5SlID1c+qKUwVNRcWKv5k3DaowaM2DIeEh4C +-----END PRIVATE KEY----- diff --git a/agdb_server/test_certs/test_root_ca.pem b/agdb_server/test_certs/test_root_ca.pem new file mode 100644 index 00000000..c078832e --- /dev/null +++ b/agdb_server/test_certs/test_root_ca.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBhzCCAS2gAwIBAgIUJjJcD95GcLgWPCRJ6UV9pKy+eoEwCgYIKoZIzj0EAwIw +IDELMAkGA1UEBhMCQ1oxETAPBgNVBAoMCEFnbmVzb2Z0MCAXDTc1MDEwMTAwMDAw +MFoYDzQwOTYwMTAxMDAwMDAwWjAgMQswCQYDVQQGEwJDWjERMA8GA1UECgwIQWdu +ZXNvZnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATQ4SjGOMwZ3p/MovVGMVOc +UncFU2Qdc3/PVmse33S+GgE4X85r5SlID1c+qKUwVNRcWKv5k3DaowaM2DIeEh4C +o0MwQTAPBgNVHQ8BAf8EBQMDB4YAMB0GA1UdDgQWBBSZHTmA/6hSFt/xS1LsKm0A +8jqk/zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIC5hzray0ggD +nJJj3VKNwZMqrV2LgHznzsrPd6elaqnOAiEAsS51yP9dbNDzqihiSFbpX/kN/qMo +eOOHbHpqT+z/BD4= +-----END CERTIFICATE----- diff --git a/agdb_server/tests/routes/cluster_test.rs b/agdb_server/tests/routes/cluster_test.rs index 31c93b9d..f038dd68 100644 --- a/agdb_server/tests/routes/cluster_test.rs +++ b/agdb_server/tests/routes/cluster_test.rs @@ -1,6 +1,7 @@ use crate::create_cluster; use crate::next_db_name; use crate::next_user_name; +use crate::reqwest_client; use crate::wait_for_leader; use crate::wait_for_ready; use crate::TestCluster; @@ -13,17 +14,12 @@ use agdb_api::DbResource; use agdb_api::DbType; use agdb_api::DbUserRole; use agdb_api::ReqwestClient; -use std::time::Duration; #[tokio::test] async fn rebalance() -> anyhow::Result<()> { - let mut servers = create_cluster(3).await?; + let mut servers = create_cluster(3, false).await?; let mut leader = AgdbApi::new( - ReqwestClient::with_client( - reqwest::Client::builder() - .timeout(Duration::from_secs(30)) - .build()?, - ), + ReqwestClient::with_client(reqwest_client()), &servers[0].address, ); leader.user_login(ADMIN, ADMIN).await?; @@ -34,11 +30,7 @@ async fn rebalance() -> anyhow::Result<()> { for server in &servers[1..] { let status = wait_for_leader(&AgdbApi::new( - ReqwestClient::with_client( - reqwest::Client::builder() - .timeout(Duration::from_secs(30)) - .build()?, - ), + ReqwestClient::with_client(reqwest_client()), &server.address, )) .await?; @@ -56,11 +48,7 @@ async fn rebalance() -> anyhow::Result<()> { for server in &servers { let status = wait_for_leader(&AgdbApi::new( - ReqwestClient::with_client( - reqwest::Client::builder() - .timeout(Duration::from_secs(30)) - .build()?, - ), + ReqwestClient::with_client(reqwest_client()), &server.address, )) .await?; diff --git a/agdb_server/tests/routes/misc_routes.rs b/agdb_server/tests/routes/misc_routes.rs index 4380a9ff..752313e0 100644 --- a/agdb_server/tests/routes/misc_routes.rs +++ b/agdb_server/tests/routes/misc_routes.rs @@ -1,3 +1,4 @@ +use crate::reqwest_client; use crate::wait_for_ready; use crate::TestServer; use crate::TestServerImpl; @@ -11,12 +12,11 @@ use agdb_api::ReqwestClient; use reqwest::StatusCode; use std::collections::HashMap; use std::path::Path; -use std::time::Duration; #[tokio::test] async fn missing() -> anyhow::Result<()> { let server = TestServer::new().await?; - let client = reqwest::Client::new(); + let client = reqwest_client(); let status = client .get(server.full_url("/missing")) .send() @@ -45,7 +45,7 @@ async fn shutdown_no_token() -> anyhow::Result<()> { #[tokio::test] async fn shutdown_bad_token() -> anyhow::Result<()> { let server = TestServer::new().await?; - let client = reqwest::Client::new(); + let client = reqwest_client(); let status = client .post(server.full_url("/admin/shutdown")) .bearer_auth("bad") @@ -59,7 +59,7 @@ async fn shutdown_bad_token() -> anyhow::Result<()> { #[tokio::test] async fn openapi() -> anyhow::Result<()> { let server = TestServer::new().await?; - let client = reqwest::Client::new(); + let client = reqwest_client(); let status = client .get(server.full_url("/openapi.json")) .send() @@ -73,11 +73,7 @@ async fn openapi() -> anyhow::Result<()> { async fn config_reuse() -> anyhow::Result<()> { let mut server = TestServerImpl::new().await?; let mut client = AgdbApi::new( - ReqwestClient::with_client( - reqwest::Client::builder() - .timeout(Duration::from_secs(10)) - .build()?, - ), + ReqwestClient::with_client(reqwest_client()), &server.address, ); client.user_login(ADMIN, ADMIN).await?; @@ -92,11 +88,7 @@ async fn config_reuse() -> anyhow::Result<()> { async fn db_list_after_shutdown() -> anyhow::Result<()> { let mut server = TestServerImpl::new().await?; let mut client = AgdbApi::new( - ReqwestClient::with_client( - reqwest::Client::builder() - .timeout(Duration::from_secs(10)) - .build()?, - ), + ReqwestClient::with_client(reqwest_client()), &server.address, ); @@ -127,11 +119,7 @@ async fn db_list_after_shutdown() -> anyhow::Result<()> { async fn db_list_after_shutdown_corrupted_data() -> anyhow::Result<()> { let mut server = TestServerImpl::new().await?; let mut client = AgdbApi::new( - ReqwestClient::with_client( - reqwest::Client::builder() - .timeout(Duration::from_secs(10)) - .build()?, - ), + ReqwestClient::with_client(reqwest_client()), &server.address, ); @@ -168,6 +156,9 @@ async fn basepath_test() -> anyhow::Result<()> { config.insert("basepath", "/public".into()); config.insert("log_level", "INFO".into()); config.insert("pepper_path", "".into()); + config.insert("tls_certificate", "".into()); + config.insert("tls_key", "".into()); + config.insert("tls_root", "".into()); config.insert("cluster_token", "test".into()); config.insert("cluster_heartbeat_timeout_ms", 1000.into()); config.insert("cluster_term_timeout_ms", 3000.into()); @@ -175,7 +166,7 @@ async fn basepath_test() -> anyhow::Result<()> { let server = TestServerImpl::with_config(config).await?; - reqwest::Client::new() + reqwest_client() .get(format!("{}/studio", server.address)) .send() .await? @@ -188,11 +179,7 @@ async fn basepath_test() -> anyhow::Result<()> { async fn location_change_after_restart() -> anyhow::Result<()> { let mut server = TestServerImpl::new().await?; let mut client = AgdbApi::new( - ReqwestClient::with_client( - reqwest::Client::builder() - .timeout(Duration::from_secs(10)) - .build()?, - ), + ReqwestClient::with_client(reqwest_client()), &server.address, ); @@ -238,11 +225,7 @@ async fn location_change_after_restart() -> anyhow::Result<()> { async fn reset_admin_password() -> anyhow::Result<()> { let mut server = TestServerImpl::new().await?; let mut client = AgdbApi::new( - ReqwestClient::with_client( - reqwest::Client::builder() - .timeout(Duration::from_secs(10)) - .build()?, - ), + ReqwestClient::with_client(reqwest_client()), &server.address, ); @@ -275,11 +258,7 @@ async fn reset_admin_password() -> anyhow::Result<()> { async fn memory_db_from_backup() -> anyhow::Result<()> { let mut server = TestServerImpl::new().await?; let mut client = AgdbApi::new( - ReqwestClient::with_client( - reqwest::Client::builder() - .timeout(Duration::from_secs(10)) - .build()?, - ), + ReqwestClient::with_client(reqwest_client()), &server.address, ); let owner = "user1"; @@ -324,8 +303,7 @@ async fn memory_db_from_backup() -> anyhow::Result<()> { #[tokio::test] async fn studio() -> anyhow::Result<()> { let server = TestServer::new().await?; - let client = reqwest::Client::new(); - client + reqwest_client() .get(server.url("/studio")) .send() .await? diff --git a/agdb_server/tests/test_cert.key.pem b/agdb_server/tests/test_cert.key.pem new file mode 100644 index 00000000..35174c00 --- /dev/null +++ b/agdb_server/tests/test_cert.key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgMdh0kQSqxqJSAZrl ++7+5sN6z07RdC11KRswGA5JaQy+hRANCAAQzetUbU9vq9WI5fLjw7dh0MWkTLlmW +0aptniMEj4FcsjITMmK/BZqps2j93OIwRj5QkScsKVgqD1wPPVmFcVZP +-----END PRIVATE KEY----- diff --git a/agdb_server/tests/test_cert.pem b/agdb_server/tests/test_cert.pem new file mode 100644 index 00000000..86e87d6b --- /dev/null +++ b/agdb_server/tests/test_cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBgjCCASigAwIBAgIUS3GnyT+oIgW41510c2nv4dl9gfswCgYIKoZIzj0EAwIw +IDELMAkGA1UEBhMCQ1oxETAPBgNVBAoMCEFnbmVzb2Z0MCAXDTc1MDEwMTAwMDAw +MFoYDzQwOTYwMTAxMDAwMDAwWjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAAQzetUbU9vq9WI5fLjw7dh0MWkTLlmW0aptniME +j4FcsjITMmK/BZqps2j93OIwRj5QkScsKVgqD1wPPVmFcVZPo0owSDAfBgNVHSME +GDAWgBQLLBRvCOdD6uE/1c4ypBdMDkMb6TAUBgNVHREEDTALgglsb2NhbGhvc3Qw +DwYDVR0PAQH/BAUDAweAADAKBggqhkjOPQQDAgNIADBFAiAbxzqZB4dEmpsdJN+J +msIzRVfX8YZW6yKNr8jMPHsGvAIhAMmvbEXR6QRugp4IDHv7xl1riUqJaDN8pgnE +L+tlwGNH +-----END CERTIFICATE----- diff --git a/agdb_server/tests/test_root_ca.key.pem b/agdb_server/tests/test_root_ca.key.pem new file mode 100644 index 00000000..2539ecba --- /dev/null +++ b/agdb_server/tests/test_root_ca.key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfsVqJXY4NhNtQ1eX +ZCVmNuphZ/3DcQOu6RpQAZJKcN6hRANCAARTfmKWb8m4Yp9ymOsHozx4F7vgCTij +p2TCN3orQZbze9xe17QGRLf2POfpCLsH4eFA2Ye7qnR39/9mJ3HckVSw +-----END PRIVATE KEY----- diff --git a/agdb_server/tests/test_root_ca.pem b/agdb_server/tests/test_root_ca.pem new file mode 100644 index 00000000..f9cecb8f --- /dev/null +++ b/agdb_server/tests/test_root_ca.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBhzCCAS2gAwIBAgIUZ8y6umm43omemq3+v2u/1DcrlKowCgYIKoZIzj0EAwIw +IDELMAkGA1UEBhMCQ1oxETAPBgNVBAoMCEFnbmVzb2Z0MCAXDTc1MDEwMTAwMDAw +MFoYDzQwOTYwMTAxMDAwMDAwWjAgMQswCQYDVQQGEwJDWjERMA8GA1UECgwIQWdu +ZXNvZnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARTfmKWb8m4Yp9ymOsHozx4 +F7vgCTijp2TCN3orQZbze9xe17QGRLf2POfpCLsH4eFA2Ye7qnR39/9mJ3HckVSw +o0MwQTAPBgNVHQ8BAf8EBQMDB4YAMB0GA1UdDgQWBBQLLBRvCOdD6uE/1c4ypBdM +DkMb6TAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIGRcAJcJcqmF +wyvYApbz5XO0RZhABp1UFnpVhPlxhraIAiEA/beON8qOQKGT4h7PPTCM1i49pELa +odGlu58oaEN++Bg= +-----END CERTIFICATE----- diff --git a/agdb_server/tests/test_server.rs b/agdb_server/tests/test_server.rs index cd958dc9..a50898a2 100644 --- a/agdb_server/tests/test_server.rs +++ b/agdb_server/tests/test_server.rs @@ -1,4 +1,6 @@ mod routes; +#[cfg(feature = "tls")] +mod tls; use agdb_api::AgdbApi; use agdb_api::ClusterStatus; @@ -64,6 +66,38 @@ pub struct TestCluster { _cluster: Arc, } +#[cfg(feature = "tls")] +pub fn root_ca() -> reqwest::Certificate { + static ROOT_CA: std::sync::OnceLock = std::sync::OnceLock::new(); + + ROOT_CA + .get_or_init(|| { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let root_ca_buf = + std::fs::read(format!("{manifest_dir}/tests/test_root_ca.pem")).unwrap(); + reqwest::Certificate::from_pem(&root_ca_buf).unwrap() + }) + .clone() +} + +#[cfg(feature = "tls")] +pub fn reqwest_client() -> reqwest::Client { + reqwest::Client::builder() + .add_root_certificate(root_ca()) + .use_rustls_tls() + .timeout(CLIENT_TIMEOUT) + .build() + .unwrap() +} + +#[cfg(not(feature = "tls"))] +pub fn reqwest_client() -> reqwest::Client { + reqwest::Client::builder() + .timeout(CLIENT_TIMEOUT) + .build() + .unwrap() +} + impl TestServerImpl { pub async fn with_config(mut config: HashMap<&str, serde_yml::Value>) -> anyhow::Result { let address = if let Some(address) = config.get("address") { @@ -101,10 +135,7 @@ impl TestServerImpl { .current_dir(&dir) .kill_on_drop(true) .spawn()?; - let api = AgdbApi::new( - ReqwestClient::with_client(reqwest::Client::builder().timeout(CLIENT_TIMEOUT).build()?), - &api_address, - ); + let api = AgdbApi::new(ReqwestClient::with_client(reqwest_client()), &api_address); for _ in 0..RETRY_ATTEMPS { match api.status().await { @@ -140,6 +171,9 @@ impl TestServerImpl { config.insert("basepath", "".into()); config.insert("log_level", "INFO".into()); config.insert("pepper_path", "".into()); + config.insert("tls_certificate", "".into()); + config.insert("tls_key", "".into()); + config.insert("tls_root", "".into()); config.insert("cluster_token", "test".into()); config.insert("cluster_heartbeat_timeout_ms", 1000.into()); config.insert("cluster_term_timeout_ms", 3000.into()); @@ -183,7 +217,8 @@ impl TestServerImpl { admin.insert("username", ADMIN.to_string()); admin.insert("password", ADMIN.to_string()); - let client = reqwest::Client::new(); + let client = reqwest_client(); + let token: String = client .post(format!("{}/api/v1/user/login", address)) .json(&admin) @@ -243,9 +278,7 @@ impl TestServer { Ok(Self { api: AgdbApi::new( - ReqwestClient::with_client( - reqwest::Client::builder().timeout(CLIENT_TIMEOUT).build()?, - ), + ReqwestClient::with_client(reqwest_client()), &server.address, ), dir: server.dir.clone(), @@ -301,7 +334,7 @@ impl TestCluster { let nodes = if let Some(nodes) = cluster_guard.upgrade() { nodes } else { - let nodes = Arc::new(create_cluster(3).await?); + let nodes = Arc::new(create_cluster(3, false).await?); *cluster_guard = Arc::downgrade(&nodes); nodes }; @@ -311,9 +344,7 @@ impl TestCluster { .iter() .map(|s| { Ok(AgdbApi::new( - ReqwestClient::with_client( - reqwest::Client::builder().timeout(CLIENT_TIMEOUT).build()?, - ), + ReqwestClient::with_client(reqwest_client()), &s.address, )) }) @@ -365,27 +396,47 @@ pub async fn wait_for_leader(api: &AgdbApi) -> anyhow::Result anyhow::Result> { +pub async fn create_cluster(nodes: usize, tls: bool) -> anyhow::Result> { let mut configs = Vec::with_capacity(nodes); let mut cluster = Vec::with_capacity(nodes); let mut servers = Vec::with_capacity(nodes); + let protocol = if tls { "https" } else { "http" }; + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?; + let tls_cert = if tls { + format!("{manifest_dir}/tests/test_cert.pem") + } else { + String::new() + }; + let tls_key = if tls { + format!("{manifest_dir}/tests/test_cert.key.pem") + } else { + String::new() + }; + let tls_root = if tls { + format!("{manifest_dir}/tests/test_root_ca.pem") + } else { + String::new() + }; for _ in 0..nodes { let port = TestServerImpl::next_port(); let mut config = HashMap::<&str, serde_yml::Value>::new(); config.insert("bind", format!("{HOST}:{port}").into()); - config.insert("address", format!("http://{HOST}:{port}").into()); + config.insert("address", format!("{protocol}://{HOST}:{port}").into()); config.insert("admin", ADMIN.into()); config.insert("basepath", "".into()); config.insert("log_level", "INFO".into()); config.insert("data_dir", SERVER_DATA_DIR.into()); config.insert("pepper_path", "".into()); + config.insert("tls_certificate", tls_cert.clone().into()); + config.insert("tls_key", tls_key.clone().into()); + config.insert("tls_root", tls_root.clone().into()); config.insert("cluster_token", "test".into()); config.insert("cluster_heartbeat_timeout_ms", 1000.into()); config.insert("cluster_term_timeout_ms", 3000.into()); configs.push(config); - cluster.push(format!("http://{HOST}:{port}")); + cluster.push(format!("{protocol}://{HOST}:{port}")); } for config in &mut configs { @@ -398,7 +449,7 @@ pub async fn create_cluster(nodes: usize) -> anyhow::Result> { let server = server.await??; let api = AgdbApi::new( - ReqwestClient::with_client(reqwest::Client::builder().timeout(CLIENT_TIMEOUT).build()?), + ReqwestClient::with_client(reqwest_client()), &server.address, ); servers.push((server, api)); diff --git a/agdb_server/tests/tls/mod.rs b/agdb_server/tests/tls/mod.rs new file mode 100644 index 00000000..9b2cd29c --- /dev/null +++ b/agdb_server/tests/tls/mod.rs @@ -0,0 +1,92 @@ +use crate::create_cluster; +use crate::reqwest_client; +use crate::wait_for_leader; +use crate::wait_for_ready; +use crate::TestServerImpl; +use crate::ADMIN; +use crate::SERVER_DATA_DIR; +use agdb_api::AgdbApi; +use agdb_api::ReqwestClient; +use std::collections::HashMap; + +#[tokio::test] +async fn https() -> anyhow::Result<()> { + let mut config = HashMap::<&str, serde_yml::Value>::new(); + let port = TestServerImpl::next_port(); + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?; + + config.insert("bind", format!(":::{port}").into()); + config.insert("address", format!("https://localhost:{port}").into()); + config.insert("data_dir", SERVER_DATA_DIR.into()); + config.insert("basepath", "".into()); + config.insert("admin", ADMIN.into()); + config.insert("log_level", "INFO".into()); + config.insert("pepper_path", "".into()); + config.insert( + "tls_certificate", + format!("{manifest_dir}/tests/test_cert.pem").into(), + ); + config.insert( + "tls_key", + format!("{manifest_dir}/tests/test_cert.key.pem").into(), + ); + config.insert( + "tls_root", + format!("{manifest_dir}/tests/test_root_ca.pem").into(), + ); + config.insert("cluster_token", "test".into()); + config.insert("cluster_heartbeat_timeout_ms", 1000.into()); + config.insert("cluster_term_timeout_ms", 3000.into()); + config.insert("cluster", Vec::::new().into()); + + TestServerImpl::with_config(config).await?; + + Ok(()) +} + +#[tokio::test] +async fn cluster() -> anyhow::Result<()> { + let mut servers = create_cluster(3, true).await?; + let mut leader = AgdbApi::new( + ReqwestClient::with_client(reqwest_client()), + &servers[0].address, + ); + leader.user_login(ADMIN, ADMIN).await?; + leader.admin_shutdown().await?; + servers[0].wait().await?; + + let mut statuses = Vec::with_capacity(servers.len() - 1); + + for server in &servers[1..] { + let status = wait_for_leader(&AgdbApi::new( + ReqwestClient::with_client(reqwest_client()), + &server.address, + )) + .await?; + statuses.push(status); + } + + for status in &statuses { + assert_eq!(statuses[0], *status); + } + + servers[0].restart()?; + wait_for_ready(&leader).await?; + + statuses.clear(); + + for server in &servers { + let status = wait_for_leader(&AgdbApi::new( + ReqwestClient::with_client(reqwest_client()), + &server.address, + )) + .await?; + statuses.push(status); + } + + for status in &statuses { + assert_eq!(statuses[0], *status); + } + + Ok(()) +} diff --git a/agdb_web/pages/en-US/docs/guides/how-to-run-server/cluster-docker.mdx b/agdb_web/pages/en-US/docs/guides/how-to-run-server/cluster-docker.mdx index 71d9ea20..f89370d6 100644 --- a/agdb_web/pages/en-US/docs/guides/how-to-run-server/cluster-docker.mdx +++ b/agdb_web/pages/en-US/docs/guides/how-to-run-server/cluster-docker.mdx @@ -46,6 +46,8 @@ docker compose -f agdb_server/compose.yaml up --wait This command runs the 3 nodes as a docker cluster using docker compose that contains valid cluster configuration. The volumes are provided for each node so that the data is persisted. It exposes the nodes at the ports `3000`, `3001` and `3002`. +By default, it is using TLS self-signed certificates. You can either remove the certificates and related configuration from the `compose.yaml` or provide your own certificates. Refer to the [server configuration](/docs/references/server) for more details. + ### Test that the cluster is up with `curl` The following commands will hit each node and return the list of nodes, their status and which one is the leader. If the servers are connected and operating normally the returned list should be the same from each node. diff --git a/agdb_web/pages/en-US/docs/guides/how-to-run-server/cluster-k8s.mdx b/agdb_web/pages/en-US/docs/guides/how-to-run-server/cluster-k8s.mdx index 3759bb73..5b429430 100644 --- a/agdb_web/pages/en-US/docs/guides/how-to-run-server/cluster-k8s.mdx +++ b/agdb_web/pages/en-US/docs/guides/how-to-run-server/cluster-k8s.mdx @@ -50,7 +50,7 @@ spec: example `http://agdb-0.agdb.default.svc.cluster.local:3000`. -### Secret +### Secrets Next document is the pepper secret `agdb-pepper`. @@ -65,9 +65,31 @@ stringData: pepper: "1234567891234567" ``` +Followed by the certificates. In production you should create the certificates using kubectl command from secure files rather than embedding them into the manifest. However, for demonstration purposes the following example is provided. It uses self-signed certificate with the corresponding CA. It needs to be provided as `base64` value. You can use e.g. `cat cert.pem | base64` bash command to get the correct value (end of lines do not matter). + + + The certificate must be issued (or used as an alternative name - SAN) for + the DNS names of the servers used in their configuration `address` field. + E.g. `agdb-0.default.svc.cluster.local`, `agdb-1.default.svc.cluster.local`, + `agdb-2.default.svc.cluster.local`. + + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: agdb-certs + labels: + app: agdb +data: + cert.pem: ... + key.pem: ... + root_ca.pem: ... +``` + ### ConfigMap -The configuration named `agdb-config` via the `ConfigMap` is required as we need specific configuration for each node. Additionally we specify a custom `start.sh` script that dynamically assigns the deployment index as a cluster index on startup. +The configuration named `agdb-config` via the `ConfigMap` is required as we need specific configuration for each node. Additionally, we specify a custom `start.sh` script that dynamically assigns the deployment index as a cluster index on startup. We are using the same certificate in all servers and it must be valid for all the names it is used for, e.g. `agdb-0.default.svc.cluster.local`, `agdb-1.default.svc.cluster.local`, `agdb-2.default.svc.cluster.local`. ```yaml --- @@ -90,6 +112,9 @@ data: log_level: INFO data_dir: /agdb/data pepper_path: /agdb/pepper/pepper + tls_certificate: /agdb/certs/cert.pem + tls_key: /agdb/certs/key.pem + tls_root: /agdb/certs/root_ca.pem cluster_token: cluster cluster_heartbeat_timeout_ms: 1000 cluster_term_timeout_ms: 3000 @@ -102,9 +127,9 @@ The main part of the deployment is the stateful set definition. It uses the sele The container spec matches the port on the service by name `agdb` and exposes the container port `3000` (default). The security context specifies the user `1000` (default uid in the container) and disables root access as it is not needed and enhances security. -Finally we specify volumes and volume mounts to add the `secret`, `configmap` and persistent volume claim (PVC) to the expected locations. The PVC is a way how data can survive restart or redeployment. By default 1 GB of storage is specified which can be increased (but not decreased) in subsequent deployments. +Finally, we specify volumes and volume mounts to add the `secrets`, `configmap` and persistent volume claim (PVC) to the expected locations. The PVC is a way how data can survive restart or redeployment. By default, 1 GB of storage is specified which can be increased (but not decreased) in subsequent deployments. -The custom command running the `start.sh` from the configmap and the environment variale `AGDB_REPLICA_INDEX` make sure the correct config is assigned to each node on startup. +The custom command running the `start.sh` from the configmap and the environment variable `AGDB_REPLICA_INDEX` make sure the correct config is assigned to each node on startup. Certificates are provided for TLS support. ```yaml --- @@ -149,6 +174,8 @@ spec: mountPath: /agdb/config - name: pepper mountPath: /agdb/pepper + - name: certs + mountPath: /agdb/certs volumes: - name: config configMap: @@ -157,6 +184,9 @@ spec: - name: pepper secret: secretName: agdb-pepper + - name: certs + secret: + secretName: agdb-certs volumeClaimTemplates: - metadata: name: agdb-data @@ -175,9 +205,9 @@ spec: The following command must be run from within the cluster unless the server was exposed via `LoadBalancer` or `Ingress`. The `.default.` bit is the name of the namespace where everything was deployed. ```bash -curl -v http://agdb-0.agdb.default.svc.cluster.local:3000/api/v1/status # should return 200 OK -curl -v http://agdb-1.agdb.default.svc.cluster.local:3000/api/v1/status # should return 200 OK -curl -v http://agdb-2.agdb.default.svc.cluster.local:3000/api/v1/status # should return 200 OK +curl -v https://agdb-0.agdb.default.svc.cluster.local:3000/api/v1/status # should return 200 OK +curl -v https://agdb-1.agdb.default.svc.cluster.local:3000/api/v1/status # should return 200 OK +curl -v https://agdb-2.agdb.default.svc.cluster.local:3000/api/v1/status # should return 200 OK ``` ### Additional considerations @@ -186,5 +216,7 @@ curl -v http://agdb-2.agdb.default.svc.cluster.local:3000/api/v1/status # should - Standard shutdown procedure via the endpoint will not work as K8s will simply restart the servers. - You should be able to adapt the above to other platforms such as AWS EKS. - Always connect to a particular node (e.g. `agdb-0`) rather than the service address since the latter will route the request to a random node. +- If you are using `agdb` only inside the K8s cluster with no visibility outside of it you may want to disable TLS. +- If you are exposing `agdb` outside the K8s cluster use real production certificate. In that case leave the `tls_root` configuration option empty. diff --git a/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-bare-metal.mdx b/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-bare-metal.mdx index a1d784cb..4f38e072 100644 --- a/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-bare-metal.mdx +++ b/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-bare-metal.mdx @@ -22,9 +22,14 @@ From the [official source](https://www.rust-lang.org/tools/install). ### Install `agdb_server` ```shell -cargo install agdb_server +cargo install agdb_server --features tls # the `tls` feature is optional and can be omitted if TLS support is not needed ``` + + On all platforms the feature `tls` requires `CMake`. On Windows it requires + additionally MSVC toolchain. If you do not need TLS simply omit the feature. + + You can also build the server manually. One advantage is that you can use a custom `pepper` value and bake it into the binary instead of using runtime configured value. The `pepper` file is located in sources as `agdb_server/pepper` and contains a random 16 character value that is used internally to additionally "season" the encrypted passwords. When building for production you should change this value to a different one and keep the pepper file as secret in case you needed to rebuild the server or build a new version. The steps for a manual build (use `bash` on Unix or `git bash` on Windows): @@ -34,7 +39,7 @@ git clone https://github.com/agnesoft/agdb.git cd agdb/ git checkout $(git describe --tags) # checkout the latest released version echo "1234567891234567" > agdb_server/pepper #use a different value, this value will be a secret -cargo build --release -p agdb_server +cargo build --release -p agdb_server --features tls # the `tls` feature is optional and can be omitted if TLS support is not needed mv target/release/agdb_server "" # Windows: target/release/agdb_server.exe ``` diff --git a/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-docker.mdx b/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-docker.mdx index f17e2c86..d04af4cc 100644 --- a/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-docker.mdx +++ b/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-docker.mdx @@ -41,7 +41,7 @@ docker build --pull -t agnesoft/agdb:dev -f agdb_server/containerfile . docker run -v agdb_data:/agdb/agdb_data --name agdb -p 3000:3000 agnesoft/agdb:dev ``` -This command runs the server using the default configuration (recommended). It assigns a volume to the data directory for data persistence, gives the container a name (`agdb`) and publishes the container's exposed port (`3000`) to the host. You can publish to a different local port (e.g. `5000:3000` (host:container)) where the container's port `3000` will be locally accessible on the port `5000`. +This command runs the server using the default configuration (without TLS). It assigns a volume to the data directory for data persistence, gives the container a name (`agdb`) and publishes the container's exposed port (`3000`) to the host. You can publish to a different local port (e.g. `5000:3000` (host:container)) where the container's port `3000` will be locally accessible on the port `5000`. ### Test that the server is up with `curl` @@ -59,4 +59,27 @@ token=$(curl -X POST -H 'Content-Type: application/json' localhost:3000/api/v1/u curl -X POST -H "Authorization: Bearer ${token}" localhost:3000/api/v1/admin/shutdown ``` +### TLS Support + +In order to enable TLS support you need to provide following config values as per the [server documentation](/docs/references/server): + +```yaml +tls_certificate: /agdb/cert.pem +tls_key: /agdb/cert.key +``` + +If you are using self-signed certificate you should provide also the root CA via: + +```yaml +tls_root: /agdb/root_ca.pem +``` + +You can then mount the custom config and the certificates into the container as volume(s). Assuming the `agdb_server.yaml` and the certificates are in `/local/path` on your host machine: + +```bash +docker run -v agdb_data:/agdb/agdb_data -v /local/path:/agdb --name agdb -p 3000:3000 agnesoft/agdb:dev +``` + +When you run the container this way it will load the configuration from `/agdb/agdb_server.yaml` and if it specifies the paths to the files they would be loaded as well. + diff --git a/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-k8s.mdx b/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-k8s.mdx index 06c411db..583090be 100644 --- a/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-k8s.mdx +++ b/agdb_web/pages/en-US/docs/guides/how-to-run-server/server-k8s.mdx @@ -49,7 +49,7 @@ spec: app: agdb ``` -### Secret +### Secrets Next document is the pepper secret `agdb-pepper`. @@ -64,9 +64,30 @@ stringData: pepper: "1234567891234567" ``` +Followed by the certificates. In production you should create the certificates using kubectl command from secure files rather than embedding them into the manifest. However, for demonstration purposes the following example is provided. It uses self-signed certificate with the corresponding CA. It needs to be provided as `base64` value. You can use e.g. `cat cert.pem | base64` bash command to get the correct value (end of lines do not matter). + + + The certificate must be issued (or used as an alternative name - SAN) for + the DNS name of the server used in its configuration `address` field. E.g. + `agdb.default.svc.cluster.local`. + + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: agdb-certs + labels: + app: agdb +data: + cert.pem: ... + key.pem: ... + root_ca.pem: ... +``` + ### ConfigMap -The configuration named `agdb-config` via the `ConfigMap` is optional as the default configuration would work just as well. It might however be useful if you needed to change anything regarding the server later. +The configuration named `agdb-config` via the `ConfigMap` is optional as the default configuration would work just as well. It might however be useful if you needed to change anything regarding the server later. For example disabling TLS if it is not needed etc. ```yaml --- @@ -85,6 +106,9 @@ data: log_level: INFO data_dir: /agdb/data pepper_path: /agdb/pepper/pepper + tls_certificate: /agdb/certs/cert.pem + tls_key: /agdb/certs/key.pem + tls_root: /agdb/certs/root_ca.pem cluster_token: cluster cluster_heartbeat_timeout_ms: 1000 cluster_term_timeout_ms: 3000 @@ -93,11 +117,11 @@ data: ### StatefulSet -The main part of the deployment is the stateful set definition. While replica set could work to some extent the instances of `agdb_server` are not interchangable and cannot be freely scaled horizontally. The stateful set type is therefore a better fit. It uses the selector and labels `app: agdb` in order to "link" the service and the underlying pod together. Kubernetes is using selectors rather than direct mapping when linking various things together such as services and pods. We specify 1 replica only (refer to the [agdb as K8s cluster](/docs/guides/how-to-run-server/cluster-k8s) for an alternative). +The main part of the deployment is the stateful set definition. While replica set could work to some extent the instances of `agdb_server` are not interchangeable and cannot be freely scaled horizontally. The stateful set type is therefore a better fit. It uses the selector and labels `app: agdb` in order to "link" the service and the underlying pod together. Kubernetes is using selectors rather than direct mapping when linking various things together such as services and pods. We specify 1 replica only (refer to the [agdb as K8s cluster](/docs/guides/how-to-run-server/cluster-k8s) for an alternative). The container spec matches the port on the service by name `agdb` and exposes the container port `3000` (default). The security context specifies the user `1000` (default uid in the container) and disables root access as it is not needed and enhances security. -Finally we specify volumes and volume mounts to add the `secret`, `configmap` and persistent volume claim (PVC) to the expected locations. The PVC is a way how data can survive restart or redeployment. By default 1 GB of storage is specified which can be increased (but not decreased) in subsequent deployments. +Finally, we specify volumes and volume mounts to add the `secrets`, `configmap` and persistent volume claim (PVC) to the expected locations. The PVC is a way how data can survive restart or redeployment. By default, 1 GB of storage is specified which can be increased (but not decreased) in subsequent deployments. Certificates are provided for TLS support. ```yaml --- @@ -136,6 +160,8 @@ spec: mountPath: /agdb - name: pepper mountPath: /agdb/pepper + - name: certs + mountPath: /agdb/certs volumes: - name: config configMap: @@ -144,6 +170,9 @@ spec: - name: pepper secret: secretName: agdb-pepper + - name: certs + secret: + secretName: agdb-certs volumeClaimTemplates: - metadata: name: agdb-data @@ -161,7 +190,7 @@ spec: The following command must be run from within the cluster unless the server was exposed via `LoadBalancer` or `Ingress`. The `.default.` bit is the name of the namespace where everything was deployed. ```bash -curl -v http://agdb.default.svc.cluster.local:3000/api/v1/status # should return 200 OK +curl -v https://agdb.default.svc.cluster.local:3000/api/v1/status # should return 200 OK ``` ### Additional considerations @@ -170,5 +199,7 @@ curl -v http://agdb.default.svc.cluster.local:3000/api/v1/status # should return - Standard shutdown procedure via the endpoint will not work as K8s will simply restart the server. - Consider running the [cluster](/docs/guides/how-to-run-server/cluster-k8s) rather than just a single node. - You should be able to adapt the above to other platforms such as AWS EKS. +- If you are using `agdb` only inside the K8s cluster with no visibility outside of it you may want to disable TLS. +- If you are exposing `agdb` outside the K8s cluster use real production certificate. In that case leave the `tls_root` configuration option empty. diff --git a/agdb_web/pages/en-US/docs/references/server.mdx b/agdb_web/pages/en-US/docs/references/server.mdx index 646a7b64..de580f7c 100644 --- a/agdb_web/pages/en-US/docs/references/server.mdx +++ b/agdb_web/pages/en-US/docs/references/server.mdx @@ -32,6 +32,9 @@ admin: admin # the admin user that will be created automatically for the server, data_dir: agdb_server_data # directory to store user data log_level: INFO # Options are: OFF, ERROR, WARN, INFO, DEBUG, TRACE pepper_path: "" # Optional path to a runtime secret file containing 16 bytes "pepper" value for additionally "seasoning" (hashing) passwords. If empty a built-in pepper value is used - see "How to run the server?" guide for details +tls_certificate: "" # path to the TLS certificate file +tls_key: "" # path to the TLS key file +tls_root: "" # path to the TLS root CA file cluster_token: cluster # token used between members of the cluster for authentication, treat this value as secret cluster_heartbeat_timeout_ms: 1000 # number of milliseconds since last message sent to a node in the cluster before the leader sends a heartbeat message cluster_term_timeout_ms: 3000 # number of milliseconds without receiving a message from the leader after which the nodes will consider leader to be off and begin a new term @@ -42,7 +45,22 @@ You can prepare it in advance in a file `agdb_server.yaml`. After the server dat The server is built with a default `pepper` (can be changed as [part of the build](/docs/guides/how-to-run-server/server-bare-metal) ) that is used if `pepper_path` is not specified. If unique it makes sure the two different `agdb_server` instances do not produce the same password hashes and enhances the security. The `pepper` value is to be considered a secret. - | +The TLS is turned on by specifying the `tls_certificate` and `tls_key` and internally uses (`rustls`)[https://github.com/rustls/rustls]. The `tls_root` is optional (can be empty) and needs to be specified only if you are using self-signed certificates. The certificate use in `tls_certificate` must be issued for the name (or one of alternative names) used in the `address` and `cluster` fields. For self-signed certificate and root CA (usable in docker compose or K8s deployments) you can use the following: + +```bash +cargo install rustls-cert-gen +rustls-cert-gen \ + --common-name=agdb \ + --ca-file-name=root_ca \ + --cert-file-name=cert \ + --country-name=CZ \ + --organization-name=Agnesoft \ + --san=localhost \ + --san=agdb0 \ + --san=agdb1 \ + --san=agdb2 \ + --output=. +``` ## Users diff --git a/examples/k8s/cluster.yaml b/examples/k8s/cluster.yaml index 8da45327..a6e825d2 100644 --- a/examples/k8s/cluster.yaml +++ b/examples/k8s/cluster.yaml @@ -22,6 +22,18 @@ metadata: stringData: pepper: "1234567891234567" +--- +apiVersion: v1 +kind: Secret +metadata: + name: agdb-certs + labels: + app: agdb +data: + cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlCOXpDQ0FaMmdBd0lCQWdJVVVHazA2ODBWa2d5TWVOcEFmamNqa3pNbHFKZ3dDZ1lJS29aSXpqMEVBd0l3DQpJREVMTUFrR0ExVUVCaE1DUTFveEVUQVBCZ05WQkFvTUNFRm5ibVZ6YjJaME1DQVhEVGMxTURFd01UQXdNREF3DQpNRm9ZRHpRd09UWXdNVEF4TURBd01EQXdXakFQTVEwd0N3WURWUVFEREFSaFoyUmlNRmt3RXdZSEtvWkl6ajBDDQpBUVlJS29aSXpqMERBUWNEUWdBRTRPNmJlQ3BtODNFNGM4emtCODVLblRIZlQ4azRubnRMamQxeE5QcTlLNlpnDQpwQ2NVamh1NHBJRGFpdFl6SlRiQ05BV2o1WmxiblNNK043TUE5Y2FzMUtPQnd6Q0J3REFmQmdOVkhTTUVHREFXDQpnQlJ6NENRYUJRNXliMEwrMXZXNGdYeUo0UUtHaHpDQml3WURWUjBSQklHRE1JR0FnZ2xzYjJOaGJHaHZjM1NDDQpKV0ZuWkdJdE1DNWhaMlJpTG1SbFptRjFiSFF1YzNaakxtTnNkWE4wWlhJdWJHOWpZV3lDSldGblpHSXRNUzVoDQpaMlJpTG1SbFptRjFiSFF1YzNaakxtTnNkWE4wWlhJdWJHOWpZV3lDSldGblpHSXRNaTVoWjJSaUxtUmxabUYxDQpiSFF1YzNaakxtTnNkWE4wWlhJdWJHOWpZV3d3RHdZRFZSMFBBUUgvQkFVREF3ZUFBREFLQmdncWhrak9QUVFEDQpBZ05JQURCRkFpRUEva2ZEZktRV20zckRHSUhPaForMGJOSklBYUgxQ3d5SUJ1TmRqYjNiZE5vQ0lHTU5XUXJpDQpDSE5jRTBmOTJyQ05OZ3FleFNSSG1XeVNxN2t5RnlhQXFxWSsNCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0NCg== + key.pem: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tDQpNSUdIQWdFQU1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhCRzB3YXdJQkFRUWdkVE5ORVpVODhTOU9aUkZxDQpwVmlLVmJGL05JZnQ2VGFJWEdvTzFuYS9iWnFoUkFOQ0FBVGc3cHQ0S21iemNUaHp6T1FIemtxZE1kOVB5VGllDQplMHVOM1hFMCtyMHJwbUNrSnhTT0c3aWtnTnFLMWpNbE5zSTBCYVBsbVZ1ZEl6NDNzd0QxeHF6VQ0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQ0K + root_ca.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlCaGpDQ0FTMmdBd0lCQWdJVWYxSk00d0pGZUF6aENjOGRkOFpCUkJNQ1hmSXdDZ1lJS29aSXpqMEVBd0l3DQpJREVMTUFrR0ExVUVCaE1DUTFveEVUQVBCZ05WQkFvTUNFRm5ibVZ6YjJaME1DQVhEVGMxTURFd01UQXdNREF3DQpNRm9ZRHpRd09UWXdNVEF4TURBd01EQXdXakFnTVFzd0NRWURWUVFHRXdKRFdqRVJNQThHQTFVRUNnd0lRV2R1DQpaWE52Wm5Rd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFSd1ZVZ2RFeVlqOGVhK0M4aUlrNU1qDQpPNGE3d0V4SS9rY0R3Si9Ob21VbFlHbEN3OWlqY0tZdGlsN2ZITTRjWkMvbVYvc3l6VFlhV3FyZ1pDVC9QU05lDQpvME13UVRBUEJnTlZIUThCQWY4RUJRTURCNFlBTUIwR0ExVWREZ1FXQkJSejRDUWFCUTV5YjBMKzF2VzRnWHlKDQo0UUtHaHpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUdIeFJkZHBPeEU3DQpHWnFHanFxYW82dU10djhtMHU3Uy8zZ3hwbkZ2ODhtMEFpQmpnU0llc2dIR2xnSHZHa0svYXVSblVKL2ZNd1c4DQowdThnTVlqQ1ZmRnlidz09DQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tDQo= + --- apiVersion: v1 kind: ConfigMap @@ -36,16 +48,19 @@ data: /usr/local/bin/agdb_server agdb_server.yaml: | bind: :::3000 - address: http://agdb-{id}.agdb.default.svc.cluster.local:3000 + address: https://agdb-{id}.agdb.default.svc.cluster.local:3000 basepath: "" admin: admin log_level: INFO data_dir: /agdb/data pepper_path: /agdb/pepper/pepper + tls_certificate: /agdb/certs/cert.pem + tls_key: /agdb/certs/key.pem + tls_root: /agdb/certs/root_ca.pem cluster_token: cluster cluster_heartbeat_timeout_ms: 1000 cluster_term_timeout_ms: 3000 - cluster: [http://agdb-0.agdb.default.svc.cluster.local:3000, http://agdb-1.agdb.default.svc.cluster.local:3000, http://agdb-2.agdb.default.svc.cluster.local:3000] + cluster: [https://agdb-0.agdb.default.svc.cluster.local:3000, https://agdb-1.agdb.default.svc.cluster.local:3000, https://agdb-2.agdb.default.svc.cluster.local:3000] --- apiVersion: apps/v1 @@ -89,6 +104,8 @@ spec: mountPath: /agdb/config - name: pepper mountPath: /agdb/pepper + - name: certs + mountPath: /agdb/certs volumes: - name: config configMap: @@ -97,6 +114,9 @@ spec: - name: pepper secret: secretName: agdb-pepper + - name: certs + secret: + secretName: agdb-certs volumeClaimTemplates: - metadata: name: agdb-data diff --git a/examples/k8s/server.yaml b/examples/k8s/server.yaml index 49dc84d6..a84067e9 100644 --- a/examples/k8s/server.yaml +++ b/examples/k8s/server.yaml @@ -22,6 +22,18 @@ metadata: stringData: pepper: "1234567891234567" +--- +apiVersion: v1 +kind: Secret +metadata: + name: agdb-certs + labels: + app: agdb +data: + cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlCbmpDQ0FVT2dBd0lCQWdJVVRSSGNnVHhkUzMyclBBSWVPM2dVM3YrRldOOHdDZ1lJS29aSXpqMEVBd0l3DQpJREVMTUFrR0ExVUVCaE1DUTFveEVUQVBCZ05WQkFvTUNFRm5ibVZ6YjJaME1DQVhEVGMxTURFd01UQXdNREF3DQpNRm9ZRHpRd09UWXdNVEF4TURBd01EQXdXakFQTVEwd0N3WURWUVFEREFSaFoyUmlNRmt3RXdZSEtvWkl6ajBDDQpBUVlJS29aSXpqMERBUWNEUWdBRWxxd2xkUzAyY1dBN09yeHJJaWpyTmNPSE13SHRtengzOE1yT09mVjh3Vkl6DQpHRXFiTEFrZzdhdTBVaXhJY3FCVllsTFp4Yk5lTGJmQjhsMWN0dlhGSDZOcU1HZ3dId1lEVlIwakJCZ3dGb0FVDQpwbldDa3hSSXk1Q2hDTHBMWGQ3ZENYUkRobHd3TkFZRFZSMFJCQzB3SzRJSmJHOWpZV3hvYjNOMGdoNWhaMlJpDQpMbVJsWm1GMWJIUXVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEd1lEVlIwUEFRSC9CQVVEQXdlQUFEQUtCZ2dxDQpoa2pPUFFRREFnTkpBREJHQWlFQWlKa2lrZWsvWm9CdWgxa2RweENRdjhEbnIwbzJmMmNkRDF3R3A0ZFNYdGdDDQpJUUNYMnZqRTF1RmJSTFFVYW1jTXZ1ZjF4N3Rtakh4a2N0b2gzWGZFc3ZmRDB3PT0NCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0NCg== + key.pem: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tDQpNSUdIQWdFQU1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhCRzB3YXdJQkFRUWdkSWk5RGpyUmFPbGJOc3Y0DQo3VHBPTzN1YUNUN1lsWjQraCt6SExmSEZiWGVoUkFOQ0FBU1dyQ1YxTFRaeFlEczZ2R3NpS09zMXc0Y3pBZTJiDQpQSGZ3eXM0NTlYekJVak1ZU3Bzc0NTRHRxN1JTTEVoeW9GVmlVdG5GczE0dHQ4SHlYVnkyOWNVZg0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQ0K + root_ca.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlCaHpDQ0FTMmdBd0lCQWdJVUpFMUZoOURIaGZUMjRtN1BYVUk3NDAycUxLMHdDZ1lJS29aSXpqMEVBd0l3DQpJREVMTUFrR0ExVUVCaE1DUTFveEVUQVBCZ05WQkFvTUNFRm5ibVZ6YjJaME1DQVhEVGMxTURFd01UQXdNREF3DQpNRm9ZRHpRd09UWXdNVEF4TURBd01EQXdXakFnTVFzd0NRWURWUVFHRXdKRFdqRVJNQThHQTFVRUNnd0lRV2R1DQpaWE52Wm5Rd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFUemFkZ3I4eGw5eVFGRnNLZ3VrZ1JxDQpKTzMrSm1CZnRuTEY0c3pMQkJtWTh1cGR5R0VXaFFmTWMvODFDRk9VRkZCL01ZbXJBME5hQllDd2dwN0VUT1lBDQpvME13UVRBUEJnTlZIUThCQWY4RUJRTURCNFlBTUIwR0ExVWREZ1FXQkJTbWRZS1RGRWpMa0tFSXVrdGQzdDBKDQpkRU9HWERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSVFDNkhlMnBJRUdTDQoxT3NtWXlwTmRsdDF4a2NNUE05T3ZodFFuNStLSTE0eEh3SWdVeW1iSE5xRHU2a1ZkMlc3aERKL3lkWUlFbUhiDQpidFBURlB1YnZhR1Y5R0U9DQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tDQo= + --- apiVersion: v1 kind: ConfigMap @@ -32,12 +44,15 @@ metadata: data: agdb_server.yaml: | bind: :::3000 - address: http://agdb.default.svc.cluster.local:3000 + address: https://agdb.default.svc.cluster.local:3000 basepath: "" admin: admin log_level: INFO data_dir: /agdb/data pepper_path: /agdb/pepper/pepper + tls_certificate: /agdb/certs/cert.pem + tls_key: /agdb/certs/key.pem + tls_root: /agdb/certs/root_ca.pem cluster_token: cluster cluster_heartbeat_timeout_ms: 1000 cluster_term_timeout_ms: 3000 @@ -79,6 +94,8 @@ spec: mountPath: /agdb - name: pepper mountPath: /agdb/pepper + - name: certs + mountPath: /agdb/certs volumes: - name: config configMap: @@ -87,6 +104,9 @@ spec: - name: pepper secret: secretName: agdb-pepper + - name: certs + secret: + secretName: agdb-certs volumeClaimTemplates: - metadata: name: agdb-data