github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/config/configfile/file.go (about)

     1  package configfile
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/docker/cli/cli/config/credentials"
    14  	"github.com/docker/cli/opts"
    15  	"github.com/docker/docker/api/types"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  const (
    20  	// This constant is only used for really old config files when the
    21  	// URL wasn't saved as part of the config file and it was just
    22  	// assumed to be this value.
    23  	defaultIndexServer = "https://index.docker.io/v1/"
    24  )
    25  
    26  // ConfigFile ~/.docker/config.json file info
    27  type ConfigFile struct {
    28  	AuthConfigs          map[string]types.AuthConfig `json:"auths"`
    29  	HTTPHeaders          map[string]string           `json:"HttpHeaders,omitempty"`
    30  	PsFormat             string                      `json:"psFormat,omitempty"`
    31  	ImagesFormat         string                      `json:"imagesFormat,omitempty"`
    32  	NetworksFormat       string                      `json:"networksFormat,omitempty"`
    33  	PluginsFormat        string                      `json:"pluginsFormat,omitempty"`
    34  	VolumesFormat        string                      `json:"volumesFormat,omitempty"`
    35  	StatsFormat          string                      `json:"statsFormat,omitempty"`
    36  	DetachKeys           string                      `json:"detachKeys,omitempty"`
    37  	CredentialsStore     string                      `json:"credsStore,omitempty"`
    38  	CredentialHelpers    map[string]string           `json:"credHelpers,omitempty"`
    39  	Filename             string                      `json:"-"` // Note: for internal use only
    40  	ServiceInspectFormat string                      `json:"serviceInspectFormat,omitempty"`
    41  	ServicesFormat       string                      `json:"servicesFormat,omitempty"`
    42  	TasksFormat          string                      `json:"tasksFormat,omitempty"`
    43  	SecretFormat         string                      `json:"secretFormat,omitempty"`
    44  	ConfigFormat         string                      `json:"configFormat,omitempty"`
    45  	NodesFormat          string                      `json:"nodesFormat,omitempty"`
    46  	PruneFilters         []string                    `json:"pruneFilters,omitempty"`
    47  	Proxies              map[string]ProxyConfig      `json:"proxies,omitempty"`
    48  	Experimental         string                      `json:"experimental,omitempty"`
    49  	StackOrchestrator    string                      `json:"stackOrchestrator,omitempty"`
    50  	Kubernetes           *KubernetesConfig           `json:"kubernetes,omitempty"`
    51  }
    52  
    53  // ProxyConfig contains proxy configuration settings
    54  type ProxyConfig struct {
    55  	HTTPProxy  string `json:"httpProxy,omitempty"`
    56  	HTTPSProxy string `json:"httpsProxy,omitempty"`
    57  	NoProxy    string `json:"noProxy,omitempty"`
    58  	FTPProxy   string `json:"ftpProxy,omitempty"`
    59  }
    60  
    61  // KubernetesConfig contains Kubernetes orchestrator settings
    62  type KubernetesConfig struct {
    63  	AllNamespaces string `json:"allNamespaces,omitempty"`
    64  }
    65  
    66  // New initializes an empty configuration file for the given filename 'fn'
    67  func New(fn string) *ConfigFile {
    68  	return &ConfigFile{
    69  		AuthConfigs: make(map[string]types.AuthConfig),
    70  		HTTPHeaders: make(map[string]string),
    71  		Filename:    fn,
    72  	}
    73  }
    74  
    75  // LegacyLoadFromReader reads the non-nested configuration data given and sets up the
    76  // auth config information with given directory and populates the receiver object
    77  func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
    78  	b, err := ioutil.ReadAll(configData)
    79  	if err != nil {
    80  		return err
    81  	}
    82  
    83  	if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
    84  		arr := strings.Split(string(b), "\n")
    85  		if len(arr) < 2 {
    86  			return errors.Errorf("The Auth config file is empty")
    87  		}
    88  		authConfig := types.AuthConfig{}
    89  		origAuth := strings.Split(arr[0], " = ")
    90  		if len(origAuth) != 2 {
    91  			return errors.Errorf("Invalid Auth config file")
    92  		}
    93  		authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
    94  		if err != nil {
    95  			return err
    96  		}
    97  		authConfig.ServerAddress = defaultIndexServer
    98  		configFile.AuthConfigs[defaultIndexServer] = authConfig
    99  	} else {
   100  		for k, authConfig := range configFile.AuthConfigs {
   101  			authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
   102  			if err != nil {
   103  				return err
   104  			}
   105  			authConfig.Auth = ""
   106  			authConfig.ServerAddress = k
   107  			configFile.AuthConfigs[k] = authConfig
   108  		}
   109  	}
   110  	return nil
   111  }
   112  
   113  // LoadFromReader reads the configuration data given and sets up the auth config
   114  // information with given directory and populates the receiver object
   115  func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
   116  	if err := json.NewDecoder(configData).Decode(&configFile); err != nil {
   117  		return err
   118  	}
   119  	var err error
   120  	for addr, ac := range configFile.AuthConfigs {
   121  		ac.Username, ac.Password, err = decodeAuth(ac.Auth)
   122  		if err != nil {
   123  			return err
   124  		}
   125  		ac.Auth = ""
   126  		ac.ServerAddress = addr
   127  		configFile.AuthConfigs[addr] = ac
   128  	}
   129  	return checkKubernetesConfiguration(configFile.Kubernetes)
   130  }
   131  
   132  // ContainsAuth returns whether there is authentication configured
   133  // in this file or not.
   134  func (configFile *ConfigFile) ContainsAuth() bool {
   135  	return configFile.CredentialsStore != "" ||
   136  		len(configFile.CredentialHelpers) > 0 ||
   137  		len(configFile.AuthConfigs) > 0
   138  }
   139  
   140  // GetAuthConfigs returns the mapping of repo to auth configuration
   141  func (configFile *ConfigFile) GetAuthConfigs() map[string]types.AuthConfig {
   142  	return configFile.AuthConfigs
   143  }
   144  
   145  // SaveToWriter encodes and writes out all the authorization information to
   146  // the given writer
   147  func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
   148  	// Encode sensitive data into a new/temp struct
   149  	tmpAuthConfigs := make(map[string]types.AuthConfig, len(configFile.AuthConfigs))
   150  	for k, authConfig := range configFile.AuthConfigs {
   151  		authCopy := authConfig
   152  		// encode and save the authstring, while blanking out the original fields
   153  		authCopy.Auth = encodeAuth(&authCopy)
   154  		authCopy.Username = ""
   155  		authCopy.Password = ""
   156  		authCopy.ServerAddress = ""
   157  		tmpAuthConfigs[k] = authCopy
   158  	}
   159  
   160  	saveAuthConfigs := configFile.AuthConfigs
   161  	configFile.AuthConfigs = tmpAuthConfigs
   162  	defer func() { configFile.AuthConfigs = saveAuthConfigs }()
   163  
   164  	data, err := json.MarshalIndent(configFile, "", "\t")
   165  	if err != nil {
   166  		return err
   167  	}
   168  	_, err = writer.Write(data)
   169  	return err
   170  }
   171  
   172  // Save encodes and writes out all the authorization information
   173  func (configFile *ConfigFile) Save() error {
   174  	if configFile.Filename == "" {
   175  		return errors.Errorf("Can't save config with empty filename")
   176  	}
   177  
   178  	dir := filepath.Dir(configFile.Filename)
   179  	if err := os.MkdirAll(dir, 0700); err != nil {
   180  		return err
   181  	}
   182  	temp, err := ioutil.TempFile(dir, filepath.Base(configFile.Filename))
   183  	if err != nil {
   184  		return err
   185  	}
   186  	err = configFile.SaveToWriter(temp)
   187  	temp.Close()
   188  	if err != nil {
   189  		os.Remove(temp.Name())
   190  		return err
   191  	}
   192  	return os.Rename(temp.Name(), configFile.Filename)
   193  }
   194  
   195  // ParseProxyConfig computes proxy configuration by retrieving the config for the provided host and
   196  // then checking this against any environment variables provided to the container
   197  func (configFile *ConfigFile) ParseProxyConfig(host string, runOpts []string) map[string]*string {
   198  	var cfgKey string
   199  
   200  	if _, ok := configFile.Proxies[host]; !ok {
   201  		cfgKey = "default"
   202  	} else {
   203  		cfgKey = host
   204  	}
   205  
   206  	config := configFile.Proxies[cfgKey]
   207  	permitted := map[string]*string{
   208  		"HTTP_PROXY":  &config.HTTPProxy,
   209  		"HTTPS_PROXY": &config.HTTPSProxy,
   210  		"NO_PROXY":    &config.NoProxy,
   211  		"FTP_PROXY":   &config.FTPProxy,
   212  	}
   213  	m := opts.ConvertKVStringsToMapWithNil(runOpts)
   214  	for k := range permitted {
   215  		if *permitted[k] == "" {
   216  			continue
   217  		}
   218  		if _, ok := m[k]; !ok {
   219  			m[k] = permitted[k]
   220  		}
   221  		if _, ok := m[strings.ToLower(k)]; !ok {
   222  			m[strings.ToLower(k)] = permitted[k]
   223  		}
   224  	}
   225  	return m
   226  }
   227  
   228  // encodeAuth creates a base64 encoded string to containing authorization information
   229  func encodeAuth(authConfig *types.AuthConfig) string {
   230  	if authConfig.Username == "" && authConfig.Password == "" {
   231  		return ""
   232  	}
   233  
   234  	authStr := authConfig.Username + ":" + authConfig.Password
   235  	msg := []byte(authStr)
   236  	encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
   237  	base64.StdEncoding.Encode(encoded, msg)
   238  	return string(encoded)
   239  }
   240  
   241  // decodeAuth decodes a base64 encoded string and returns username and password
   242  func decodeAuth(authStr string) (string, string, error) {
   243  	if authStr == "" {
   244  		return "", "", nil
   245  	}
   246  
   247  	decLen := base64.StdEncoding.DecodedLen(len(authStr))
   248  	decoded := make([]byte, decLen)
   249  	authByte := []byte(authStr)
   250  	n, err := base64.StdEncoding.Decode(decoded, authByte)
   251  	if err != nil {
   252  		return "", "", err
   253  	}
   254  	if n > decLen {
   255  		return "", "", errors.Errorf("Something went wrong decoding auth config")
   256  	}
   257  	arr := strings.SplitN(string(decoded), ":", 2)
   258  	if len(arr) != 2 {
   259  		return "", "", errors.Errorf("Invalid auth configuration file")
   260  	}
   261  	password := strings.Trim(arr[1], "\x00")
   262  	return arr[0], password, nil
   263  }
   264  
   265  // GetCredentialsStore returns a new credentials store from the settings in the
   266  // configuration file
   267  func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store {
   268  	if helper := getConfiguredCredentialStore(configFile, registryHostname); helper != "" {
   269  		return newNativeStore(configFile, helper)
   270  	}
   271  	return credentials.NewFileStore(configFile)
   272  }
   273  
   274  // var for unit testing.
   275  var newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
   276  	return credentials.NewNativeStore(configFile, helperSuffix)
   277  }
   278  
   279  // GetAuthConfig for a repository from the credential store
   280  func (configFile *ConfigFile) GetAuthConfig(registryHostname string) (types.AuthConfig, error) {
   281  	return configFile.GetCredentialsStore(registryHostname).Get(registryHostname)
   282  }
   283  
   284  // getConfiguredCredentialStore returns the credential helper configured for the
   285  // given registry, the default credsStore, or the empty string if neither are
   286  // configured.
   287  func getConfiguredCredentialStore(c *ConfigFile, registryHostname string) string {
   288  	if c.CredentialHelpers != nil && registryHostname != "" {
   289  		if helper, exists := c.CredentialHelpers[registryHostname]; exists {
   290  			return helper
   291  		}
   292  	}
   293  	return c.CredentialsStore
   294  }
   295  
   296  // GetAllCredentials returns all of the credentials stored in all of the
   297  // configured credential stores.
   298  func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig, error) {
   299  	auths := make(map[string]types.AuthConfig)
   300  	addAll := func(from map[string]types.AuthConfig) {
   301  		for reg, ac := range from {
   302  			auths[reg] = ac
   303  		}
   304  	}
   305  
   306  	defaultStore := configFile.GetCredentialsStore("")
   307  	newAuths, err := defaultStore.GetAll()
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  	addAll(newAuths)
   312  
   313  	// Auth configs from a registry-specific helper should override those from the default store.
   314  	for registryHostname := range configFile.CredentialHelpers {
   315  		newAuth, err := configFile.GetAuthConfig(registryHostname)
   316  		if err != nil {
   317  			return nil, err
   318  		}
   319  		auths[registryHostname] = newAuth
   320  	}
   321  	return auths, nil
   322  }
   323  
   324  // GetFilename returns the file name that this config file is based on.
   325  func (configFile *ConfigFile) GetFilename() string {
   326  	return configFile.Filename
   327  }
   328  
   329  func checkKubernetesConfiguration(kubeConfig *KubernetesConfig) error {
   330  	if kubeConfig == nil {
   331  		return nil
   332  	}
   333  	switch kubeConfig.AllNamespaces {
   334  	case "":
   335  	case "enabled":
   336  	case "disabled":
   337  	default:
   338  		return fmt.Errorf("invalid 'kubernetes.allNamespaces' value, should be 'enabled' or 'disabled': %s", kubeConfig.AllNamespaces)
   339  	}
   340  	return nil
   341  }