/build/source/nativelink-util/src/tls_utils.rs
Line | Count | Source |
1 | | // Copyright 2024 The NativeLink Authors. All rights reserved. |
2 | | // |
3 | | // Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // See LICENSE file for details |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | use core::time::Duration; |
16 | | |
17 | | use nativelink_config::stores::{ClientTlsConfig, GrpcEndpoint}; |
18 | | use nativelink_error::{Code, Error, make_err, make_input_err}; |
19 | | use tonic::transport::Uri; |
20 | | use tracing::{info, warn}; |
21 | | |
22 | 11 | pub fn load_client_config( |
23 | 11 | config: &Option<ClientTlsConfig>, |
24 | 11 | ) -> Result<Option<tonic::transport::ClientTlsConfig>, Error> { |
25 | 11 | let Some(config9 ) = config else { |
26 | 2 | return Ok(None); |
27 | | }; |
28 | | |
29 | 9 | if config.use_native_roots == Some(true) { |
30 | 5 | if config.ca_file.is_some() { |
31 | 0 | warn!("Native root certificates are being used, all certificate files will be ignored"); |
32 | 5 | } |
33 | 5 | return Ok(Some( |
34 | 5 | tonic::transport::ClientTlsConfig::new().with_native_roots(), |
35 | 5 | )); |
36 | 4 | } |
37 | | |
38 | 4 | let Some(ca_file3 ) = &config.ca_file else { |
39 | 1 | return Err(make_err!( |
40 | 1 | Code::Internal, |
41 | 1 | "CA certificate must be provided if not using native root certificates" |
42 | 1 | )); |
43 | | }; |
44 | | |
45 | 3 | let read_config = tonic::transport::ClientTlsConfig::new().ca_certificate( |
46 | 3 | tonic::transport::Certificate::from_pem(std::fs::read_to_string(ca_file)?0 ), |
47 | | ); |
48 | 3 | let config1 = if let Some(client_certificate2 ) = &config.cert_file { |
49 | 2 | let Some(client_key1 ) = &config.key_file else { |
50 | 1 | return Err(make_err!( |
51 | 1 | Code::Internal, |
52 | 1 | "Client certificate specified, but no key" |
53 | 1 | )); |
54 | | }; |
55 | 1 | read_config.identity(tonic::transport::Identity::from_pem( |
56 | 1 | std::fs::read_to_string(client_certificate)?0 , |
57 | 1 | std::fs::read_to_string(client_key)?0 , |
58 | | )) |
59 | | } else { |
60 | 1 | if config.key_file.is_some() { |
61 | 1 | return Err(make_err!( |
62 | 1 | Code::Internal, |
63 | 1 | "Client key specified, but no certificate" |
64 | 1 | )); |
65 | 0 | } |
66 | 0 | read_config |
67 | | }; |
68 | | |
69 | 1 | Ok(Some(config)) |
70 | 11 | } |
71 | | |
72 | 8 | pub fn endpoint_from( |
73 | 8 | endpoint: &str, |
74 | 8 | tls_config: Option<tonic::transport::ClientTlsConfig>, |
75 | 8 | ) -> Result<tonic::transport::Endpoint, Error> { |
76 | 8 | let endpoint7 = Uri::try_from(endpoint).map_err(|e| {1 |
77 | 1 | Error::from_std_err(Code::Internal, &e) |
78 | 1 | .append(format!("Unable to parse endpoint {endpoint}")) |
79 | 1 | })?; |
80 | | |
81 | | // Tonic uses the TLS configuration if the scheme is "https", so replace |
82 | | // grpcs with https. |
83 | 7 | let endpoint = if endpoint.scheme_str() == Some("grpcs") { |
84 | 1 | let mut parts = endpoint.into_parts(); |
85 | 1 | parts.scheme = Some("https".parse().map_err(|e| {0 |
86 | 0 | Error::from_std_err(Code::Internal, &e).append("https is an invalid scheme apparently?") |
87 | 0 | })?); |
88 | 1 | parts.try_into().map_err(|e| {0 |
89 | 0 | Error::from_std_err(Code::Internal, &e).append("Error changing Uri from grpcs to https") |
90 | 0 | })? |
91 | | } else { |
92 | 6 | endpoint |
93 | | }; |
94 | | |
95 | 7 | let endpoint_transport4 = if let Some(tls_config4 ) = tls_config { |
96 | 4 | let Some(authority3 ) = endpoint.authority() else { |
97 | 1 | return Err(make_input_err!( |
98 | 1 | "Unable to determine authority of endpoint: {endpoint}" |
99 | 1 | )); |
100 | | }; |
101 | 3 | if endpoint.scheme_str() != Some("https") { |
102 | 1 | return Err(make_input_err!( |
103 | 1 | "You have set TLS configuration on {endpoint}, but the scheme is not https or grpcs" |
104 | 1 | )); |
105 | 2 | } |
106 | 2 | let tls_config = tls_config.domain_name(authority.host()); |
107 | 2 | tonic::transport::Endpoint::from(endpoint) |
108 | 2 | .tls_config(tls_config) |
109 | 2 | .map_err(|e| {0 |
110 | 0 | Error::from_std_err(Code::InvalidArgument, &e).append("Setting mTLS configuration") |
111 | 0 | })? |
112 | | } else { |
113 | 3 | if endpoint.scheme_str() == Some("https") { |
114 | 1 | return Err(make_input_err!( |
115 | 1 | "The scheme of {endpoint} is https or grpcs, but no TLS configuration was provided" |
116 | 1 | )); |
117 | 2 | } |
118 | 2 | tonic::transport::Endpoint::from(endpoint) |
119 | | }; |
120 | | |
121 | 4 | Ok(endpoint_transport) |
122 | 8 | } |
123 | | |
124 | 1 | pub fn endpoint(endpoint_config: &GrpcEndpoint) -> Result<tonic::transport::Endpoint, Error> { |
125 | 1 | let endpoint = endpoint_from( |
126 | 1 | &endpoint_config.address, |
127 | 1 | load_client_config(&endpoint_config.tls_config)?0 , |
128 | 0 | )?; |
129 | | |
130 | 1 | let connect_timeout = if endpoint_config.connect_timeout_s > 0 { |
131 | 0 | Duration::from_secs(endpoint_config.connect_timeout_s) |
132 | | } else { |
133 | 1 | Duration::from_secs(30) |
134 | | }; |
135 | 1 | let tcp_keepalive = if endpoint_config.tcp_keepalive_s > 0 { |
136 | 0 | Duration::from_secs(endpoint_config.tcp_keepalive_s) |
137 | | } else { |
138 | 1 | Duration::from_secs(30) |
139 | | }; |
140 | 1 | let http2_keepalive_interval = if endpoint_config.http2_keepalive_interval_s > 0 { |
141 | 0 | Duration::from_secs(endpoint_config.http2_keepalive_interval_s) |
142 | | } else { |
143 | 1 | Duration::from_secs(30) |
144 | | }; |
145 | 1 | let http2_keepalive_timeout = if endpoint_config.http2_keepalive_timeout_s > 0 { |
146 | 0 | Duration::from_secs(endpoint_config.http2_keepalive_timeout_s) |
147 | | } else { |
148 | 1 | Duration::from_secs(20) |
149 | | }; |
150 | | |
151 | 1 | info!( |
152 | | address = %endpoint_config.address, |
153 | | concurrency_limit = ?endpoint_config.concurrency_limit, |
154 | 1 | connect_timeout_s = connect_timeout.as_secs(), |
155 | 1 | tcp_keepalive_s = tcp_keepalive.as_secs(), |
156 | 1 | http2_keepalive_interval_s = http2_keepalive_interval.as_secs(), |
157 | 1 | http2_keepalive_timeout_s = http2_keepalive_timeout.as_secs(), |
158 | | "tls_utils::endpoint: creating gRPC endpoint with keepalive", |
159 | | ); |
160 | | |
161 | 1 | let mut endpoint = endpoint |
162 | 1 | .connect_timeout(connect_timeout) |
163 | 1 | .tcp_keepalive(Some(tcp_keepalive)) |
164 | 1 | .http2_keep_alive_interval(http2_keepalive_interval) |
165 | 1 | .keep_alive_timeout(http2_keepalive_timeout) |
166 | 1 | .keep_alive_while_idle(true); |
167 | | |
168 | 1 | if let Some(concurrency_limit0 ) = endpoint_config.concurrency_limit { |
169 | 0 | endpoint = endpoint.concurrency_limit(concurrency_limit); |
170 | 1 | } |
171 | | |
172 | 1 | Ok(endpoint) |
173 | 1 | } |