Coverage Report

Created: 2026-05-23 21:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
16
pub fn load_client_config(
23
16
    config: &Option<ClientTlsConfig>,
24
16
) -> Result<Option<tonic::transport::ClientTlsConfig>, Error> {
25
16
    let Some(
config9
) = config else {
26
7
        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
16
}
71
72
13
pub fn endpoint_from(
73
13
    endpoint: &str,
74
13
    tls_config: Option<tonic::transport::ClientTlsConfig>,
75
13
) -> Result<tonic::transport::Endpoint, Error> {
76
13
    let 
endpoint12
= 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
12
    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
11
        endpoint
93
    };
94
95
12
    let 
endpoint_transport9
= 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
8
        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
7
        }
118
7
        tonic::transport::Endpoint::from(endpoint)
119
    };
120
121
9
    Ok(endpoint_transport)
122
13
}
123
124
6
pub fn endpoint(endpoint_config: &GrpcEndpoint) -> Result<tonic::transport::Endpoint, Error> {
125
6
    let endpoint = endpoint_from(
126
6
        &endpoint_config.address,
127
6
        load_client_config(&endpoint_config.tls_config)
?0
,
128
0
    )?;
129
130
6
    let connect_timeout = if endpoint_config.connect_timeout_s > 0 {
131
0
        Duration::from_secs(endpoint_config.connect_timeout_s)
132
    } else {
133
6
        Duration::from_secs(30)
134
    };
135
6
    let tcp_keepalive = if endpoint_config.tcp_keepalive_s > 0 {
136
0
        Duration::from_secs(endpoint_config.tcp_keepalive_s)
137
    } else {
138
6
        Duration::from_secs(30)
139
    };
140
6
    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
6
        Duration::from_secs(30)
144
    };
145
6
    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
6
        Duration::from_secs(20)
149
    };
150
151
6
    info!(
152
        address = %endpoint_config.address,
153
        concurrency_limit = ?endpoint_config.concurrency_limit,
154
6
        connect_timeout_s = connect_timeout.as_secs(),
155
6
        tcp_keepalive_s = tcp_keepalive.as_secs(),
156
6
        http2_keepalive_interval_s = http2_keepalive_interval.as_secs(),
157
6
        http2_keepalive_timeout_s = http2_keepalive_timeout.as_secs(),
158
        "tls_utils::endpoint: creating gRPC endpoint with keepalive",
159
    );
160
161
6
    let mut endpoint = endpoint
162
6
        .connect_timeout(connect_timeout)
163
6
        .tcp_keepalive(Some(tcp_keepalive))
164
6
        .http2_keep_alive_interval(http2_keepalive_interval)
165
6
        .keep_alive_timeout(http2_keepalive_timeout)
166
6
        .keep_alive_while_idle(true);
167
168
6
    if let Some(
concurrency_limit0
) = endpoint_config.concurrency_limit {
169
0
        endpoint = endpoint.concurrency_limit(concurrency_limit);
170
6
    }
171
172
6
    Ok(endpoint)
173
6
}