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 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 make_key(&ca_key, 4096);
41
42 make_key(&redis_key, 2048);
44
45 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 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 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 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
135pub 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}