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  }