k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/credentialprovider/keyring.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package credentialprovider 18 19 import ( 20 "net" 21 "net/url" 22 "path/filepath" 23 "sort" 24 "strings" 25 26 "k8s.io/apimachinery/pkg/util/sets" 27 "k8s.io/klog/v2" 28 ) 29 30 // DockerKeyring tracks a set of docker registry credentials, maintaining a 31 // reverse index across the registry endpoints. A registry endpoint is made 32 // up of a host (e.g. registry.example.com), but it may also contain a path 33 // (e.g. registry.example.com/foo) This index is important for two reasons: 34 // - registry endpoints may overlap, and when this happens we must find the 35 // most specific match for a given image 36 // - iterating a map does not yield predictable results 37 type DockerKeyring interface { 38 Lookup(image string) ([]AuthConfig, bool) 39 } 40 41 // BasicDockerKeyring is a trivial map-backed implementation of DockerKeyring 42 type BasicDockerKeyring struct { 43 index []string 44 creds map[string][]AuthConfig 45 } 46 47 // providersDockerKeyring is an implementation of DockerKeyring that 48 // materializes its dockercfg based on a set of dockerConfigProviders. 49 type providersDockerKeyring struct { 50 Providers []DockerConfigProvider 51 } 52 53 // AuthConfig contains authorization information for connecting to a Registry 54 // This type mirrors "github.com/docker/docker/api/types.AuthConfig" 55 type AuthConfig struct { 56 Username string `json:"username,omitempty"` 57 Password string `json:"password,omitempty"` 58 Auth string `json:"auth,omitempty"` 59 60 // Email is an optional value associated with the username. 61 // This field is deprecated and will be removed in a later 62 // version of docker. 63 Email string `json:"email,omitempty"` 64 65 ServerAddress string `json:"serveraddress,omitempty"` 66 67 // IdentityToken is used to authenticate the user and get 68 // an access token for the registry. 69 IdentityToken string `json:"identitytoken,omitempty"` 70 71 // RegistryToken is a bearer token to be sent to a registry 72 RegistryToken string `json:"registrytoken,omitempty"` 73 } 74 75 // Add add some docker config in basic docker keyring 76 func (dk *BasicDockerKeyring) Add(cfg DockerConfig) { 77 if dk.index == nil { 78 dk.index = make([]string, 0) 79 dk.creds = make(map[string][]AuthConfig) 80 } 81 for loc, ident := range cfg { 82 creds := AuthConfig{ 83 Username: ident.Username, 84 Password: ident.Password, 85 Email: ident.Email, 86 } 87 88 value := loc 89 if !strings.HasPrefix(value, "https://") && !strings.HasPrefix(value, "http://") { 90 value = "https://" + value 91 } 92 parsed, err := url.Parse(value) 93 if err != nil { 94 klog.Errorf("Entry %q in dockercfg invalid (%v), ignoring", loc, err) 95 continue 96 } 97 98 // The docker client allows exact matches: 99 // foo.bar.com/namespace 100 // Or hostname matches: 101 // foo.bar.com 102 // It also considers /v2/ and /v1/ equivalent to the hostname 103 // See ResolveAuthConfig in docker/registry/auth.go. 104 effectivePath := parsed.Path 105 if strings.HasPrefix(effectivePath, "/v2/") || strings.HasPrefix(effectivePath, "/v1/") { 106 effectivePath = effectivePath[3:] 107 } 108 var key string 109 if (len(effectivePath) > 0) && (effectivePath != "/") { 110 key = parsed.Host + effectivePath 111 } else { 112 key = parsed.Host 113 } 114 dk.creds[key] = append(dk.creds[key], creds) 115 dk.index = append(dk.index, key) 116 } 117 118 eliminateDupes := sets.NewString(dk.index...) 119 dk.index = eliminateDupes.List() 120 121 // Update the index used to identify which credentials to use for a given 122 // image. The index is reverse-sorted so more specific paths are matched 123 // first. For example, if for the given image "gcr.io/etcd-development/etcd", 124 // credentials for "quay.io/coreos" should match before "quay.io". 125 sort.Sort(sort.Reverse(sort.StringSlice(dk.index))) 126 } 127 128 const ( 129 defaultRegistryHost = "index.docker.io" 130 defaultRegistryKey = defaultRegistryHost + "/v1/" 131 ) 132 133 // isDefaultRegistryMatch determines whether the given image will 134 // pull from the default registry (DockerHub) based on the 135 // characteristics of its name. 136 func isDefaultRegistryMatch(image string) bool { 137 parts := strings.SplitN(image, "/", 2) 138 139 if len(parts[0]) == 0 { 140 return false 141 } 142 143 if len(parts) == 1 { 144 // e.g. library/ubuntu 145 return true 146 } 147 148 if parts[0] == "docker.io" || parts[0] == "index.docker.io" { 149 // resolve docker.io/image and index.docker.io/image as default registry 150 return true 151 } 152 153 // From: http://blog.docker.com/2013/07/how-to-use-your-own-registry/ 154 // Docker looks for either a “.” (domain separator) or “:” (port separator) 155 // to learn that the first part of the repository name is a location and not 156 // a user name. 157 return !strings.ContainsAny(parts[0], ".:") 158 } 159 160 // ParseSchemelessURL parses a schemeless url and returns a url.URL 161 // url.Parse require a scheme, but ours don't have schemes. Adding a 162 // scheme to make url.Parse happy, then clear out the resulting scheme. 163 func ParseSchemelessURL(schemelessURL string) (*url.URL, error) { 164 parsed, err := url.Parse("https://" + schemelessURL) 165 if err != nil { 166 return nil, err 167 } 168 // clear out the resulting scheme 169 parsed.Scheme = "" 170 return parsed, nil 171 } 172 173 // SplitURL splits the host name into parts, as well as the port 174 func SplitURL(url *url.URL) (parts []string, port string) { 175 host, port, err := net.SplitHostPort(url.Host) 176 if err != nil { 177 // could not parse port 178 host, port = url.Host, "" 179 } 180 return strings.Split(host, "."), port 181 } 182 183 // URLsMatchStr is wrapper for URLsMatch, operating on strings instead of URLs. 184 func URLsMatchStr(glob string, target string) (bool, error) { 185 globURL, err := ParseSchemelessURL(glob) 186 if err != nil { 187 return false, err 188 } 189 targetURL, err := ParseSchemelessURL(target) 190 if err != nil { 191 return false, err 192 } 193 return URLsMatch(globURL, targetURL) 194 } 195 196 // URLsMatch checks whether the given target url matches the glob url, which may have 197 // glob wild cards in the host name. 198 // 199 // Examples: 200 // 201 // globURL=*.docker.io, targetURL=blah.docker.io => match 202 // globURL=*.docker.io, targetURL=not.right.io => no match 203 // 204 // Note that we don't support wildcards in ports and paths yet. 205 func URLsMatch(globURL *url.URL, targetURL *url.URL) (bool, error) { 206 globURLParts, globPort := SplitURL(globURL) 207 targetURLParts, targetPort := SplitURL(targetURL) 208 if globPort != targetPort { 209 // port doesn't match 210 return false, nil 211 } 212 if len(globURLParts) != len(targetURLParts) { 213 // host name does not have the same number of parts 214 return false, nil 215 } 216 if !strings.HasPrefix(targetURL.Path, globURL.Path) { 217 // the path of the credential must be a prefix 218 return false, nil 219 } 220 for k, globURLPart := range globURLParts { 221 targetURLPart := targetURLParts[k] 222 matched, err := filepath.Match(globURLPart, targetURLPart) 223 if err != nil { 224 return false, err 225 } 226 if !matched { 227 // glob mismatch for some part 228 return false, nil 229 } 230 } 231 // everything matches 232 return true, nil 233 } 234 235 // Lookup implements the DockerKeyring method for fetching credentials based on image name. 236 // Multiple credentials may be returned if there are multiple potentially valid credentials 237 // available. This allows for rotation. 238 func (dk *BasicDockerKeyring) Lookup(image string) ([]AuthConfig, bool) { 239 // range over the index as iterating over a map does not provide a predictable ordering 240 ret := []AuthConfig{} 241 for _, k := range dk.index { 242 // both k and image are schemeless URLs because even though schemes are allowed 243 // in the credential configurations, we remove them in Add. 244 if matched, _ := URLsMatchStr(k, image); matched { 245 ret = append(ret, dk.creds[k]...) 246 } 247 } 248 249 if len(ret) > 0 { 250 return ret, true 251 } 252 253 // Use credentials for the default registry if provided, and appropriate 254 if isDefaultRegistryMatch(image) { 255 if auth, ok := dk.creds[defaultRegistryHost]; ok { 256 return auth, true 257 } 258 } 259 260 return []AuthConfig{}, false 261 } 262 263 // Lookup implements the DockerKeyring method for fetching credentials 264 // based on image name. 265 func (dk *providersDockerKeyring) Lookup(image string) ([]AuthConfig, bool) { 266 keyring := &BasicDockerKeyring{} 267 268 for _, p := range dk.Providers { 269 keyring.Add(p.Provide(image)) 270 } 271 272 return keyring.Lookup(image) 273 } 274 275 // FakeKeyring a fake config credentials 276 type FakeKeyring struct { 277 auth []AuthConfig 278 ok bool 279 } 280 281 // Lookup implements the DockerKeyring method for fetching credentials based on image name 282 // return fake auth and ok 283 func (f *FakeKeyring) Lookup(image string) ([]AuthConfig, bool) { 284 return f.auth, f.ok 285 } 286 287 // UnionDockerKeyring delegates to a set of keyrings. 288 type UnionDockerKeyring []DockerKeyring 289 290 // Lookup implements the DockerKeyring method for fetching credentials based on image name. 291 // return each credentials 292 func (k UnionDockerKeyring) Lookup(image string) ([]AuthConfig, bool) { 293 authConfigs := []AuthConfig{} 294 for _, subKeyring := range k { 295 if subKeyring == nil { 296 continue 297 } 298 299 currAuthResults, _ := subKeyring.Lookup(image) 300 authConfigs = append(authConfigs, currAuthResults...) 301 } 302 303 return authConfigs, (len(authConfigs) > 0) 304 }