zlayer_core/auth/
docker_config.rs1use crate::error::Result;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fs;
10use std::path::{Path, PathBuf};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct DockerConfigAuth {
15 #[serde(default)]
16 auths: HashMap<String, AuthEntry>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21struct AuthEntry {
22 auth: Option<String>,
24 username: Option<String>,
26 password: Option<String>,
28}
29
30impl DockerConfigAuth {
31 pub fn load() -> Result<Self> {
33 let path = Self::default_config_path()?;
34 Self::load_from_path(&path)
35 }
36
37 pub fn load_from_path(path: &Path) -> Result<Self> {
39 if !path.exists() {
40 return Ok(Self {
41 auths: HashMap::new(),
42 });
43 }
44
45 let contents = fs::read_to_string(path).map_err(|e| {
46 crate::error::Error::config(format!("Failed to read Docker config: {}", e))
47 })?;
48
49 let config: DockerConfigAuth = serde_json::from_str(&contents).map_err(|e| {
50 crate::error::Error::config(format!("Failed to parse Docker config: {}", e))
51 })?;
52
53 Ok(config)
54 }
55
56 pub fn get_credentials(&self, registry: &str) -> Option<(String, String)> {
61 if let Some(entry) = self.auths.get(registry) {
63 return Self::extract_credentials(entry);
64 }
65
66 let https_registry = format!("https://{}", registry);
68 if let Some(entry) = self.auths.get(&https_registry) {
69 return Self::extract_credentials(entry);
70 }
71
72 if registry == "docker.io" || registry == "registry-1.docker.io" {
74 if let Some(entry) = self.auths.get("https://index.docker.io/v1/") {
75 return Self::extract_credentials(entry);
76 }
77 }
78
79 None
80 }
81
82 fn extract_credentials(entry: &AuthEntry) -> Option<(String, String)> {
84 if let (Some(username), Some(password)) = (&entry.username, &entry.password) {
86 return Some((username.clone(), password.clone()));
87 }
88
89 if let Some(auth) = &entry.auth {
91 return Self::decode_auth(auth);
92 }
93
94 None
95 }
96
97 fn decode_auth(auth: &str) -> Option<(String, String)> {
99 use base64::Engine;
100 let decoded = base64::engine::general_purpose::STANDARD
101 .decode(auth)
102 .ok()?;
103
104 let decoded_str = String::from_utf8(decoded).ok()?;
105 let parts: Vec<&str> = decoded_str.splitn(2, ':').collect();
106
107 if parts.len() == 2 {
108 Some((parts[0].to_string(), parts[1].to_string()))
109 } else {
110 None
111 }
112 }
113
114 fn default_config_path() -> Result<PathBuf> {
116 let home = dirs::home_dir().ok_or_else(|| {
117 crate::error::Error::config("Cannot determine home directory".to_string())
118 })?;
119
120 Ok(home.join(".docker").join("config.json"))
121 }
122
123 pub fn registries(&self) -> Vec<String> {
125 self.auths.keys().cloned().collect()
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_parse_docker_config() {
135 let config_json = r#"
136 {
137 "auths": {
138 "ghcr.io": {
139 "auth": "dXNlcm5hbWU6cGFzc3dvcmQ="
140 },
141 "docker.io": {
142 "username": "myuser",
143 "password": "mypass"
144 }
145 }
146 }
147 "#;
148
149 let config: DockerConfigAuth = serde_json::from_str(config_json).unwrap();
150
151 let (username, password) = config.get_credentials("ghcr.io").unwrap();
153 assert_eq!(username, "username");
154 assert_eq!(password, "password");
155
156 let (username, password) = config.get_credentials("docker.io").unwrap();
158 assert_eq!(username, "myuser");
159 assert_eq!(password, "mypass");
160 }
161
162 #[test]
163 fn test_registry_normalization() {
164 let config_json = r#"
165 {
166 "auths": {
167 "https://ghcr.io": {
168 "auth": "dXNlcm5hbWU6cGFzc3dvcmQ="
169 },
170 "https://index.docker.io/v1/": {
171 "auth": "ZG9ja2VyOnBhc3M="
172 }
173 }
174 }
175 "#;
176
177 let config: DockerConfigAuth = serde_json::from_str(config_json).unwrap();
178
179 assert!(config.get_credentials("ghcr.io").is_some());
181
182 assert!(config.get_credentials("docker.io").is_some());
184 }
185
186 #[test]
187 fn test_decode_auth() {
188 let auth = "dXNlcm5hbWU6cGFzc3dvcmQ=";
190 let (username, password) = DockerConfigAuth::decode_auth(auth).unwrap();
191 assert_eq!(username, "username");
192 assert_eq!(password, "password");
193 }
194
195 #[test]
196 fn test_empty_config() {
197 let config = DockerConfigAuth {
198 auths: HashMap::new(),
199 };
200
201 assert!(config.get_credentials("docker.io").is_none());
202 }
203}