containerregistry_auth/
resolver.rs1use crate::config::DockerConfig;
11use crate::helper::{CredentialHelper, normalize_server_url};
12use crate::{Credential, Result};
13
14#[derive(Clone, Debug)]
19pub struct AuthResolver {
20 config: Option<DockerConfig>,
22
23 explicit: Option<Credential>,
25}
26
27impl AuthResolver {
28 pub fn new() -> Self {
33 let config = DockerConfig::load().ok();
34 Self {
35 config,
36 explicit: None,
37 }
38 }
39
40 pub fn with_config(config: DockerConfig) -> Self {
42 Self {
43 config: Some(config),
44 explicit: None,
45 }
46 }
47
48 pub fn anonymous() -> Self {
50 Self {
51 config: None,
52 explicit: Some(Credential::Anonymous),
53 }
54 }
55
56 pub fn with_explicit(mut self, credential: Credential) -> Self {
58 self.explicit = Some(credential);
59 self
60 }
61
62 pub fn resolve(&self, registry: &str) -> Result<Credential> {
71 if let Some(ref cred) = self.explicit {
73 return Ok(cred.clone());
74 }
75
76 let config = match &self.config {
77 Some(c) => c,
78 None => return Ok(Credential::Anonymous),
79 };
80
81 if let Some(cred) = config.get_auth(registry) {
83 return Ok(cred);
84 }
85
86 if let Some(helper_name) = config.get_credential_helper(registry) {
88 let helper = CredentialHelper::new(helper_name);
89 let server_url = normalize_server_url(registry);
90
91 match helper.get(&server_url) {
92 Ok(cred) if !cred.is_anonymous() => return Ok(cred),
93 Ok(_) => {} Err(crate::Error::HelperNotFound(_)) => {} Err(e) => return Err(e), }
97 }
98
99 Ok(Credential::Anonymous)
101 }
102
103 pub fn resolve_or_anonymous(&self, registry: &str) -> Credential {
108 self.resolve(registry).unwrap_or(Credential::Anonymous)
109 }
110}
111
112impl Default for AuthResolver {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::test_util::ENV_LOCK;
122
123 #[test]
124 fn test_resolver_explicit_credentials() {
125 let resolver = AuthResolver::anonymous().with_explicit(Credential::basic("user", "pass"));
126
127 let cred = resolver.resolve("any-registry.io").unwrap();
128 assert_eq!(cred.username(), Some("user"));
129 }
130
131 #[test]
132 fn test_resolver_anonymous() {
133 let resolver = AuthResolver::anonymous();
134 let cred = resolver.resolve("any-registry.io").unwrap();
135 assert!(cred.is_anonymous());
136 }
137
138 #[test]
139 fn test_resolver_from_config_auths() {
140 let config = DockerConfig::from_json(
141 r#"{
142 "auths": {
143 "gcr.io": {
144 "auth": "dXNlcjpwYXNz"
145 }
146 }
147 }"#,
148 )
149 .unwrap();
150
151 let resolver = AuthResolver::with_config(config);
152 let cred = resolver.resolve("gcr.io").unwrap();
153
154 assert_eq!(cred.username(), Some("user"));
155 assert_eq!(cred.password(), Some("pass"));
156 }
157
158 #[test]
159 fn test_resolver_falls_back_to_anonymous() {
160 let config = DockerConfig::from_json(r#"{}"#).unwrap();
161
162 let resolver = AuthResolver::with_config(config);
163 let cred = resolver.resolve("unknown-registry.io").unwrap();
164
165 assert!(cred.is_anonymous());
166 }
167
168 #[test]
169 fn test_resolver_resolve_or_anonymous() {
170 let resolver = AuthResolver::anonymous();
171 let cred = resolver.resolve_or_anonymous("any-registry.io");
172 assert!(cred.is_anonymous());
173 }
174
175 #[test]
176 fn test_resolver_auths_precede_helpers() {
177 let config = DockerConfig::from_json(
178 r#"{
179 "auths": {
180 "example.com": { "username": "user", "password": "pass" }
181 },
182 "credHelpers": {
183 "example.com": "fake"
184 }
185 }"#,
186 )
187 .unwrap();
188
189 let resolver = AuthResolver::with_config(config);
190 let cred = resolver.resolve("example.com").unwrap();
191 assert_eq!(cred.username(), Some("user"));
192 }
193
194 #[test]
195 fn test_resolver_uses_credential_helper_when_no_auths() {
196 let _guard = ENV_LOCK.lock().unwrap();
197 let temp = tempfile::tempdir().unwrap();
198 let helper_path = temp.path().join("docker-credential-fake");
199
200 std::fs::write(
201 &helper_path,
202 r#"#!/bin/sh
203read server
204if [ "$server" = "https://example.com" ]; then
205 echo '{"Username":"helper","Secret":"secret"}'
206 exit 0
207fi
208echo "credentials not found" 1>&2
209exit 1
210"#,
211 )
212 .unwrap();
213 #[cfg(unix)]
214 {
215 use std::os::unix::fs::PermissionsExt;
216 let mut perms = std::fs::metadata(&helper_path).unwrap().permissions();
217 perms.set_mode(0o755);
218 std::fs::set_permissions(&helper_path, perms).unwrap();
219 }
220
221 let prev_path = std::env::var("PATH").ok();
222 let new_path = format!(
223 "{}:{}",
224 temp.path().to_string_lossy(),
225 prev_path.clone().unwrap_or_default()
226 );
227 unsafe {
228 std::env::set_var("PATH", new_path);
229 }
230
231 let config = DockerConfig::from_json(
232 r#"{
233 "credHelpers": {
234 "example.com": "fake"
235 }
236 }"#,
237 )
238 .unwrap();
239
240 let resolver = AuthResolver::with_config(config);
241 let cred = resolver.resolve("example.com").unwrap();
242 assert_eq!(cred.username(), Some("helper"));
243 assert_eq!(cred.password(), Some("secret"));
244
245 if let Some(value) = prev_path {
246 unsafe {
247 std::env::set_var("PATH", value);
248 }
249 } else {
250 unsafe {
251 std::env::remove_var("PATH");
252 }
253 }
254 }
255}