cli_shared/
client_config.rs1use std::net::{IpAddr, SocketAddr};
5
6use wire::AuthToken;
7
8#[derive(Debug, Clone)]
10pub struct ClientConfig {
11 pub client_id: String,
13 pub token: Option<AuthToken>,
15 pub auth_proof_key_pem: Option<String>,
17 pub authenticated_principal: Option<String>,
21 pub server_key: Option<String>,
24 pub tls_enabled: bool,
26 pub tls_domain_name: Option<String>,
28 pub tls_ca_certificate_pem: Option<String>,
30 pub tls_skip_verify: bool,
32 pub allow_insecure: bool,
38 pub timeout_secs: u64,
40 pub compression: bool,
42 pub chunk_size: usize,
44 pub chunked_transfer: bool,
46 pub resumable_transfer: bool,
48 pub pack_transfer: bool,
50 pub partial_fetch: bool,
52}
53
54impl ClientConfig {
55 pub fn new(client_id: impl Into<String>) -> Self {
57 Self {
58 client_id: client_id.into(),
59 token: None,
60 auth_proof_key_pem: None,
61 authenticated_principal: None,
62 server_key: None,
63 tls_enabled: false,
64 tls_domain_name: None,
65 tls_ca_certificate_pem: None,
66 tls_skip_verify: false,
67 allow_insecure: false,
68 timeout_secs: 30,
69 compression: true,
70 chunk_size: 64 * 1024,
71 chunked_transfer: true,
72 resumable_transfer: true,
73 pack_transfer: true,
74 partial_fetch: true,
75 }
76 }
77
78 pub fn with_token(mut self, token: AuthToken) -> Self {
80 self.token = Some(token);
81 self
82 }
83
84 pub fn with_auth_proof_key_pem(mut self, pem: impl Into<String>) -> Self {
86 self.auth_proof_key_pem = Some(pem.into());
87 self
88 }
89
90 pub fn with_authenticated_principal(mut self, principal: impl Into<String>) -> Self {
92 self.authenticated_principal = Some(principal.into());
93 self
94 }
95
96 pub fn with_server_key(mut self, key: impl Into<String>) -> Self {
98 self.server_key = Some(key.into());
99 self
100 }
101
102 pub fn with_tls(mut self, skip_verify: bool) -> Self {
104 self.tls_enabled = true;
105 self.tls_skip_verify = skip_verify;
106 self
107 }
108
109 pub fn with_tls_domain_name(mut self, domain_name: impl Into<String>) -> Self {
110 self.tls_domain_name = Some(domain_name.into());
111 self
112 }
113
114 pub fn with_tls_ca_certificate_pem(mut self, pem: impl Into<String>) -> Self {
115 self.tls_enabled = true;
116 self.tls_ca_certificate_pem = Some(pem.into());
117 self
118 }
119
120 pub fn with_timeout(mut self, secs: u64) -> Self {
122 self.timeout_secs = secs;
123 self
124 }
125
126 pub fn without_compression(mut self) -> Self {
128 self.compression = false;
129 self
130 }
131
132 pub fn with_chunk_size(mut self, size: usize) -> Self {
134 self.chunk_size = size;
135 self
136 }
137
138 pub fn with_chunked_transfer(mut self, enabled: bool) -> Self {
140 self.chunked_transfer = enabled;
141 self
142 }
143
144 pub fn with_resumable_transfer(mut self, enabled: bool) -> Self {
146 self.resumable_transfer = enabled;
147 self
148 }
149
150 pub fn with_pack_transfer(mut self, enabled: bool) -> Self {
152 self.pack_transfer = enabled;
153 self
154 }
155
156 pub fn with_partial_fetch(mut self, enabled: bool) -> Self {
158 self.partial_fetch = enabled;
159 self
160 }
161
162 pub fn with_allow_insecure(mut self, allow: bool) -> Self {
164 self.allow_insecure = allow;
165 self
166 }
167}
168
169impl Default for ClientConfig {
170 fn default() -> Self {
171 Self::new("heddle-client")
172 }
173}
174
175pub fn is_loopback_ip(ip: IpAddr) -> bool {
177 ip.is_loopback()
178}
179
180pub fn cleartext_connect_allowed(
186 addr: SocketAddr,
187 tls_enabled: bool,
188 allow_insecure: bool,
189) -> bool {
190 tls_enabled || is_loopback_ip(addr.ip()) || allow_insecure
191}
192
193pub fn cleartext_refused_message(addr: SocketAddr) -> String {
195 format!(
196 "refusing cleartext connection to non-loopback address {addr}; \
197enable TLS (remote.tls_enabled / HEDDLE_REMOTE_TLS) or pass --insecure \
198(or set remote.insecure=true / HEDDLE_REMOTE_INSECURE=1) for intentional cleartext \
199(e.g. VPN → VPS testing)"
200 )
201}
202
203#[cfg(test)]
204mod tests {
205 use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
206
207 use super::*;
208
209 #[test]
210 fn loopback_cleartext_allowed_without_insecure() {
211 let v4 = SocketAddr::from((Ipv4Addr::LOCALHOST, 8421));
212 let v6 = SocketAddr::from((Ipv6Addr::LOCALHOST, 8421));
213 assert!(cleartext_connect_allowed(v4, false, false));
214 assert!(cleartext_connect_allowed(v6, false, false));
215 }
216
217 #[test]
218 fn non_loopback_cleartext_requires_insecure() {
219 let addr = SocketAddr::from((Ipv4Addr::new(203, 0, 113, 10), 8421));
220 assert!(!cleartext_connect_allowed(addr, false, false));
221 assert!(cleartext_connect_allowed(addr, false, true));
222 assert!(cleartext_connect_allowed(addr, true, false));
223 }
224
225 #[test]
226 fn is_loopback_classifies_hosts() {
227 assert!(is_loopback_ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))));
228 assert!(is_loopback_ip(IpAddr::V6(Ipv6Addr::LOCALHOST)));
229 assert!(!is_loopback_ip(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))));
230 }
231}