ic_bn_lib/tls/
resolver.rs1use std::{fmt::Debug, path::PathBuf, sync::Arc, time::Instant};
2
3use anyhow::{Context as _, Error};
4use ic_bn_lib_common::{traits::tls::ResolvesServerCert, types::http::ALPN_ACME};
5use prometheus::{
6 HistogramVec, IntCounterVec, Registry, register_histogram_vec_with_registry,
7 register_int_counter_vec_with_registry,
8};
9use rustls::{
10 server::{ClientHello, ResolvesServerCert as ResolvesServerCertRustls},
11 sign::CertifiedKey,
12};
13use tracing::debug;
14
15use crate::tls::{pem_convert_to_rustls, pem_load_rustls, pem_load_rustls_single};
16
17#[derive(Debug, Clone)]
18pub struct Metrics {
19 resolve_count: IntCounterVec,
20 supported_scheme: IntCounterVec,
21 supported_cipher: IntCounterVec,
22 resolve_duration: HistogramVec,
23}
24
25impl Metrics {
26 pub fn new(registry: &Registry) -> Self {
27 Self {
28 resolve_count: register_int_counter_vec_with_registry!(
29 format!("tls_resolver_total"),
30 format!("Counts the number of resolves"),
31 &["found"],
32 registry
33 )
34 .unwrap(),
35
36 supported_scheme: register_int_counter_vec_with_registry!(
37 format!("tls_resolver_supported_scheme"),
38 format!("Counts the number clients that support given scheme"),
39 &["scheme"],
40 registry
41 )
42 .unwrap(),
43
44 supported_cipher: register_int_counter_vec_with_registry!(
45 format!("tls_resolver_supported_cipher"),
46 format!("Counts the number clients that support given ciphersuite"),
47 &["cipher"],
48 registry
49 )
50 .unwrap(),
51
52 resolve_duration: register_histogram_vec_with_registry!(
53 format!("tls_resolver_duration_sec"),
54 format!("Records the duration of resolves in seconds"),
55 &["found"],
56 [0.0001, 0.0005, 0.001, 0.002, 0.004, 0.008, 0.016, 0.032].to_vec(),
57 registry
58 )
59 .unwrap(),
60 }
61 }
62}
63
64#[derive(Debug, derive_new::new)]
67pub struct AggregatingResolver {
68 acme: Option<Arc<dyn ResolvesServerCertRustls>>,
69 resolvers: Vec<Arc<dyn ResolvesServerCert>>,
70 metrics: Metrics,
71}
72
73impl AggregatingResolver {
74 fn resolve_inner(&self, ch: ClientHello) -> Option<Arc<CertifiedKey>> {
75 let sni = ch.server_name().unwrap_or("").to_string();
77
78 if ch.alpn().is_some_and(|mut x| x.all(|x| x == ALPN_ACME))
80 && let Some(acme) = &self.acme
81 {
82 return acme.resolve(ch);
83 }
84
85 let alpn = ch
86 .alpn()
87 .map(|x| x.map(String::from_utf8_lossy).collect::<Vec<_>>());
88
89 let cert = self
91 .resolvers
92 .iter()
93 .find_map(|x| x.resolve(&ch))
94 .or_else(|| self.acme.as_ref().and_then(|x| x.resolve(ch)));
96
97 if cert.is_some() {
98 return cert;
99 }
100
101 debug!(
102 "AggregatingResolver: No certificate found for SNI '{}' (ALPN {:?}), trying fallback",
103 sni, alpn
104 );
105
106 self.resolvers.iter().find_map(|x| x.resolve_any())
108 }
109}
110
111impl ResolvesServerCertRustls for AggregatingResolver {
113 fn resolve(&self, ch: ClientHello) -> Option<Arc<CertifiedKey>> {
114 for v in ch.signature_schemes() {
115 self.metrics
116 .supported_scheme
117 .with_label_values(&[v.as_str().unwrap_or("unknown")])
118 .inc();
119 }
120
121 for v in ch.cipher_suites() {
122 self.metrics
123 .supported_cipher
124 .with_label_values(&[v.as_str().unwrap_or("unknown")])
125 .inc();
126 }
127
128 let start = Instant::now();
129 let r = self.resolve_inner(ch);
130 let found = if r.is_some() { "yes" } else { "no" };
131
132 self.metrics
133 .resolve_duration
134 .with_label_values(&[found])
135 .observe(start.elapsed().as_secs_f64());
136
137 self.metrics.resolve_count.with_label_values(&[found]).inc();
138
139 r
140 }
141}
142
143#[derive(Clone, Debug)]
145pub struct StubResolver(Arc<CertifiedKey>);
146
147impl StubResolver {
148 pub fn new(cert: &[u8], key: &[u8]) -> Result<Self, Error> {
150 Ok(Self(Arc::new(
151 pem_convert_to_rustls(key, cert).context("unable to parse cert and/or key")?,
152 )))
153 }
154
155 pub fn new_from_files(cert: PathBuf, key: PathBuf) -> Result<Self, Error> {
157 Ok(Self(Arc::new(
158 pem_load_rustls(key, cert).context("unable to load certificates")?,
159 )))
160 }
161
162 pub fn new_from_file(pem: PathBuf) -> Result<Self, Error> {
164 Ok(Self(Arc::new(
165 pem_load_rustls_single(pem).context("unable to load certificates")?,
166 )))
167 }
168}
169
170impl ResolvesServerCertRustls for StubResolver {
171 fn resolve(&self, _client_hello: ClientHello) -> Option<Arc<CertifiedKey>> {
172 Some(self.0.clone())
173 }
174}