Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
636 changes: 231 additions & 405 deletions Cargo.lock

Large diffs are not rendered by default.

15 changes: 7 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,18 @@ resolver = "3"
[workspace.package]
authors = ["Markus Schirp <mbj@schirp-dso.com>"]
edition = "2024"
rust-version = "1.98"
rust-version = "1.99"

[workspace.dependencies]
# Pinned to 1.129 as global `cargo update` resolves to 1.119 due to a
# crc/crc-fast version conflict. 1.119 pulls in lru 0.12.5, which has a
# security advisory (GHSA-2qph-qpvm-2qf7). 1.129 pulls in lru 0.16.3.
aws-sdk-s3 = { version = "1.133", default-features = false }
# Floor at 1.152: earlier releases resolve lru 0.16, which has a use-after-free
# advisory (RUSTSEC-2026-0253, patched in 0.18.2). 1.152 resolves lru 0.18.5.
aws-lc-rs = "1"
aws-sdk-s3 = { version = "1.152", default-features = false }
async-stream = "0.3"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.6", features = ["derive"] }
cmd-proc = { version = "0.6.2", path = "cmd-proc" }
comfy-table = "7"
dirs = "6"
env_logger = "0.11"
fluent-uri = { version = "0.4", features = ["net"] }
git-proc = { version = "0.6.0", path = "git-proc" }
Expand All @@ -47,12 +46,14 @@ ipnet = { version = "2", features = ["serde"] }
http = "1.4"
indoc = "2"
itertools = "0.14"
jiff = { version = "0.2", default-features = false, features = ["std"] }
log = "0.4"
typed-reqwest = { version = "0.2.0", path = "typed-reqwest" }
mmigration = { path = "mmigration" }
nom = "8"
nom-language = "0.1"
ociman = { version = "0.9.0", path = "ociman" }
pem-rfc7468 = { version = "0.7", features = ["alloc"] }
pg-client = { version = "0.6.1", path = "pg-client", features = ["sqlx"] }
pg-ephemeral = { version = "0.7.0", path = "pg-ephemeral" }
pretty_assertions = "1.4"
Expand All @@ -62,10 +63,8 @@ quote = "1"
rand = "0.10"
regex-lite = "0.1.9"
reqwest = { version = "0.13", features = ["json", "query", "rustls"], default-features = false }
rsa = { version = "0.9", features = ["pem"] }
rustix = { version = "1", features = ["fs", "process"] }
rustls = "0.23"
rustls-pemfile = "2"
semver = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["arbitrary_precision", "indexmap"] }
Expand Down
22 changes: 11 additions & 11 deletions clippy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
# resolved without waiting for upstream crates to update their dependencies.

