99//! struct. Use [`super::vpn::build_openvpn_connection`] to convert it into
1010//! NetworkManager connection settings.
1111
12- use std:: path:: Path ;
12+ use std:: { fmt , path:: Path } ;
1313
1414use uuid:: Uuid ;
1515
16- use crate :: Passphrase ;
1716use crate :: api:: models:: {
1817 ConnectionError , OpenVpnAuthType , OpenVpnCompression , OpenVpnConfig , OpenVpnProxy , VpnRoute ,
19- vpn_route_from_parser,
18+ Redacted , redact_option , vpn_route_from_parser,
2019} ;
2120use crate :: core:: ovpn_parser:: parser:: { self , CertSource , OvpnFile } ;
2221use crate :: util:: cert_store:: store_inline_cert;
@@ -48,7 +47,6 @@ use crate::util::validation::validate_connection_name;
4847/// .expect("Failed to build OpenVPN config");
4948/// ```
5049#[ non_exhaustive]
51- #[ derive( Debug ) ]
5250pub struct OpenVpnBuilder {
5351 name : String ,
5452 remote : Option < String > ,
@@ -63,9 +61,9 @@ pub struct OpenVpnBuilder {
6361 ca_cert : Option < String > ,
6462 client_cert : Option < String > ,
6563 client_key : Option < String > ,
66- key_password : Option < Passphrase > ,
64+ key_password : Option < String > ,
6765 username : Option < String > ,
68- password : Option < Passphrase > ,
66+ password : Option < String > ,
6967 compression : Option < OpenVpnCompression > ,
7068 proxy : Option < OpenVpnProxy > ,
7169 tls_auth_key : Option < String > ,
@@ -90,6 +88,52 @@ pub struct OpenVpnBuilder {
9088 ncp_disable : bool ,
9189}
9290
91+ impl fmt:: Debug for OpenVpnBuilder {
92+ fn fmt ( & self , formatter : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
93+ formatter
94+ . debug_struct ( "OpenVpnBuilder" )
95+ . field ( "name" , & self . name )
96+ . field ( "remote" , & self . remote )
97+ . field ( "port" , & self . port )
98+ . field ( "tcp" , & self . tcp )
99+ . field ( "auth_type" , & self . auth_type )
100+ . field ( "auth" , & self . auth )
101+ . field ( "cipher" , & self . cipher )
102+ . field ( "dns" , & self . dns )
103+ . field ( "mtu" , & self . mtu )
104+ . field ( "uuid" , & self . uuid )
105+ . field ( "ca_cert" , & self . ca_cert )
106+ . field ( "client_cert" , & self . client_cert )
107+ . field ( "client_key" , & self . client_key )
108+ . field ( "key_password" , & redact_option ( & self . key_password ) )
109+ . field ( "username" , & self . username )
110+ . field ( "password" , & redact_option ( & self . password ) )
111+ . field ( "compression" , & self . compression )
112+ . field ( "proxy" , & self . proxy )
113+ . field ( "tls_auth_key" , & self . tls_auth_key )
114+ . field ( "tls_auth_direction" , & self . tls_auth_direction )
115+ . field ( "tls_crypt" , & self . tls_crypt )
116+ . field ( "tls_crypt_v2" , & self . tls_crypt_v2 )
117+ . field ( "tls_version_min" , & self . tls_version_min )
118+ . field ( "tls_version_max" , & self . tls_version_max )
119+ . field ( "tls_cipher" , & self . tls_cipher )
120+ . field ( "remote_cert_tls" , & self . remote_cert_tls )
121+ . field ( "verify_x509_name" , & self . verify_x509_name )
122+ . field ( "crl_verify" , & self . crl_verify )
123+ . field ( "redirect_gateway" , & self . redirect_gateway )
124+ . field ( "routes" , & self . routes )
125+ . field ( "ping" , & self . ping )
126+ . field ( "ping_exit" , & self . ping_exit )
127+ . field ( "ping_restart" , & self . ping_restart )
128+ . field ( "reneg_seconds" , & self . reneg_seconds )
129+ . field ( "connect_timeout" , & self . connect_timeout )
130+ . field ( "data_ciphers" , & self . data_ciphers )
131+ . field ( "data_ciphers_fallback" , & self . data_ciphers_fallback )
132+ . field ( "ncp_disable" , & self . ncp_disable )
133+ . finish ( )
134+ }
135+ }
136+
93137impl OpenVpnBuilder {
94138 /// Creates a new OpenVPN connection builder.
95139 #[ must_use]
@@ -390,7 +434,7 @@ impl OpenVpnBuilder {
390434
391435 /// Sets the password for an encrypted private key.
392436 #[ must_use]
393- pub fn key_password ( mut self , password : impl Into < Passphrase > ) -> Self {
437+ pub fn key_password ( mut self , password : impl Into < String > ) -> Self {
394438 self . key_password = Some ( password. into ( ) ) ;
395439 self
396440 }
@@ -404,7 +448,7 @@ impl OpenVpnBuilder {
404448
405449 /// Sets the password for password authentication.
406450 #[ must_use]
407- pub fn password ( mut self , password : impl Into < Passphrase > ) -> Self {
451+ pub fn password ( mut self , password : impl Into < String > ) -> Self {
408452 self . password = Some ( password. into ( ) ) ;
409453 self
410454 }
@@ -1187,4 +1231,32 @@ key /etc/openvpn/client.key
11871231
11881232 let _ = std:: fs:: remove_dir_all ( & dir) ;
11891233 }
1234+
1235+ #[ test]
1236+ fn debug_output_redacts_passwords ( ) {
1237+ let builder = OpenVpnBuilder :: new ( "vpn" )
1238+ . key_password ( "private-key-password" )
1239+ . password ( "vpn-password" )
1240+ . proxy ( OpenVpnProxy :: Http {
1241+ server : "proxy.example.com" . into ( ) ,
1242+ port : 8080 ,
1243+ username : Some ( "proxy-user" . into ( ) ) ,
1244+ password : Some ( "proxy-password" . into ( ) ) ,
1245+ retry : true ,
1246+ } ) ;
1247+
1248+ let output = format ! ( "{builder:?}" ) ;
1249+
1250+ assert ! ( output. contains( "[REDACTED]" ) ) ;
1251+ for secret in [
1252+ "private-key-password" ,
1253+ "vpn-password" ,
1254+ "proxy-password" ,
1255+ ] {
1256+ assert ! (
1257+ !output. contains( secret) ,
1258+ "debug output exposed secret {secret:?}: {output}"
1259+ ) ;
1260+ }
1261+ }
11901262}
0 commit comments