parse redis ssl_context

This commit is contained in:
Robin Appelman 2025-04-29 18:13:10 +02:00
commit e4bb72fd00
6 changed files with 220 additions and 14 deletions

119
Cargo.lock generated
View file

@ -165,6 +165,22 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "core-foundation"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.17" version = "0.2.17"
@ -910,6 +926,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]] [[package]]
name = "owo-colors" name = "owo-colors"
version = "4.1.0" version = "4.1.0"
@ -1112,6 +1134,8 @@ dependencies = [
"itoa", "itoa",
"num-bigint", "num-bigint",
"percent-encoding", "percent-encoding",
"rustls",
"rustls-native-certs",
"ryu", "ryu",
"sha1_smol", "sha1_smol",
"socket2", "socket2",
@ -1156,6 +1180,20 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.16",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "rsa" name = "rsa"
version = "0.9.8" version = "0.9.8"
@ -1208,18 +1246,93 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "rustls"
version = "0.23.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pki-types"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
[[package]]
name = "rustls-webpki"
version = "0.103.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.18" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "schannel"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.217" version = "1.0.217"
@ -1803,6 +1916,12 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.4" version = "2.5.4"

View file

@ -19,4 +19,4 @@ form_urlencoded = "1.2.1"
[dev-dependencies] [dev-dependencies]
miette = { version = "7.4.0", features = ["fancy"] } miette = { version = "7.4.0", features = ["fancy"] }
sqlx = { version = "0.8.3", default-features = false, features = ["any", "mysql", "sqlite", "postgres"] } sqlx = { version = "0.8.3", default-features = false, features = ["any", "mysql", "sqlite", "postgres"] }
redis = "0.30.0" redis = { version = "0.30.0", features = ["tls-rustls", "tls-rustls-insecure"] }

View file

@ -31,14 +31,41 @@ pub enum RedisConnectionAddr {
TcpTls { TcpTls {
host: String, host: String,
port: u16, port: u16,
insecure: bool, tls_params: Option<RedisTlsParams>,
tls_params: Option<String>,
}, },
Unix { Unix {
path: PathBuf, path: PathBuf,
}, },
} }
impl RedisConnectionAddr {
pub fn with_tls(self, tls_params: RedisTlsParams) -> Self {
match self {
RedisConnectionAddr::Tcp { host, port }
| RedisConnectionAddr::TcpTls { host, port, .. } => RedisConnectionAddr::TcpTls {
host,
port,
tls_params: Some(tls_params),
},
unix => unix,
}
}
pub fn with_tls_opt(self, tls_params: Option<RedisTlsParams>) -> Self {
if let Some(params) = tls_params {
self.with_tls(params)
} else {
match self {
RedisConnectionAddr::Tcp { host, port }
| RedisConnectionAddr::TcpTls { host, port, .. } => {
RedisConnectionAddr::Tcp { host, port }
}
unix => unix,
}
}
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RedisConnectionInfo { pub struct RedisConnectionInfo {
pub addr: RedisConnectionAddr, pub addr: RedisConnectionAddr,
@ -47,6 +74,15 @@ pub struct RedisConnectionInfo {
pub password: Option<String>, pub password: Option<String>,
} }
#[derive(Clone, Debug)]
pub struct RedisTlsParams {
pub local_cert: Option<PathBuf>,
pub local_pk: Option<PathBuf>,
pub ca_file: Option<PathBuf>,
pub accept_invalid_hostname: bool,
pub insecure: bool,
}
impl RedisConfig { impl RedisConfig {
pub fn addr(&self) -> impl Iterator<Item = &RedisConnectionAddr> { pub fn addr(&self) -> impl Iterator<Item = &RedisConnectionAddr> {
let boxed: Box<dyn Iterator<Item = &RedisConnectionAddr>> = match self { let boxed: Box<dyn Iterator<Item = &RedisConnectionAddr>> = match self {

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
Config, Database, DbConnect, DbError, Error, NotAConfigError, PhpParseError, Config, Database, DbConnect, DbError, Error, NotAConfigError, PhpParseError,
RedisConnectionInfo, Result, SslOptions, RedisConnectionInfo, RedisTlsParams, Result, SslOptions,
}; };
use crate::{RedisConfig, RedisConnectionAddr}; use crate::{RedisConfig, RedisConnectionAddr};
use php_literal_parser::Value; use php_literal_parser::Value;
@ -337,6 +337,20 @@ fn parse_redis_options(parsed: &Value) -> RedisConfig {
(redis_options, address) (redis_options, address)
}; };
let tls_params = if redis_options["ssl_context"].is_array() {
let ssl_options = &redis_options["ssl_context"];
Some(RedisTlsParams {
local_cert: ssl_options["local_cert"].as_str().map(From::from),
local_pk: ssl_options["local_pk"].as_str().map(From::from),
ca_file: ssl_options["cafile"].as_str().map(From::from),
accept_invalid_hostname: ssl_options["allow_self_signed"] == true
|| ssl_options["verify_peer_name"] == false,
insecure: ssl_options["verify_peer "] == false,
})
} else {
None
};
let db = redis_options["dbindex"].clone().into_int().unwrap_or(0); let db = redis_options["dbindex"].clone().into_int().unwrap_or(0);
let password = redis_options["password"] let password = redis_options["password"]
.as_str() .as_str()
@ -349,7 +363,7 @@ fn parse_redis_options(parsed: &Value) -> RedisConfig {
match address { match address {
RedisAddress::Single(addr) => RedisConfig::Single(RedisConnectionInfo { RedisAddress::Single(addr) => RedisConfig::Single(RedisConnectionInfo {
addr, addr: addr.with_tls_opt(tls_params),
db, db,
username, username,
password, password,
@ -358,7 +372,7 @@ fn parse_redis_options(parsed: &Value) -> RedisConfig {
addresses addresses
.into_iter() .into_iter()
.map(|addr| RedisConnectionInfo { .map(|addr| RedisConnectionInfo {
addr, addr: addr.with_tls_opt(tls_params.clone()),
db, db,
username: username.clone(), username: username.clone(),
password: password.clone(), password: password.clone(),

View file

@ -1,6 +1,6 @@
use nextcloud_config_parser::{ use nextcloud_config_parser::{
parse, parse_glob, Config, Database, DbConnect, RedisConfig, RedisConnectionAddr, parse, parse_glob, Config, Database, DbConnect, RedisConfig, RedisConnectionAddr,
RedisConnectionInfo, SslOptions, RedisConnectionInfo, RedisTlsParams, SslOptions,
}; };
use std::fmt::Debug; use std::fmt::Debug;
@ -25,15 +25,9 @@ fn parse_redis(cfg: &str) -> RedisConnectionInfo {
let redis = ConnectionInfo::from_str(cfg).unwrap(); let redis = ConnectionInfo::from_str(cfg).unwrap();
let addr = match redis.addr { let addr = match redis.addr {
ConnectionAddr::Tcp(host, port) => RedisConnectionAddr::Tcp { host, port }, ConnectionAddr::Tcp(host, port) => RedisConnectionAddr::Tcp { host, port },
ConnectionAddr::TcpTls { ConnectionAddr::TcpTls { host, port, .. } => RedisConnectionAddr::TcpTls {
host, host,
port, port,
insecure,
..
} => RedisConnectionAddr::TcpTls {
host,
port,
insecure,
tls_params: None, tls_params: None,
}, },
ConnectionAddr::Unix(path) => RedisConnectionAddr::Unix { path }, ConnectionAddr::Unix(path) => RedisConnectionAddr::Unix { path },
@ -109,6 +103,30 @@ fn test_parse_redis_socket() {
); );
} }
#[test]
fn test_parse_redis_tls() {
let config = config_from_file("tests/configs/redis_tls.php");
assert_debug_equal(
RedisConfig::Single(RedisConnectionInfo {
addr: RedisConnectionAddr::TcpTls {
host: "127.0.0.1".into(),
port: 6379,
tls_params: Some(RedisTlsParams {
local_cert: Some("/certs/redis.crt".into()),
local_pk: Some("/certs/redis.key".into()),
ca_file: Some("/certs/ca.crt".into()),
insecure: false,
accept_invalid_hostname: false,
}),
},
db: 0,
username: None,
password: None,
}),
config.redis,
);
}
#[test] #[test]
fn test_parse_comment_whitespace() { fn test_parse_comment_whitespace() {
let config = config_from_file("tests/configs/comment_whitespace.php"); let config = config_from_file("tests/configs/comment_whitespace.php");

View file

@ -0,0 +1,19 @@
<?php
$CONFIG = [
'overwrite.cli.url' => 'https://cloud.example.com',
'dbtype' => 'mysql',
'dbname' => 'nextcloud',
'dbhost' => 'localhost',
'dbport' => '',
'dbuser' => 'nextcloud',
'dbpassword' => 'secret',
'redis' => [
'host' => 'localhost',
'ssl_context' => [
'local_cert' => '/certs/redis.crt',
'local_pk' => '/certs/redis.key',
'cafile' => '/certs/ca.crt'
]
]
];