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