Skip to main content

bpf_oci/
auth.rs

1use std::{collections::HashMap, path::Path};
2
3use anyhow::{anyhow, Context, Result};
4use base64::Engine;
5use oci_distribution::secrets::RegistryAuth;
6use serde::Deserialize;
7
8#[derive(Deserialize)]
9struct AuthEntry {
10    auth: String,
11}
12
13impl AuthEntry {
14    pub fn extract_registry_auth(&self) -> Result<RegistryAuth> {
15        let decoded = base64::engine::general_purpose::STANDARD
16            .decode(&self.auth)
17            .with_context(|| anyhow!("Failed to decode base64"))?;
18        let decoded_str =
19            String::from_utf8(decoded).with_context(|| anyhow!("Invalid utf8 chars"))?;
20        let (user, pass) = decoded_str
21            .split_once(':')
22            .with_context(|| anyhow!("Unable to find `:` in the auth string"))?;
23        Ok(RegistryAuth::Basic(user.to_owned(), pass.to_owned()))
24    }
25}
26
27#[derive(Deserialize)]
28struct DockerConfig {
29    auths: HashMap<String, AuthEntry>,
30}
31
32pub trait RegistryAuthExt {
33    fn load_from_docker(path: Option<&Path>, registry: &str) -> Result<Self>
34    where
35        Self: Sized;
36
37    fn load_from_prompt() -> Result<Self>
38    where
39        Self: Sized;
40}
41
42impl RegistryAuthExt for RegistryAuth {
43    fn load_from_docker(path: Option<&Path>, registry: &str) -> Result<Self>
44    where
45        Self: Sized,
46    {
47        let docker_cfg_path = match path.map(|v| v.to_path_buf()) {
48            Some(v) => v,
49            None => home::home_dir()
50                .with_context(|| anyhow!("Unable to retrive home directory"))?
51                .join(".docker/config.json"),
52        };
53        let config: DockerConfig = serde_json::from_str(
54            &std::fs::read_to_string(&docker_cfg_path)
55                .with_context(|| anyhow!("Failed to read docker config"))?,
56        )
57        .with_context(|| anyhow!("Failed to deserialize docker config"))?;
58        let auth_entry = config.auths.get(registry).with_context(|| {
59            anyhow!(
60                "Unable to find auth entry named `{}` in {:?}",
61                registry,
62                docker_cfg_path
63            )
64        })?;
65        auth_entry.extract_registry_auth()
66    }
67    fn load_from_prompt() -> Result<Self>
68    where
69        Self: Sized,
70    {
71        print!("Username: ");
72        let mut username = String::default();
73        std::io::stdin().read_line(&mut username)?;
74        let password = rpassword::prompt_password("Password: ")?;
75        Ok(Self::Basic(username, password))
76    }
77}
78
79#[cfg(test)]
80mod tests {
81
82    use std::path::PathBuf;
83
84    use oci_distribution::secrets::RegistryAuth;
85    use tempfile::{tempdir, TempDir};
86
87    use super::RegistryAuthExt;
88
89    fn write_temp_file() -> (PathBuf, TempDir) {
90        let dir = tempdir().unwrap();
91        let data = include_bytes!("../assets/docker-config-test.json");
92        std::fs::write(dir.path().join("test.json"), data).unwrap();
93        (dir.path().join("test.json"), dir)
94    }
95
96    #[test]
97    fn test_load_docker_config() {
98        let (f, _dir) = write_temp_file();
99        let auth = RegistryAuth::load_from_docker(Some(&f), "ghcr.io").unwrap();
100        match auth {
101            RegistryAuth::Basic(a, b) => {
102                assert_eq!(a, "aaa");
103                assert_eq!(b, "bbb");
104            }
105            _ => unreachable!(),
106        }
107    }
108}