github.com/AbhinandanKurakure/podman/v3@v3.4.10/pkg/auth/auth.go (about) 1 package auth 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "strings" 11 12 imageAuth "github.com/containers/image/v5/pkg/docker/config" 13 "github.com/containers/image/v5/types" 14 dockerAPITypes "github.com/docker/docker/api/types" 15 "github.com/pkg/errors" 16 "github.com/sirupsen/logrus" 17 ) 18 19 type HeaderAuthName string 20 21 func (h HeaderAuthName) String() string { return string(h) } 22 23 // XRegistryAuthHeader is the key to the encoded registry authentication configuration in an http-request header. 24 // This header supports one registry per header occurrence. To support N registries provided N headers, one per registry. 25 // As of Docker API 1.40 and Libpod API 1.0.0, this header is supported by all endpoints. 26 const XRegistryAuthHeader HeaderAuthName = "X-Registry-Auth" 27 28 // XRegistryConfigHeader is the key to the encoded registry authentication configuration in an http-request header. 29 // This header supports N registries in one header via a Base64 encoded, JSON map. 30 // As of Docker API 1.40 and Libpod API 2.0.0, this header is supported by build endpoints. 31 const XRegistryConfigHeader HeaderAuthName = "X-Registry-Config" 32 33 // GetCredentials queries the http.Request for X-Registry-.* headers and extracts 34 // the necessary authentication information for libpod operations 35 func GetCredentials(r *http.Request) (*types.DockerAuthConfig, string, HeaderAuthName, error) { 36 has := func(key HeaderAuthName) bool { hdr, found := r.Header[string(key)]; return found && len(hdr) > 0 } 37 switch { 38 case has(XRegistryConfigHeader): 39 c, f, err := getConfigCredentials(r) 40 return c, f, XRegistryConfigHeader, err 41 case has(XRegistryAuthHeader): 42 c, f, err := getAuthCredentials(r) 43 return c, f, XRegistryAuthHeader, err 44 } 45 return nil, "", "", nil 46 } 47 48 // getConfigCredentials extracts one or more docker.AuthConfig from the request's 49 // header. An empty key will be used as default while a named registry will be 50 // returned as types.DockerAuthConfig 51 func getConfigCredentials(r *http.Request) (*types.DockerAuthConfig, string, error) { 52 var auth *types.DockerAuthConfig 53 configs := make(map[string]types.DockerAuthConfig) 54 55 for _, h := range r.Header[string(XRegistryConfigHeader)] { 56 param, err := base64.URLEncoding.DecodeString(h) 57 if err != nil { 58 return nil, "", errors.Wrapf(err, "failed to decode %q", XRegistryConfigHeader) 59 } 60 61 ac := make(map[string]dockerAPITypes.AuthConfig) 62 err = json.Unmarshal(param, &ac) 63 if err != nil { 64 return nil, "", errors.Wrapf(err, "failed to unmarshal %q", XRegistryConfigHeader) 65 } 66 67 for k, v := range ac { 68 configs[k] = dockerAuthToImageAuth(v) 69 } 70 } 71 72 // Empty key implies no registry given in API 73 if c, found := configs[""]; found { 74 auth = &c 75 } 76 77 // Override any default given above if specialized credentials provided 78 if registries, found := r.URL.Query()["registry"]; found { 79 for _, r := range registries { 80 for k, v := range configs { 81 if strings.Contains(k, r) { 82 v := v 83 auth = &v 84 break 85 } 86 } 87 if auth != nil { 88 break 89 } 90 } 91 92 if auth == nil { 93 logrus.Debugf("%q header found in request, but \"registry=%v\" query parameter not provided", 94 XRegistryConfigHeader, registries) 95 } else { 96 logrus.Debugf("%q header found in request for username %q", XRegistryConfigHeader, auth.Username) 97 } 98 } 99 100 authfile, err := authConfigsToAuthFile(configs) 101 return auth, authfile, err 102 } 103 104 // getAuthCredentials extracts one or more DockerAuthConfigs from the request's 105 // header. The header could specify a single-auth config in which case the 106 // first return value is set. In case of a multi-auth header, the contents are 107 // stored in a temporary auth file (2nd return value). Note that the auth file 108 // should be removed after usage. 109 func getAuthCredentials(r *http.Request) (*types.DockerAuthConfig, string, error) { 110 // First look for a multi-auth header (i.e., a map). 111 authConfigs, err := multiAuthHeader(r) 112 if err == nil { 113 authfile, err := authConfigsToAuthFile(authConfigs) 114 return nil, authfile, err 115 } 116 117 // Fallback to looking for a single-auth header (i.e., one config). 118 authConfigs, err = singleAuthHeader(r) 119 if err != nil { 120 return nil, "", err 121 } 122 var conf *types.DockerAuthConfig 123 for k := range authConfigs { 124 c := authConfigs[k] 125 conf = &c 126 break 127 } 128 return conf, "", nil 129 } 130 131 // Header builds the requested Authentication Header 132 func Header(sys *types.SystemContext, headerName HeaderAuthName, authfile, username, password string) (map[string]string, error) { 133 var ( 134 content string 135 err error 136 ) 137 switch headerName { 138 case XRegistryAuthHeader: 139 content, err = headerAuth(sys, authfile, username, password) 140 case XRegistryConfigHeader: 141 content, err = headerConfig(sys, authfile, username, password) 142 default: 143 err = fmt.Errorf("unsupported authentication header: %q", headerName) 144 } 145 if err != nil { 146 return nil, err 147 } 148 149 if len(content) > 0 { 150 return map[string]string{string(headerName): content}, nil 151 } 152 return nil, nil 153 } 154 155 // headerConfig returns a map with the XRegistryConfigHeader set which can 156 // conveniently be used in the http stack. 157 func headerConfig(sys *types.SystemContext, authfile, username, password string) (string, error) { 158 if sys == nil { 159 sys = &types.SystemContext{} 160 } 161 if authfile != "" { 162 sys.AuthFilePath = authfile 163 } 164 authConfigs, err := imageAuth.GetAllCredentials(sys) 165 if err != nil { 166 return "", err 167 } 168 169 if username != "" { 170 authConfigs[""] = types.DockerAuthConfig{ 171 Username: username, 172 Password: password, 173 } 174 } 175 176 if len(authConfigs) == 0 { 177 return "", nil 178 } 179 return encodeMultiAuthConfigs(authConfigs) 180 } 181 182 // headerAuth returns a base64 encoded map with the XRegistryAuthHeader set which can 183 // conveniently be used in the http stack. 184 func headerAuth(sys *types.SystemContext, authfile, username, password string) (string, error) { 185 if username != "" { 186 return encodeSingleAuthConfig(types.DockerAuthConfig{Username: username, Password: password}) 187 } 188 189 if sys == nil { 190 sys = &types.SystemContext{} 191 } 192 if authfile != "" { 193 sys.AuthFilePath = authfile 194 } 195 authConfigs, err := imageAuth.GetAllCredentials(sys) 196 if err != nil { 197 return "", err 198 } 199 return encodeMultiAuthConfigs(authConfigs) 200 } 201 202 // RemoveAuthfile is a convenience function that is meant to be called in a 203 // deferred statement. If non-empty, it removes the specified authfile and log 204 // errors. It's meant to reduce boilerplate code at call sites of 205 // `GetCredentials`. 206 func RemoveAuthfile(authfile string) { 207 if authfile == "" { 208 return 209 } 210 if err := os.Remove(authfile); err != nil { 211 logrus.Errorf("Error removing temporary auth file %q: %v", authfile, err) 212 } 213 } 214 215 // encodeSingleAuthConfig serializes the auth configuration as a base64 encoded JSON payload. 216 func encodeSingleAuthConfig(authConfig types.DockerAuthConfig) (string, error) { 217 conf := imageAuthToDockerAuth(authConfig) 218 buf, err := json.Marshal(conf) 219 if err != nil { 220 return "", err 221 } 222 return base64.URLEncoding.EncodeToString(buf), nil 223 } 224 225 // encodeMultiAuthConfigs serializes the auth configurations as a base64 encoded JSON payload. 226 func encodeMultiAuthConfigs(authConfigs map[string]types.DockerAuthConfig) (string, error) { 227 confs := make(map[string]dockerAPITypes.AuthConfig) 228 for registry, authConf := range authConfigs { 229 confs[registry] = imageAuthToDockerAuth(authConf) 230 } 231 buf, err := json.Marshal(confs) 232 if err != nil { 233 return "", err 234 } 235 return base64.URLEncoding.EncodeToString(buf), nil 236 } 237 238 // authConfigsToAuthFile stores the specified auth configs in a temporary files 239 // and returns its path. The file can later be used an auth file for contacting 240 // one or more container registries. If tmpDir is empty, the system's default 241 // TMPDIR will be used. 242 func authConfigsToAuthFile(authConfigs map[string]types.DockerAuthConfig) (string, error) { 243 // Initialize an empty temporary JSON file. 244 tmpFile, err := ioutil.TempFile("", "auth.json.") 245 if err != nil { 246 return "", err 247 } 248 if _, err := tmpFile.Write([]byte{'{', '}'}); err != nil { 249 return "", errors.Wrap(err, "error initializing temporary auth file") 250 } 251 if err := tmpFile.Close(); err != nil { 252 return "", errors.Wrap(err, "error closing temporary auth file") 253 } 254 authFilePath := tmpFile.Name() 255 256 // TODO: It would be nice if c/image could dump the map at once. 257 // 258 // Now use the c/image packages to store the credentials. It's battle 259 // tested, and we make sure to use the same code as the image backend. 260 sys := types.SystemContext{AuthFilePath: authFilePath} 261 for server, config := range authConfigs { 262 server = normalize(server) 263 264 // Note that we do not validate the credentials here. We assume 265 // that all credentials are valid. They'll be used on demand 266 // later. 267 if err := imageAuth.SetAuthentication(&sys, server, config.Username, config.Password); err != nil { 268 return "", errors.Wrapf(err, "error storing credentials in temporary auth file (server: %q, user: %q)", server, config.Username) 269 } 270 } 271 272 return authFilePath, nil 273 } 274 275 // normalize takes a server and removes the leading "http[s]://" prefix as well 276 // as removes path suffixes from docker registries. 277 func normalize(server string) string { 278 stripped := strings.TrimPrefix(server, "http://") 279 stripped = strings.TrimPrefix(stripped, "https://") 280 281 /// Normalize docker registries 282 if strings.HasPrefix(stripped, "index.docker.io/") || 283 strings.HasPrefix(stripped, "registry-1.docker.io/") || 284 strings.HasPrefix(stripped, "docker.io/") { 285 stripped = strings.SplitN(stripped, "/", 2)[0] 286 } 287 288 return stripped 289 } 290 291 // dockerAuthToImageAuth converts a docker auth config to one we're using 292 // internally from c/image. Note that the Docker types look slightly 293 // different, so we need to convert to be extra sure we're not running into 294 // undesired side-effects when unmarhalling directly to our types. 295 func dockerAuthToImageAuth(authConfig dockerAPITypes.AuthConfig) types.DockerAuthConfig { 296 return types.DockerAuthConfig{ 297 Username: authConfig.Username, 298 Password: authConfig.Password, 299 IdentityToken: authConfig.IdentityToken, 300 } 301 } 302 303 // reverse conversion of `dockerAuthToImageAuth`. 304 func imageAuthToDockerAuth(authConfig types.DockerAuthConfig) dockerAPITypes.AuthConfig { 305 return dockerAPITypes.AuthConfig{ 306 Username: authConfig.Username, 307 Password: authConfig.Password, 308 IdentityToken: authConfig.IdentityToken, 309 } 310 } 311 312 // singleAuthHeader extracts a DockerAuthConfig from the request's header. 313 // The header content is a single DockerAuthConfig. 314 func singleAuthHeader(r *http.Request) (map[string]types.DockerAuthConfig, error) { 315 authHeader := r.Header.Get(string(XRegistryAuthHeader)) 316 authConfig := dockerAPITypes.AuthConfig{} 317 // Accept "null" and handle it as empty value for compatibility reason with Docker. 318 // Some java docker clients pass this value, e.g. this one used in Eclipse. 319 if len(authHeader) > 0 && authHeader != "null" { 320 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader)) 321 if err := json.NewDecoder(authJSON).Decode(&authConfig); err != nil { 322 return nil, err 323 } 324 } 325 authConfigs := make(map[string]types.DockerAuthConfig) 326 authConfigs["0"] = dockerAuthToImageAuth(authConfig) 327 return authConfigs, nil 328 } 329 330 // multiAuthHeader extracts a DockerAuthConfig from the request's header. 331 // The header content is a map[string]DockerAuthConfigs. 332 func multiAuthHeader(r *http.Request) (map[string]types.DockerAuthConfig, error) { 333 authHeader := r.Header.Get(string(XRegistryAuthHeader)) 334 // Accept "null" and handle it as empty value for compatibility reason with Docker. 335 // Some java docker clients pass this value, e.g. this one used in Eclipse. 336 if len(authHeader) == 0 || authHeader == "null" { 337 return nil, nil 338 } 339 340 dockerAuthConfigs := make(map[string]dockerAPITypes.AuthConfig) 341 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader)) 342 if err := json.NewDecoder(authJSON).Decode(&dockerAuthConfigs); err != nil { 343 return nil, err 344 } 345 346 // Now convert to the internal types. 347 authConfigs := make(map[string]types.DockerAuthConfig) 348 for server := range dockerAuthConfigs { 349 authConfigs[server] = dockerAuthToImageAuth(dockerAuthConfigs[server]) 350 } 351 return authConfigs, nil 352 }