@@ -16,6 +16,10 @@ use tokio_util::io::StreamReader;
16
16
use fliptevaluation:: error:: Error ;
17
17
use fliptevaluation:: models:: source;
18
18
19
+ use crate :: TlsConfig ;
20
+ use base64:: prelude:: BASE64_STANDARD ;
21
+ use base64:: Engine as Base64Engine ;
22
+
19
23
#[ derive( Debug , Clone , Default , Deserialize ) ]
20
24
#[ cfg_attr( test, derive( PartialEq ) ) ]
21
25
#[ serde( rename_all = "snake_case" ) ]
@@ -111,6 +115,7 @@ pub struct HTTPFetcherBuilder {
111
115
request_timeout : Option < Duration > ,
112
116
update_interval : Duration ,
113
117
mode : FetchMode ,
118
+ tls_config : Option < TlsConfig > ,
114
119
}
115
120
116
121
#[ derive( Deserialize ) ]
@@ -136,6 +141,7 @@ impl HTTPFetcherBuilder {
136
141
request_timeout : None ,
137
142
update_interval : Duration :: from_secs ( 120 ) ,
138
143
mode : FetchMode :: default ( ) ,
144
+ tls_config : None ,
139
145
}
140
146
}
141
147
@@ -174,6 +180,11 @@ impl HTTPFetcherBuilder {
174
180
self
175
181
}
176
182
183
+ pub fn tls_config ( mut self , tls_config : TlsConfig ) -> Self {
184
+ self . tls_config = Some ( tls_config) ;
185
+ self
186
+ }
187
+
177
188
pub fn build ( self ) -> Result < HTTPFetcher , Error > {
178
189
let retry_policy = ExponentialBackoff :: builder ( )
179
190
. retry_bounds ( Duration :: from_secs ( 1 ) , Duration :: from_secs ( 30 ) )
@@ -205,6 +216,11 @@ impl HTTPFetcherBuilder {
205
216
}
206
217
}
207
218
219
+ // Apply TLS configuration if provided
220
+ if let Some ( tls_config) = & self . tls_config {
221
+ client_builder = configure_tls ( client_builder, tls_config) ?;
222
+ }
223
+
208
224
let client = client_builder
209
225
. build ( )
210
226
. map_err ( |e| Error :: Internal ( format ! ( "failed to create client: {e}" ) ) ) ?;
@@ -476,6 +492,65 @@ impl HTTPFetcher {
476
492
}
477
493
}
478
494
495
+ fn configure_tls (
496
+ mut builder : reqwest:: ClientBuilder ,
497
+ tls_config : & TlsConfig ,
498
+ ) -> Result < reqwest:: ClientBuilder , Error > {
499
+ // Handle insecure mode
500
+ if tls_config. insecure_skip_verify . unwrap_or ( false ) {
501
+ builder = builder. danger_accept_invalid_certs ( true ) ;
502
+ }
503
+
504
+ // Handle custom CA certificates
505
+ if let Some ( ca_cert_data) = & tls_config. ca_cert_data {
506
+ let cert_bytes = BASE64_STANDARD
507
+ . decode ( ca_cert_data)
508
+ . map_err ( |e| Error :: Internal ( format ! ( "Invalid CA cert data: {e}" ) ) ) ?;
509
+ let cert = reqwest:: Certificate :: from_pem ( & cert_bytes)
510
+ . map_err ( |e| Error :: Internal ( format ! ( "Invalid CA certificate: {e}" ) ) ) ?;
511
+ builder = builder. add_root_certificate ( cert) ;
512
+ } else if let Some ( ca_cert_file) = & tls_config. ca_cert_file {
513
+ let cert_bytes = std:: fs:: read ( ca_cert_file)
514
+ . map_err ( |e| Error :: Internal ( format ! ( "Failed to read CA cert file: {e}" ) ) ) ?;
515
+ let cert = reqwest:: Certificate :: from_pem ( & cert_bytes)
516
+ . map_err ( |e| Error :: Internal ( format ! ( "Invalid CA certificate file: {e}" ) ) ) ?;
517
+ builder = builder. add_root_certificate ( cert) ;
518
+ }
519
+
520
+ // Handle client certificates for mutual TLS
521
+ if let ( Some ( cert_data) , Some ( key_data) ) = (
522
+ & tls_config. client_cert_data ,
523
+ & tls_config. client_key_data ,
524
+ ) {
525
+ let cert_bytes = BASE64_STANDARD
526
+ . decode ( cert_data)
527
+ . map_err ( |e| Error :: Internal ( format ! ( "Invalid client cert data: {e}" ) ) ) ?;
528
+ let key_bytes = BASE64_STANDARD
529
+ . decode ( key_data)
530
+ . map_err ( |e| Error :: Internal ( format ! ( "Invalid client key data: {e}" ) ) ) ?;
531
+ let mut combined = cert_bytes. clone ( ) ;
532
+ combined. extend_from_slice ( & key_bytes) ;
533
+ let identity = reqwest:: Identity :: from_pem ( & combined)
534
+ . map_err ( |e| Error :: Internal ( format ! ( "Invalid client certificate: {e}" ) ) ) ?;
535
+ builder = builder. identity ( identity) ;
536
+ } else if let ( Some ( cert_file) , Some ( key_file) ) = (
537
+ & tls_config. client_cert_file ,
538
+ & tls_config. client_key_file ,
539
+ ) {
540
+ let cert_bytes = std:: fs:: read ( cert_file)
541
+ . map_err ( |e| Error :: Internal ( format ! ( "Failed to read client cert file: {e}" ) ) ) ?;
542
+ let key_bytes = std:: fs:: read ( key_file)
543
+ . map_err ( |e| Error :: Internal ( format ! ( "Failed to read client key file: {e}" ) ) ) ?;
544
+ let mut combined = cert_bytes. clone ( ) ;
545
+ combined. extend_from_slice ( & key_bytes) ;
546
+ let identity = reqwest:: Identity :: from_pem ( & combined)
547
+ . map_err ( |e| Error :: Internal ( format ! ( "Invalid client certificate files: {e}" ) ) ) ?;
548
+ builder = builder. identity ( identity) ;
549
+ }
550
+
551
+ Ok ( builder)
552
+ }
553
+
479
554
#[ cfg( test) ]
480
555
mod tests {
481
556
use futures:: FutureExt ;
@@ -484,6 +559,10 @@ mod tests {
484
559
use crate :: http:: Authentication ;
485
560
use crate :: http:: FetchMode ;
486
561
use crate :: http:: HTTPFetcherBuilder ;
562
+ use crate :: http:: configure_tls;
563
+ use crate :: TlsConfig ;
564
+ use base64:: prelude:: BASE64_STANDARD ;
565
+ use base64:: Engine as Base64Engine ;
487
566
use tokio:: sync:: mpsc;
488
567
489
568
#[ tokio:: test]
@@ -774,4 +853,171 @@ mod tests {
774
853
775
854
assert_eq ! ( unwrapped_string, Authentication :: JwtToken ( "secret" . into( ) ) ) ;
776
855
}
856
+
857
+ #[ test]
858
+ fn test_tls_config_insecure_skip_verify ( ) {
859
+ let tls_config = TlsConfig {
860
+ insecure_skip_verify : Some ( true ) ,
861
+ ca_cert_file : None ,
862
+ ca_cert_data : None ,
863
+ client_cert_file : None ,
864
+ client_key_file : None ,
865
+ client_cert_data : None ,
866
+ client_key_data : None ,
867
+ } ;
868
+
869
+ let builder = reqwest:: Client :: builder ( ) ;
870
+ let result = configure_tls ( builder, & tls_config) ;
871
+ assert ! ( result. is_ok( ) ) ;
872
+ }
873
+
874
+ #[ test]
875
+ fn test_tls_config_custom_ca_cert_data ( ) {
876
+ // Use the existing localhost.crt for testing
877
+ let cert_pem = include_str ! ( "testdata/localhost.crt" ) ;
878
+ let cert_b64 = BASE64_STANDARD . encode ( cert_pem) ;
879
+
880
+ let tls_config = TlsConfig {
881
+ ca_cert_data : Some ( cert_b64) ,
882
+ insecure_skip_verify : None ,
883
+ ca_cert_file : None ,
884
+ client_cert_file : None ,
885
+ client_key_file : None ,
886
+ client_cert_data : None ,
887
+ client_key_data : None ,
888
+ } ;
889
+
890
+ let builder = reqwest:: Client :: builder ( ) ;
891
+ let result = configure_tls ( builder, & tls_config) ;
892
+ assert ! ( result. is_ok( ) ) ;
893
+ }
894
+
895
+ #[ test]
896
+ fn test_tls_config_custom_ca_cert_file ( ) {
897
+ let tls_config = TlsConfig {
898
+ ca_cert_file : Some ( "src/testdata/localhost.crt" . to_string ( ) ) ,
899
+ insecure_skip_verify : None ,
900
+ ca_cert_data : None ,
901
+ client_cert_file : None ,
902
+ client_key_file : None ,
903
+ client_cert_data : None ,
904
+ client_key_data : None ,
905
+ } ;
906
+
907
+ let builder = reqwest:: Client :: builder ( ) ;
908
+ let result = configure_tls ( builder, & tls_config) ;
909
+ assert ! ( result. is_ok( ) ) ;
910
+ }
911
+
912
+ #[ test]
913
+ fn test_tls_config_client_certificates_data ( ) {
914
+ let cert_pem = include_str ! ( "testdata/localhost.crt" ) ;
915
+ let key_pem = include_str ! ( "testdata/localhost.key" ) ;
916
+ let cert_b64 = BASE64_STANDARD . encode ( cert_pem) ;
917
+ let key_b64 = BASE64_STANDARD . encode ( key_pem) ;
918
+
919
+ let tls_config = TlsConfig {
920
+ client_cert_data : Some ( cert_b64) ,
921
+ client_key_data : Some ( key_b64) ,
922
+ insecure_skip_verify : None ,
923
+ ca_cert_file : None ,
924
+ ca_cert_data : None ,
925
+ client_cert_file : None ,
926
+ client_key_file : None ,
927
+ } ;
928
+
929
+ let builder = reqwest:: Client :: builder ( ) ;
930
+ let result = configure_tls ( builder, & tls_config) ;
931
+ assert ! ( result. is_ok( ) ) ;
932
+ }
933
+
934
+ #[ test]
935
+ fn test_tls_config_client_certificates_files ( ) {
936
+ let tls_config = TlsConfig {
937
+ client_cert_file : Some ( "src/testdata/localhost.crt" . to_string ( ) ) ,
938
+ client_key_file : Some ( "src/testdata/localhost.key" . to_string ( ) ) ,
939
+ insecure_skip_verify : None ,
940
+ ca_cert_file : None ,
941
+ ca_cert_data : None ,
942
+ client_cert_data : None ,
943
+ client_key_data : None ,
944
+ } ;
945
+
946
+ let builder = reqwest:: Client :: builder ( ) ;
947
+ let result = configure_tls ( builder, & tls_config) ;
948
+ assert ! ( result. is_ok( ) ) ;
949
+ }
950
+
951
+ #[ test]
952
+ fn test_tls_config_invalid_ca_cert_data ( ) {
953
+ let tls_config = TlsConfig {
954
+ ca_cert_data : Some ( "invalid_base64" . to_string ( ) ) ,
955
+ insecure_skip_verify : None ,
956
+ ca_cert_file : None ,
957
+ client_cert_file : None ,
958
+ client_key_file : None ,
959
+ client_cert_data : None ,
960
+ client_key_data : None ,
961
+ } ;
962
+
963
+ let builder = reqwest:: Client :: builder ( ) ;
964
+ let result = configure_tls ( builder, & tls_config) ;
965
+ assert ! ( result. is_err( ) ) ;
966
+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "Invalid CA cert data" ) ) ;
967
+ }
968
+
969
+ #[ test]
970
+ fn test_tls_config_invalid_ca_cert_file ( ) {
971
+ let tls_config = TlsConfig {
972
+ ca_cert_file : Some ( "nonexistent.crt" . to_string ( ) ) ,
973
+ insecure_skip_verify : None ,
974
+ ca_cert_data : None ,
975
+ client_cert_file : None ,
976
+ client_key_file : None ,
977
+ client_cert_data : None ,
978
+ client_key_data : None ,
979
+ } ;
980
+
981
+ let builder = reqwest:: Client :: builder ( ) ;
982
+ let result = configure_tls ( builder, & tls_config) ;
983
+ assert ! ( result. is_err( ) ) ;
984
+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "Failed to read CA cert file" ) ) ;
985
+ }
986
+
987
+ #[ test]
988
+ fn test_tls_config_combined_options ( ) {
989
+ let cert_pem = include_str ! ( "testdata/localhost.crt" ) ;
990
+ let cert_b64 = BASE64_STANDARD . encode ( cert_pem) ;
991
+
992
+ let tls_config = TlsConfig {
993
+ ca_cert_data : Some ( cert_b64) ,
994
+ insecure_skip_verify : Some ( true ) ,
995
+ ca_cert_file : None ,
996
+ client_cert_file : None ,
997
+ client_key_file : None ,
998
+ client_cert_data : None ,
999
+ client_key_data : None ,
1000
+ } ;
1001
+
1002
+ let builder = reqwest:: Client :: builder ( ) ;
1003
+ let result = configure_tls ( builder, & tls_config) ;
1004
+ assert ! ( result. is_ok( ) ) ;
1005
+ }
1006
+
1007
+ #[ test]
1008
+ fn test_tls_config_empty ( ) {
1009
+ let tls_config = TlsConfig {
1010
+ insecure_skip_verify : None ,
1011
+ ca_cert_file : None ,
1012
+ ca_cert_data : None ,
1013
+ client_cert_file : None ,
1014
+ client_key_file : None ,
1015
+ client_cert_data : None ,
1016
+ client_key_data : None ,
1017
+ } ;
1018
+
1019
+ let builder = reqwest:: Client :: builder ( ) ;
1020
+ let result = configure_tls ( builder, & tls_config) ;
1021
+ assert ! ( result. is_ok( ) ) ;
1022
+ }
777
1023
}
0 commit comments