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> {
37 let path = Self::default_config_path()?;
38 Self::load_from_path(&path)
39 }
40
41 pub fn load_from_path(path: &Path) -> Result<Self> {
46 if !path.exists() {
47 return Ok(Self {
48 auths: HashMap::new(),
49 });
50 }
51
52 let contents = fs::read_to_string(path).map_err(|e| {
53 crate::error::Error::config(format!("Failed to read Docker config: {e}"))
54 })?;
55
56 let config: DockerConfigAuth = serde_json::from_str(&contents).map_err(|e| {
57 crate::error::Error::config(format!("Failed to parse Docker config: {e}"))
58 })?;
59
60 Ok(config)
61 }
62
63 #[must_use]
68 pub fn get_credentials(&self, registry: &str) -> Option<(String, String)> {
69 if let Some(entry) = self.auths.get(registry) {
71 return Self::extract_credentials(entry);
72 }
73
74 let https_registry = format!("https://{registry}");
76 if let Some(entry) = self.auths.get(&https_registry) {
77 return Self::extract_credentials(entry);
78 }
79
80 if registry == "docker.io" || registry == "registry-1.docker.io" {
82 if let Some(entry) = self.auths.get("https://index.docker.io/v1/") {
83 return Self::extract_credentials(entry);
84 }
85 }
86
87 None
88 }
89
90 fn extract_credentials(entry: &AuthEntry) -> Option<(String, String)> {
92 if let (Some(username), Some(password)) = (&entry.username, &entry.password) {
94 return Some((username.clone(), password.clone()));
95 }
96
97 if let Some(auth) = &entry.auth {
99 return Self::decode_auth(auth);
100 }
101
102 None
103 }
104
105 fn decode_auth(auth: &str) -> Option<(String, String)> {
107 use base64::Engine;
108 let decoded = base64::engine::general_purpose::STANDARD
109 .decode(auth)
110 .ok()?;
111
112 let decoded_str = String::from_utf8(decoded).ok()?;
113 let parts: Vec<&str> = decoded_str.splitn(2, ':').collect();
114
115 if parts.len() == 2 {
116 Some((parts[0].to_string(), parts[1].to_string()))
117 } else {
118 None
119 }
120 }
121
122 fn default_config_path() -> Result<PathBuf> {
129 if let Ok(config_dir) = std::env::var("DOCKER_CONFIG") {
130 return Ok(PathBuf::from(config_dir).join("config.json"));
131 }
132
133 let home = dirs::home_dir().ok_or_else(|| {
134 crate::error::Error::config("Cannot determine home directory".to_string())
135 })?;
136
137 Ok(home.join(".docker").join("config.json"))
138 }
139
140 #[must_use]
142 pub fn registries(&self) -> Vec<String> {
143 self.auths.keys().cloned().collect()
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_parse_docker_config() {
153 let config_json = r#"
154 {
155 "auths": {
156 "ghcr.io": {
157 "auth": "dXNlcm5hbWU6cGFzc3dvcmQ="
158 },
159 "docker.io": {
160 "username": "myuser",
161 "password": "mypass"
162 }
163 }
164 }
165 "#;
166
167 let config: DockerConfigAuth = serde_json::from_str(config_json).unwrap();
168
169 let (username, password) = config.get_credentials("ghcr.io").unwrap();
171 assert_eq!(username, "username");
172 assert_eq!(password, "password");
173
174 let (username, password) = config.get_credentials("docker.io").unwrap();
176 assert_eq!(username, "myuser");
177 assert_eq!(password, "mypass");
178 }
179
180 #[test]
181 fn test_registry_normalization() {
182 let config_json = r#"
183 {
184 "auths": {
185 "https://ghcr.io": {
186 "auth": "dXNlcm5hbWU6cGFzc3dvcmQ="
187 },
188 "https://index.docker.io/v1/": {
189 "auth": "ZG9ja2VyOnBhc3M="
190 }
191 }
192 }
193 "#;
194
195 let config: DockerConfigAuth = serde_json::from_str(config_json).unwrap();
196
197 assert!(config.get_credentials("ghcr.io").is_some());
199
200 assert!(config.get_credentials("docker.io").is_some());
202 }
203
204 #[test]
205 fn test_decode_auth() {
206 let auth = "dXNlcm5hbWU6cGFzc3dvcmQ=";
208 let (username, password) = DockerConfigAuth::decode_auth(auth).unwrap();
209 assert_eq!(username, "username");
210 assert_eq!(password, "password");
211 }
212
213 #[test]
214 fn test_empty_config() {
215 let config = DockerConfigAuth {
216 auths: HashMap::new(),
217 };
218
219 assert!(config.get_credentials("docker.io").is_none());
220 }
221}