allowed-duplicate-crates = [
# AWS SDK still uses http 0.2 internally, while reqwest/hyper use http 1.x
# Source: aws-smithy-* crates depend on http 0.2, our direct deps use http 1.x
# Tracking: https://github.com/awslabs/aws-sdk-rust/issues/977
"http",
"http-body",
# google-cloud-auth and google-cloud-wkt moved to base64 0.23, while
# reqwest, tonic, msqlx, serde_with, jsonwebtoken and stack-deploy's own
# dependency are all still on 0.22.
"base64",

# jiff declares an optional defmt dependency, which we never enable and so
# never compile. It resolves bitflags 1.3 into the graph regardless, and
# multiple_crate_versions reads the graph rather than the activated
# features. Everything actually built uses bitflags 2.x.
"bitflags",

# serde_with 3.x (transitive from google-cloud-sql-v1) depends on indexmap 1.9
# and schemars 0.9, while the rest of the workspace uses indexmap 2.x and schemars 1.x
Expand All @@ -24,7 +29,6 @@ allowed-duplicate-crates = [
# Source: sqlx-postgres -> rand 0.8, pg-ephemeral -> rand 0.10
"rand",
"rand_core",
"rand_chacha",

# Transitive from rand 0.8/0.10 split, plus ring (crypto) uses getrandom 0.2
# Source: rand 0.8 -> getrandom 0.2, rand 0.10 -> getrandom 0.4
Expand All @@ -34,12 +38,11 @@ allowed-duplicate-crates = [
"cpufeatures",

# sha2 0.11 (direct dep) vs 0.10 (transitive from aws-sdk, google-cloud, msqlx)
# Also causes duplicate digest, crypto-common, block-buffer, const-oid
# Also causes duplicate digest, crypto-common, block-buffer
"sha2",
"digest",
"crypto-common",
"block-buffer",
"const-oid",

# untrusted 0.7.1 vs 0.9.0. NOT ring (we compile no ring) — untrusted is a
# tiny zero-dependency, no-C, pure-Rust byte-input parser. aws-lc-rs's
Expand All @@ -53,9 +56,6 @@ allowed-duplicate-crates = [
# We use nom 8 while most of the ecosystem still depends on nom 7
"nom",

# libredox 0.1 requires redox_syscall 0.7, while other crates still use 0.5
"redox_syscall",

# getrandom 0.3 -> r-efi 5.x, getrandom 0.4 -> r-efi 6.x
"r-efi",

Expand Down
29 changes: 29 additions & 0 deletions cloud-sql-connector/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# Changelog

## 0.8.0

### Breaking Changes

- Malformed certificate PEM from the Cloud SQL Admin API now reports as the new
`Error::CertificatePem`, which carries the underlying PEM parse error as its
source, rather than as `Error::Io`. `Error::Io` is now only ever a genuine
I/O failure — opening the socket or the TLS handshake — so matching it to
detect a bad certificate no longer works. `Error::NoCertificatesInPem` is
unchanged and still reports PEM data containing no certificate section.
- `Error::Pkcs8` and `Error::Spki` are **removed**, replaced by
`Error::PrivateKeyEncode` and `Error::PublicKeyEncode`, which name the key
being encoded rather than the encoding standard. `Error::RsaKeyGeneration` no
longer carries a payload. None of the three carries a source error any more,
because the underlying key generation and encoding failures report no detail.

### Changed

- Raised minimum supported Rust version to 1.99.
- Dropped the `rustls-pemfile` dependency. Certificate PEM parsing now uses the
PEM support in `rustls-pki-types`, which was already in the dependency tree
via `rustls`.
- Dropped the `rsa` dependency. The ephemeral certificate key pair is now
generated by `aws-lc-rs`, already in the dependency tree via `rustls`, which
removes the pure-Rust bignum stack (`rsa`, `num-bigint-dig`, `pkcs1`,
`pkcs8`, `spki`, `der`) and generates the key in native code. The public key
is armored with `pem-rfc7468` for the API's PEM-encoded `public_key` field;
the key size is unchanged at RSA 2048.

## 0.7.0

### Fixed
Expand Down
6 changes: 3 additions & 3 deletions cloud-sql-connector/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cloud-sql-connector"
version = "0.7.0"
version = "0.8.0"
edition.workspace = true
authors.workspace = true
rust-version.workspace = true
Expand All @@ -15,15 +15,15 @@ categories = ["database"]
workspace = true

[dependencies]
aws-lc-rs.workspace = true
google-cloud-auth.workspace = true
google-cloud-gax.workspace = true
google-cloud-sql-v1.workspace = true
ipnet.workspace = true
log.workspace = true
nom.workspace = true
rsa.workspace = true
pem-rfc7468.workspace = true
rustls.workspace = true
rustls-pemfile.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["io-util", "net", "sync"] }
tokio-rustls.workspace = true
Expand Down
71 changes: 47 additions & 24 deletions cloud-sql-connector/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ use std::path::Path;
use std::sync::Arc;
use std::time::{Duration, SystemTime};

use aws_lc_rs::encoding::{AsDer as _, Pkcs8V1Der, PublicKeyX509Der};
use aws_lc_rs::rsa::{KeyPair, KeySize};
use aws_lc_rs::signature::KeyPair as _;
use google_cloud_auth::credentials::Credentials;
use google_cloud_sql_v1::client::SqlConnectService;
use google_cloud_sql_v1::model::{ConnectSettings, IpMapping, SqlIpAddressType, SslCert};
use rsa::RsaPrivateKey;
use rsa::pkcs8::EncodePrivateKey as _;
use rsa::pkcs8::EncodePublicKey as _;
use rustls::pki_types::{PrivateKeyDer, ServerName};
use tokio::io::copy_bidirectional;
use tokio::net::{TcpListener, TcpStream, UnixListener};
Expand All @@ -38,12 +38,22 @@ const CLOUD_SQL_PORT: u16 = 3307;
/// lapse.
const CERT_REFRESH_MARGIN: Duration = Duration::from_secs(4 * 60);

/// RSA key size in bits for ephemeral certificate requests.
const RSA_KEY_BITS: usize = 2048;
/// RSA key size for ephemeral certificate requests.
const RSA_KEY_SIZE: KeySize = KeySize::Rsa2048;

/// PEM label for an X.509 `SubjectPublicKeyInfo` document (RFC 7468 section 13).
const PEM_LABEL_PUBLIC_KEY: &str = "PUBLIC KEY";

/// Cloud SQL connector errors.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Certificate PEM data from the API is malformed.
#[error("failed to parse certificate from PEM data")]
CertificatePem {
/// The underlying PEM parse error.
#[source]
source: rustls::pki_types::pem::Error,
},
/// Cloud SQL API client builder error.
#[error(transparent)]
ClientBuilder(#[from] google_cloud_gax::client_builder::Error),
Expand Down Expand Up @@ -80,21 +90,21 @@ pub enum Error {
/// No primary IP address found for the instance.
#[error("no primary IP address found for Cloud SQL instance")]
NoPrimaryIp,
/// PKCS#8 encoding error.
#[error("failed to encode RSA key: {0}")]
Pkcs8(#[from] rsa::pkcs8::Error),
/// RSA private key could not be encoded as PKCS#8 DER.
#[error("failed to encode RSA private key")]
PrivateKeyEncode,
/// RSA public key could not be encoded as SubjectPublicKeyInfo PEM.
#[error("failed to encode RSA public key")]
PublicKeyEncode,
/// RSA key generation error.
#[error("failed to generate RSA key: {0}")]
RsaKeyGeneration(#[from] rsa::Error),
#[error("failed to generate RSA key")]
RsaKeyGeneration,
/// Server CA certificate PEM is empty.
#[error("server CA certificate PEM is empty")]
ServerCaCertEmpty,
/// Server CA certificate missing from API response.
#[error("server CA certificate missing from connectSettings response")]
ServerCaCertMissing,
/// SPKI encoding error.
#[error("failed to encode RSA public key: {0}")]
Spki(#[from] rsa::pkcs8::spki::Error),
/// TLS configuration error.
#[error("TLS configuration error: {0}")]
TlsConfig(#[from] rustls::Error),
Expand Down Expand Up @@ -147,8 +157,8 @@ struct ConnectInfoFetcher {
instance: String,
/// GCP project ID.
project: String,
/// RSA private key for ephemeral certificate requests.
rsa_private_key: RsaPrivateKey,
/// RSA key pair for ephemeral certificate requests.
rsa_key_pair: KeyPair,
}

impl ConnectInfoFetcher {
Expand Down Expand Up @@ -179,18 +189,31 @@ impl ConnectInfoFetcher {

/// Encode the RSA private key as PKCS#8 DER for TLS.
fn private_key_der(&self) -> Result<PrivateKeyDer<'static>, Error> {
let der = self.rsa_private_key.to_pkcs8_der()?;
let der: Pkcs8V1Der<'static> = self
.rsa_key_pair
.as_der()
.map_err(|_| Error::PrivateKeyEncode)?;

Ok(PrivateKeyDer::Pkcs8(
rustls::pki_types::PrivatePkcs8KeyDer::from(der.as_bytes().to_vec()),
rustls::pki_types::PrivatePkcs8KeyDer::from(der.as_ref().to_vec()),
))
}

/// Encode the RSA public key as PEM for the Cloud SQL Admin API.
/// Encode the RSA public key as `SubjectPublicKeyInfo` PEM, the form the
/// Cloud SQL Admin API's `public_key` field takes.
fn public_key_pem(&self) -> Result<String, Error> {
Ok(self
.rsa_private_key
.to_public_key()
.to_public_key_pem(rsa::pkcs8::LineEnding::LF)?)
let der: PublicKeyX509Der<'static> = self
.rsa_key_pair
.public_key()
.as_der()
.map_err(|_| Error::PublicKeyEncode)?;

pem_rfc7468::encode_string(
PEM_LABEL_PUBLIC_KEY,
pem_rfc7468::LineEnding::LF,
der.as_ref(),
)
.map_err(|_| Error::PublicKeyEncode)
}
}

Expand Down Expand Up @@ -322,13 +345,13 @@ impl Dialer {
instance: impl Into<String>,
client: SqlConnectService,
) -> Result<Self, Error> {
let rsa_private_key = RsaPrivateKey::new(&mut rsa::rand_core::OsRng, RSA_KEY_BITS)?;
let rsa_key_pair = KeyPair::generate(RSA_KEY_SIZE).map_err(|_| Error::RsaKeyGeneration)?;

let fetcher = ConnectInfoFetcher {
client,
instance: instance.into(),
project: project.into(),
rsa_private_key,
rsa_key_pair,
};

Ok(Self {
Expand Down
14 changes: 6 additions & 8 deletions cloud-sql-connector/src/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
//! certificate authenticates the server identity, so hostname verification
//! is skipped.

use std::io::BufReader;
use std::sync::Arc;
use std::time::{Duration, SystemTime};

use google_cloud_sql_v1::model::ConnectSettings;
use rustls::RootCertStore;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::pki_types::pem::{self, PemObject};
use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};

use crate::Error;
Expand Down Expand Up @@ -149,13 +149,11 @@ pub(crate) fn cert_not_after(cert: &CertificateDer<'_>) -> Result<SystemTime, Er
}

/// Parse a single certificate from PEM data.
pub(crate) fn parse_pem_cert(pem: &str) -> Result<CertificateDer<'static>, Error> {
let mut reader = BufReader::new(pem.as_bytes());

rustls_pemfile::certs(&mut reader)
.next()
.ok_or(Error::NoCertificatesInPem)?
.map_err(Error::from)
pub(crate) fn parse_pem_cert(input: &str) -> Result<CertificateDer<'static>, Error> {
CertificateDer::from_pem_slice(input.as_bytes()).map_err(|source| match source {
pem::Error::NoItemsFound => Error::NoCertificatesInPem,
source => Error::CertificatePem { source },
})
}

#[cfg(test)]
Expand Down
6 changes: 6 additions & 0 deletions cmd-proc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Changed

- Raised minimum supported Rust version to 1.99.

## 0.6.2

### Added
Expand Down
6 changes: 6 additions & 0 deletions git-proc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Changed

- Raised minimum supported Rust version to 1.99.

## 0.6.0

### Breaking Changes
Expand Down
2 changes: 1 addition & 1 deletion greenhell/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Changed

- Raised minimum supported Rust version to 1.95.
- Raised minimum supported Rust version to 1.99.

## 0.0.1

Expand Down
2 changes: 1 addition & 1 deletion mlambda/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Changed

- Raised minimum supported Rust version to 1.95.
- Raised minimum supported Rust version to 1.99.

## 0.0.1

Expand Down
2 changes: 1 addition & 1 deletion mmigration/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Changed

- Raised minimum supported Rust version to 1.95.
- Raised minimum supported Rust version to 1.99.

## 0.0.1

Expand Down
Loading
Loading