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