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