Skip to main content

mkit_server/scanner_retrieval/
service.rs

1use crate::ServerError;
2use crate::pipeline::PipelineConfig;
3use bytes::Bytes;
4
5/// A bounded pack segment, fully checked before the adapter returns its bytes.
6#[derive(Debug)]
7pub struct RetrievalResponse {
8    /// Exact raw bytes, bounded by `MAX_RESPONSE_BYTES`.
9    pub bytes: Bytes,
10    /// First byte of the selected segment.
11    pub start: u64,
12    /// Exact bound raw pack length.
13    pub total: u64,
14    /// Whether the scanner explicitly requested a range.
15    pub partial: bool,
16}
17
18pub(crate) fn missing() -> ServerError {
19    ServerError::not_found("pack not found")
20}
21
22/// Validate scanner prerequisites and key separation before serving requests.
23///
24/// # Errors
25/// Returns a configuration error when an enabled retrieval role is incomplete
26/// or shares key material with another configured role.
27pub fn validate_config(cfg: &PipelineConfig) -> Result<(), ServerError> {
28    let Some(config) = &cfg.scanner_retrieval else {
29        return Ok(());
30    };
31    if !cfg!(feature = "remote-hooks")
32        || !matches!(cfg.auth, crate::pipeline::AuthMode::AuthV2(_))
33        || cfg.indexed.is_none()
34        || cfg.ticket_keys.is_none()
35        || cfg.begin_upload_threshold_bytes != 0
36        || cfg.write_policy == crate::policy::WritePolicy::Open
37    {
38        return Err(ServerError::invalid_argument(
39            "scanner retrieval requires ticketed indexed inspection and auth v2",
40        ));
41    }
42    let mut public = cfg.admin_keys.clone();
43    if let Some(tokens) = &cfg.url_tokens {
44        public.extend(tokens.keys().public_keys());
45    }
46    if let Some(fence) = &cfg.authority_fence {
47        public.extend(fence.public_keys());
48    }
49    let namespaces: Vec<_> = match &cfg.addressing {
50        crate::Addressing::Single { repo } => vec![repo.namespace.as_str().to_owned()],
51        crate::Addressing::Multi(multi) => match &multi.namespace_policy {
52            crate::policy::NamespacePolicy::Allowlist(list) => {
53                list.iter().map(ToString::to_string).collect()
54            }
55            _ => Vec::new(),
56        },
57    };
58    for namespace in namespaces {
59        if let Ok(mkit_core::repo_identity::Namespace::Ed25519(key)) =
60            mkit_core::repo_identity::Namespace::parse(&namespace)
61        {
62            public.push(key);
63        }
64    }
65    config.check_role_keys(&public, &[]).map_err(|_| {
66        ServerError::invalid_argument("scanner retrieval key roles must be distinct")
67    })?;
68    if let Some(tokens) = &cfg.url_tokens
69        && (config.keys.iter().any(|key| {
70            tokens.keys().contains_secret(&key.secret)
71                || tokens.keys().contains_secret(
72                    &ed25519_dalek::SigningKey::from_bytes(&key.secret)
73                        .verifying_key()
74                        .to_bytes(),
75                )
76        }) || config
77            .scanner_keys()
78            .any(|key| tokens.keys().contains_secret(&key)))
79    {
80        return Err(ServerError::invalid_argument(
81            "scanner retrieval and URL token key roles must be distinct",
82        ));
83    }
84    if let Some(tickets) = &cfg.ticket_keys
85        && (config.keys.iter().any(|key| {
86            tickets.contains_secret(&key.secret)
87                || tickets.contains_ed25519_public(&key.secret)
88                || tickets.contains_secret(
89                    &ed25519_dalek::SigningKey::from_bytes(&key.secret)
90                        .verifying_key()
91                        .to_bytes(),
92                )
93                || tickets.contains_ed25519_public(
94                    &ed25519_dalek::SigningKey::from_bytes(&key.secret)
95                        .verifying_key()
96                        .to_bytes(),
97                )
98        }) || config
99            .scanner_keys()
100            .any(|key| tickets.contains_secret(&key) || tickets.contains_ed25519_public(&key)))
101    {
102        return Err(ServerError::invalid_argument(
103            "scanner retrieval and ticket key roles must be distinct",
104        ));
105    }
106    Ok(())
107}