redis_test/
utils.rs

1use std::net::{SocketAddr, TcpListener};
2use std::path::PathBuf;
3use std::{fs, process};
4
5use socket2::{Domain, Socket, Type};
6use tempfile::TempDir;
7
8#[derive(Clone, Debug)]
9pub struct TlsFilePaths {
10    pub redis_crt: PathBuf,
11    pub redis_key: PathBuf,
12    pub ca_crt: PathBuf,
13}
14
15pub fn build_keys_and_certs_for_tls(tempdir: &TempDir) -> TlsFilePaths {
16    // Based on shell script in redis's server tests
17    // https://github.com/redis/redis/blob/8c291b97b95f2e011977b522acf77ead23e26f55/utils/gen-test-certs.sh
18    let ca_crt = tempdir.path().join("ca.crt");
19    let ca_key = tempdir.path().join("ca.key");
20    let ca_serial = tempdir.path().join("ca.txt");
21    let redis_crt = tempdir.path().join("redis.crt");
22    let redis_key = tempdir.path().join("redis.key");
23    let ext_file = tempdir.path().join("openssl.cnf");
24
25    fn make_key<S: AsRef<std::ffi::OsStr>>(name: S, size: usize) {
26        process::Command::new("openssl")
27            .arg("genrsa")
28            .arg("-out")
29            .arg(name)
30            .arg(format!("{size}"))
31            .stdout(process::Stdio::piped())
32            .stderr(process::Stdio::piped())
33            .spawn()
34            .expect("failed to spawn openssl")
35            .wait()
36            .expect("failed to create key");
37    }
38
39    // Build CA Key
40    make_key(&ca_key, 4096);
41
42    // Build redis key
43    make_key(&redis_key, 2048);
44
45    // Build CA Cert
46    process::Command::new("openssl")
47        .arg("req")
48        .arg("-x509")
49        .arg("-new")
50        .arg("-nodes")
51        .arg("-sha256")
52        .arg("-key")
53        .arg(&ca_key)
54        .arg("-days")
55        .arg("3650")
56        .arg("-subj")
57        .arg("/O=Redis Test/CN=Certificate Authority")
58        .arg("-out")
59        .arg(&ca_crt)
60        .stdout(process::Stdio::piped())
61        .stderr(process::Stdio::piped())
62        .spawn()
63        .expect("failed to spawn openssl")
64        .wait()
65        .expect("failed to create CA cert");
66
67    // Build x509v3 extensions file
68    fs::write(
69        &ext_file,
70        b"keyUsage = digitalSignature, keyEncipherment\n\
71    subjectAltName = @alt_names\n\
72    [alt_names]\n\
73    IP.1 = 127.0.0.1\n",
74    )
75    .expect("failed to create x509v3 extensions file");
76
77    // Read redis key
78    let mut key_cmd = process::Command::new("openssl")
79        .arg("req")
80        .arg("-new")
81        .arg("-sha256")
82        .arg("-subj")
83        .arg("/O=Redis Test/CN=Generic-cert")
84        .arg("-key")
85        .arg(&redis_key)
86        .stdout(process::Stdio::piped())
87        .stderr(process::Stdio::piped())
88        .spawn()
89        .expect("failed to spawn openssl");
90
91    // build redis cert
92    process::Command::new("openssl")
93        .arg("x509")
94        .arg("-req")
95        .arg("-sha256")
96        .arg("-CA")
97        .arg(&ca_crt)
98        .arg("-CAkey")
99        .arg(&ca_key)
100        .arg("-CAserial")
101        .arg(&ca_serial)
102        .arg("-CAcreateserial")
103        .arg("-days")
104        .arg("365")
105        .arg("-extfile")
106        .arg(&ext_file)
107        .arg("-out")
108        .arg(&redis_crt)
109        .stdin(key_cmd.stdout.take().expect("should have stdout"))
110        .stdout(process::Stdio::piped())
111        .stderr(process::Stdio::piped())
112        .spawn()
113        .expect("failed to spawn openssl")
114        .wait()
115        .expect("failed to create redis cert");
116
117    key_cmd.wait().expect("failed to create redis key");
118
119    TlsFilePaths {
120        redis_crt,
121        redis_key,
122        ca_crt,
123    }
124}
125
126pub fn get_listener_on_free_port() -> TcpListener {
127    let addr = &"127.0.0.1:0".parse::<SocketAddr>().unwrap().into();
128    let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
129    socket.set_reuse_address(true).unwrap();
130    socket.bind(addr).unwrap();
131    socket.listen(1).unwrap();
132    TcpListener::from(socket)
133}
134
135/// Finds a random open port available for listening at, by spawning a TCP server with
136/// port "zero" (which prompts the OS to just use any available port). Between calling
137/// this function and trying to bind to this port, the port may be given to another
138/// process, so this must be used with care (since here we only use it for tests, it's
139/// mostly okay).
140pub fn get_random_available_port() -> u16 {
141    for _ in 0..10000 {
142        let listener = get_listener_on_free_port();
143        let port = listener.local_addr().unwrap().port();
144        if port < 55535 {
145            return port;
146        }
147    }
148    panic!("Couldn't get a valid port");
149}