Skip to main content

microsandbox_image/registry/
builder.rs

1use oci_client::{
2    Client,
3    client::{Certificate, CertificateEncoding, ClientConfig, ClientProtocol},
4};
5use rustls_pki_types::{CertificateDer, pem::PemObject};
6
7use crate::{
8    auth::RegistryAuth,
9    cache::GlobalCache,
10    error::{ImageError, ImageResult},
11    platform::Platform,
12};
13
14use super::client::{Registry, resolve_platform_digest};
15
16//--------------------------------------------------------------------------------------------------
17// Types
18//--------------------------------------------------------------------------------------------------
19
20/// Builder for constructing a [`Registry`] client with optional auth and TLS settings.
21pub struct RegistryBuilder {
22    pub(super) platform: Platform,
23    pub(super) cache: GlobalCache,
24    pub(super) auth: oci_client::secrets::RegistryAuth,
25    pub(super) insecure_registries: Vec<String>,
26    pub(super) extra_ca_certs: Vec<Vec<u8>>,
27}
28
29//--------------------------------------------------------------------------------------------------
30// Methods
31//--------------------------------------------------------------------------------------------------
32
33impl RegistryBuilder {
34    /// Create a registry builder with anonymous authentication and default TLS settings.
35    pub(crate) fn new(platform: Platform, cache: GlobalCache) -> Self {
36        Self {
37            platform,
38            cache,
39            auth: oci_client::secrets::RegistryAuth::Anonymous,
40            insecure_registries: Vec::new(),
41            extra_ca_certs: Vec::new(),
42        }
43    }
44
45    /// Set authentication credentials for the registry.
46    pub fn auth(mut self, auth: RegistryAuth) -> Self {
47        self.auth = (&auth).into();
48        self
49    }
50
51    /// Add registries that should be accessed over plain HTTP instead of HTTPS.
52    pub fn add_insecure_registries(mut self, registries: Vec<String>) -> Self {
53        self.insecure_registries.extend(registries);
54        self
55    }
56
57    /// Add PEM-encoded CA root certificates to trust.
58    pub fn extra_ca_certs(mut self, certs: Vec<Vec<u8>>) -> Self {
59        self.extra_ca_certs = certs;
60        self
61    }
62
63    /// Build the registry client.
64    ///
65    /// Returns [`ImageError::InvalidCertificate`] if any PEM data in
66    /// `extra_ca_certs` cannot be parsed as valid certificates.
67    pub fn build(self) -> ImageResult<Registry> {
68        let protocol = if self.insecure_registries.is_empty() {
69            ClientProtocol::Https
70        } else {
71            ClientProtocol::HttpsExcept(self.insecure_registries)
72        };
73
74        let mut extra_root_certificates = Vec::new();
75        for (i, pem_data) in self.extra_ca_certs.into_iter().enumerate() {
76            let certs: Vec<_> = CertificateDer::pem_slice_iter(&pem_data)
77                .collect::<Result<_, _>>()
78                .map_err(|e| {
79                    ImageError::InvalidCertificate(format!("entry {i}: failed to parse: {e}"))
80                })?;
81
82            if certs.is_empty() {
83                return Err(ImageError::InvalidCertificate(format!(
84                    "entry {i}: no certificates found in PEM data"
85                )));
86            }
87
88            for cert in certs {
89                extra_root_certificates.push(Certificate {
90                    encoding: CertificateEncoding::Der,
91                    data: cert.to_vec(),
92                });
93            }
94        }
95
96        let platform = self.platform.clone();
97        let client = Client::new(ClientConfig {
98            protocol,
99            extra_root_certificates,
100            platform_resolver: Some(Box::new(move |manifests| {
101                resolve_platform_digest(manifests, &platform)
102            })),
103            ..Default::default()
104        });
105
106        Ok(Registry {
107            client,
108            auth: self.auth,
109            platform: self.platform,
110            cache: self.cache,
111        })
112    }
113}