Coverage Report

Created: 2026-04-07 08:46

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
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
}