From 50841ab868a0816503587a5ccd9e6b2e5a9fa96f Mon Sep 17 00:00:00 2001 From: Dmitrii Kovalkov Date: Fri, 11 Apr 2025 15:22:12 +0200 Subject: [PATCH] User rcgen in neon_local --- Cargo.lock | 92 ++++++++++++++++++++++++++++ Cargo.toml | 2 +- control_plane/Cargo.toml | 3 + control_plane/src/local_env.rs | 107 ++++++++++++++------------------- 4 files changed, 141 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aea8924f4f..316d937c7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,6 +167,45 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -1431,8 +1470,10 @@ dependencies = [ "pageserver_client", "postgres_backend", "postgres_connection", + "rcgen", "regex", "reqwest", + "rustls-pki-types", "safekeeper_api", "scopeguard", "serde", @@ -1774,6 +1815,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "der_derive" version = "0.7.3" @@ -4018,6 +4073,15 @@ dependencies = [ "workspace_hack", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.20.2" @@ -5383,6 +5447,7 @@ dependencies = [ "ring", "rustls-pki-types", "time", + "x509-parser", "yasna", ] @@ -5813,6 +5878,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.41" @@ -8547,6 +8621,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "xattr" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index d957fa9070..9fa75053fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -270,7 +270,7 @@ workspace_hack = { version = "0.1", path = "./workspace_hack/" } ## Build dependencies criterion = "0.5.1" -rcgen = "0.13" +rcgen = { version = "0.13", features = ["x509-parser"] } rstest = "0.18" camino-tempfile = "1.0.2" tonic-build = "0.12" diff --git a/control_plane/Cargo.toml b/control_plane/Cargo.toml index 162c49ec7c..974f8c88b1 100644 --- a/control_plane/Cargo.toml +++ b/control_plane/Cargo.toml @@ -15,6 +15,7 @@ nix.workspace = true once_cell.workspace = true humantime-serde.workspace = true hyper0.workspace = true +rcgen.workspace = true regex.workspace = true reqwest = { workspace = true, features = ["blocking", "json"] } scopeguard.workspace = true @@ -36,6 +37,8 @@ storage_broker.workspace = true http-utils.workspace = true utils.workspace = true whoami.workspace = true +rustls-pki-types.workspace = true + compute_api.workspace = true workspace_hack.workspace = true diff --git a/control_plane/src/local_env.rs b/control_plane/src/local_env.rs index 8e2a110366..f73af12d22 100644 --- a/control_plane/src/local_env.rs +++ b/control_plane/src/local_env.rs @@ -975,25 +975,26 @@ fn generate_auth_keys(private_key_path: &Path, public_key_path: &Path) -> anyhow Ok(()) } -fn generate_ssl_ca_cert(cert_path: &Path, key_path: &Path) -> anyhow::Result<()> { - // openssl req -x509 -newkey rsa:2048 -nodes -subj "/CN=Neon Local CA" -days 36500 \ - // -out rootCA.crt -keyout rootCA.key - let keygen_output = Command::new("openssl") - .args([ - "req", "-x509", "-newkey", "rsa:2048", "-nodes", "-days", "36500", - ]) - .args(["-subj", "/CN=Neon Local CA"]) - .args(["-out", cert_path.to_str().unwrap()]) - .args(["-keyout", key_path.to_str().unwrap()]) - .output() - .context("failed to generate CA certificate")?; - if !keygen_output.status.success() { - bail!( - "openssl failed: '{}'", - String::from_utf8_lossy(&keygen_output.stderr) - ); - } - Ok(()) +fn generate_ssl_ca_cert( + cert_path: &Path, + key_path: &Path, +) -> anyhow::Result<(rcgen::Certificate, rcgen::KeyPair)> { + let ca_key = rcgen::KeyPair::generate()?; + let ca_cert = { + let mut params = rcgen::CertificateParams::default(); + params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); + params.distinguished_name = rcgen::DistinguishedName::new(); + params + .distinguished_name + .push(rcgen::DnType::CommonName, "Neon Local CA"); + + params.self_signed(&ca_key)? + }; + + fs::write(cert_path, ca_cert.pem())?; + fs::write(key_path, ca_key.serialize_pem())?; + + Ok((ca_cert, ca_key)) } fn generate_ssl_cert( @@ -1002,52 +1003,34 @@ fn generate_ssl_cert( ca_cert_path: &Path, ca_key_path: &Path, ) -> anyhow::Result<()> { - // Generate Certificate Signing Request (CSR). - let mut csr_path = cert_path.to_path_buf(); - csr_path.set_extension(".csr"); + use rcgen::{CertificateParams, KeyPair, SanType}; - // openssl req -new -nodes -newkey rsa:2048 -keyout server.key -out server.csr \ - // -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" - let keygen_output = Command::new("openssl") - .args(["req", "-new", "-nodes"]) - .args(["-newkey", "rsa:2048"]) - .args(["-subj", "/CN=localhost"]) - .args(["-addext", "subjectAltName=DNS:localhost,IP:127.0.0.1"]) - .args(["-keyout", key_path.to_str().unwrap()]) - .args(["-out", csr_path.to_str().unwrap()]) - .output() - .context("failed to generate CSR")?; - if !keygen_output.status.success() { - bail!( - "openssl failed: '{}'", - String::from_utf8_lossy(&keygen_output.stderr) - ); - } + let ca_key_pem = fs::read_to_string(ca_key_path)?; + let ca_key = KeyPair::from_pem(&ca_key_pem)?; - // Sign CSR with CA key. - // - // openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial \ - // -out server.crt -days 36500 -copy_extensions copyall - let keygen_output = Command::new("openssl") - .args(["x509", "-req"]) - .args(["-in", csr_path.to_str().unwrap()]) - .args(["-CA", ca_cert_path.to_str().unwrap()]) - .args(["-CAkey", ca_key_path.to_str().unwrap()]) - .arg("-CAcreateserial") - .args(["-out", cert_path.to_str().unwrap()]) - .args(["-days", "36500"]) - .args(["-copy_extensions", "copyall"]) - .output() - .context("failed to sign CSR")?; - if !keygen_output.status.success() { - bail!( - "openssl failed: '{}'", - String::from_utf8_lossy(&keygen_output.stderr) - ); - } + let ca_cert_pem = fs::read_to_string(ca_cert_path)?; + let ca_cert_params = CertificateParams::from_ca_cert_pem(&ca_cert_pem)?; + let ca_cert = ca_cert_params.self_signed(&ca_key)?; - // Remove CSR file as it's not needed anymore. - fs::remove_file(csr_path)?; + let key = KeyPair::generate()?; + let cert = { + let mut params = CertificateParams::default(); + params.subject_alt_names = vec![ + SanType::DnsName("localhost".try_into().unwrap()), + SanType::IpAddress("127.0.0.1".parse().unwrap()), + ]; + params.distinguished_name = rcgen::DistinguishedName::new(); + params + .distinguished_name + .push(rcgen::DnType::CommonName, "Neon Local Cert"); + + params + .signed_by(&key, &ca_cert, &ca_key) + .context("failed to sign certificate")? + }; + + fs::write(cert_path, cert.pem())?; + fs::write(key_path, key.serialize_pem())?; Ok(()) }