Coverage Report

Created: 2025-05-30 16:37

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 Apache License, Version 2.0 (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
//    http://www.apache.org/licenses/LICENSE-2.0
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 nativelink_config::stores::{ClientTlsConfig, GrpcEndpoint};
16
use nativelink_error::{Code, Error, make_err, make_input_err};
17
use tonic::transport::Uri;
18
use tracing::warn;
19
20
10
pub fn load_client_config(
21
10
    config: &Option<ClientTlsConfig>,
22
10
) -> Result<Option<tonic::transport::ClientTlsConfig>, Error> {
23
10
    let Some(
config9
) = config else {
  Branch (23:9): [True: 9, False: 1]
  Branch (23:9): [Folded - Ignored]
24
1
        return Ok(None);
25
    };
26
27
9
    if config.use_native_roots == Some(true) {
  Branch (27:8): [True: 5, False: 4]
  Branch (27:8): [Folded - Ignored]
28
5
        if config.ca_file.is_some() {
  Branch (28:12): [True: 0, False: 5]
  Branch (28:12): [Folded - Ignored]
29
0
            warn!("Native root certificates are being used, all certificate files will be ignored");
30
5
        }
31
5
        return Ok(Some(
32
5
            tonic::transport::ClientTlsConfig::new().with_native_roots(),
33
5
        ));
34
4
    }
35
36
4
    let Some(
ca_file3
) = &config.ca_file else {
  Branch (36:9): [True: 3, False: 1]
  Branch (36:9): [Folded - Ignored]
37
1
        return Err(make_err!(
38
1
            Code::Internal,
39
1
            "CA certificate must be provided if not using native root certificates"
40
1
        ));
41
    };
42
43
3
    let read_config = tonic::transport::ClientTlsConfig::new().ca_certificate(
44
3
        tonic::transport::Certificate::from_pem(std::fs::read_to_string(ca_file)
?0
),
45
    );
46
3
    let 
config1
= if let Some(
client_certificate2
) = &config.cert_file {
  Branch (46:25): [True: 2, False: 1]
  Branch (46:25): [Folded - Ignored]
47
2
        let Some(
client_key1
) = &config.key_file else {
  Branch (47:13): [True: 1, False: 1]
  Branch (47:13): [Folded - Ignored]
48
1
            return Err(make_err!(
49
1
                Code::Internal,
50
1
                "Client certificate specified, but no key"
51
1
            ));
52
        };
53
1
        read_config.identity(tonic::transport::Identity::from_pem(
54
1
            std::fs::read_to_string(client_certificate)
?0
,
55
1
            std::fs::read_to_string(client_key)
?0
,
56
        ))
57
    } else {
58
1
        if config.key_file.is_some() {
  Branch (58:12): [True: 1, False: 0]
  Branch (58:12): [Folded - Ignored]
59
1
            return Err(make_err!(
60
1
                Code::Internal,
61
1
                "Client key specified, but no certificate"
62
1
            ));
63
0
        }
64
0
        read_config
65
    };
66
67
1
    Ok(Some(config))
68
10
}
69
70
7
pub fn endpoint_from(
71
7
    endpoint: &str,
72
7
    tls_config: Option<tonic::transport::ClientTlsConfig>,
73
7
) -> Result<tonic::transport::Endpoint, Error> {
74
7
    let 
endpoint6
= Uri::try_from(endpoint)
75
7
        .map_err(|e| make_err!(
Code::Internal1
, "Unable to parse endpoint {endpoint}: {e:?}"))
?1
;
76
77
    // Tonic uses the TLS configuration if the scheme is "https", so replace
78
    // grpcs with https.
79
6
    let endpoint = if endpoint.scheme_str() == Some("grpcs") {
  Branch (79:23): [True: 1, False: 5]
  Branch (79:23): [Folded - Ignored]
80
1
        let mut parts = endpoint.into_parts();
81
1
        parts.scheme = Some("https".parse().map_err(|e| 
{0
82
0
            make_err!(
83
0
                Code::Internal,
84
                "https is an invalid scheme apparently? {e:?}"
85
            )
86
0
        })?);
87
1
        parts.try_into().map_err(|e| 
{0
88
0
            make_err!(
89
0
                Code::Internal,
90
                "Error changing Uri from grpcs to https: {e:?}"
91
            )
92
0
        })?
93
    } else {
94
5
        endpoint
95
    };
96
97
6
    let 
endpoint_transport3
= if let Some(
tls_config4
) = tls_config {
  Branch (97:37): [True: 4, False: 2]
  Branch (97:37): [Folded - Ignored]
98
4
        let Some(
authority3
) = endpoint.authority() else {
  Branch (98:13): [True: 3, False: 1]
  Branch (98:13): [Folded - Ignored]
99
1
            return Err(make_input_err!(
100
1
                "Unable to determine authority of endpoint: {endpoint}"
101
1
            ));
102
        };
103
3
        if endpoint.scheme_str() != Some("https") {
  Branch (103:12): [True: 1, False: 2]
  Branch (103:12): [Folded - Ignored]
104
1
            return Err(make_input_err!(
105
1
                "You have set TLS configuration on {endpoint}, but the scheme is not https or grpcs"
106
1
            ));
107
2
        }
108
2
        let tls_config = tls_config.domain_name(authority.host());
109
2
        tonic::transport::Endpoint::from(endpoint)
110
2
            .tls_config(tls_config)
111
2
            .map_err(|e| make_input_err!("Setting mTLS configuration: {e:?}"))
?0
112
    } else {
113
2
        if endpoint.scheme_str() == Some("https") {
  Branch (113:12): [True: 1, False: 1]
  Branch (113:12): [Folded - Ignored]
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
1
        }
118
1
        tonic::transport::Endpoint::from(endpoint)
119
    };
120
121
3
    Ok(endpoint_transport)
122
7
}
123
124
0
pub fn endpoint(endpoint_config: &GrpcEndpoint) -> Result<tonic::transport::Endpoint, Error> {
125
0
    let endpoint = endpoint_from(
126
0
        &endpoint_config.address,
127
0
        load_client_config(&endpoint_config.tls_config)?,
128
0
    )?;
129
0
    if let Some(concurrency_limit) = endpoint_config.concurrency_limit {
  Branch (129:12): [True: 0, False: 0]
  Branch (129:12): [Folded - Ignored]
130
0
        Ok(endpoint.concurrency_limit(concurrency_limit))
131
    } else {
132
0
        Ok(endpoint)
133
    }
134
0
}