github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/cliconfig/config.go (about)

     1  package cliconfig
     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/docker/pkg/homedir"
    14  	"github.com/docker/engine-api/types"
    15  )
    16  
    17  const (
    18  	// ConfigFileName is the name of config file
    19  	ConfigFileName = "config.json"
    20  	configFileDir  = ".docker"
    21  	oldConfigfile  = ".dockercfg"
    22  
    23  	// This constant is only used for really old config files when the
    24  	// URL wasn't saved as part of the config file and it was just
    25  	// assumed to be this value.
    26  	defaultIndexserver = "https://index.docker.io/v1/"
    27  )
    28  
    29  var (
    30  	configDir = os.Getenv("DOCKER_CONFIG")
    31  )
    32  
    33  func init() {
    34  	if configDir == "" {
    35  		configDir = filepath.Join(homedir.Get(), configFileDir)
    36  	}
    37  }
    38  
    39  // ConfigDir returns the directory the configuration file is stored in
    40  func ConfigDir() string {
    41  	return configDir
    42  }
    43  
    44  // SetConfigDir sets the directory the configuration file is stored in
    45  func SetConfigDir(dir string) {
    46  	configDir = dir
    47  }
    48  
    49  // ConfigFile ~/.docker/config.json file info
    50  type ConfigFile struct {
    51  	AuthConfigs      map[string]types.AuthConfig `json:"auths"`
    52  	HTTPHeaders      map[string]string           `json:"HttpHeaders,omitempty"`
    53  	PsFormat         string                      `json:"psFormat,omitempty"`
    54  	ImagesFormat     string                      `json:"imagesFormat,omitempty"`
    55  	DetachKeys       string                      `json:"detachKeys,omitempty"`
    56  	CredentialsStore string                      `json:"credsStore,omitempty"`
    57  	filename         string                      // Note: not serialized - for internal use only
    58  }
    59  
    60  // NewConfigFile initializes an empty configuration file for the given filename 'fn'
    61  func NewConfigFile(fn string) *ConfigFile {
    62  	return &ConfigFile{
    63  		AuthConfigs: make(map[string]types.AuthConfig),
    64  		HTTPHeaders: make(map[string]string),
    65  		filename:    fn,
    66  	}
    67  }
    68  
    69  // LegacyLoadFromReader reads the non-nested configuration data given and sets up the
    70  // auth config information with given directory and populates the receiver object
    71  func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
    72  	b, err := ioutil.ReadAll(configData)
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
    78  		arr := strings.Split(string(b), "\n")
    79  		if len(arr) < 2 {
    80  			return fmt.Errorf("The Auth config file is empty")
    81  		}
    82  		authConfig := types.AuthConfig{}
    83  		origAuth := strings.Split(arr[0], " = ")
    84  		if len(origAuth) != 2 {
    85  			return fmt.Errorf("Invalid Auth config file")
    86  		}
    87  		authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
    88  		if err != nil {
    89  			return err
    90  		}
    91  		authConfig.ServerAddress = defaultIndexserver
    92  		configFile.AuthConfigs[defaultIndexserver] = authConfig
    93  	} else {
    94  		for k, authConfig := range configFile.AuthConfigs {
    95  			authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
    96  			if err != nil {
    97  				return err
    98  			}
    99  			authConfig.Auth = ""
   100  			authConfig.ServerAddress = k
   101  			configFile.AuthConfigs[k] = authConfig
   102  		}
   103  	}
   104  	return nil
   105  }
   106  
   107  // LoadFromReader reads the configuration data given and sets up the auth config
   108  // information with given directory and populates the receiver object
   109  func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
   110  	if err := json.NewDecoder(configData).Decode(&configFile); err != nil {
   111  		return err
   112  	}
   113  	var err error
   114  	for addr, ac := range configFile.AuthConfigs {
   115  		ac.Username, ac.Password, err = decodeAuth(ac.Auth)
   116  		if err != nil {
   117  			return err
   118  		}
   119  		ac.Auth = ""
   120  		ac.ServerAddress = addr
   121  		configFile.AuthConfigs[addr] = ac
   122  	}
   123  	return nil
   124  }
   125  
   126  // ContainsAuth returns whether there is authentication configured
   127  // in this file or not.
   128  func (configFile *ConfigFile) ContainsAuth() bool {
   129  	return configFile.CredentialsStore != "" ||
   130  		(configFile.AuthConfigs != nil && len(configFile.AuthConfigs) > 0)
   131  }
   132  
   133  // LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
   134  // a non-nested reader
   135  func LegacyLoadFromReader(configData io.Reader) (*ConfigFile, error) {
   136  	configFile := ConfigFile{
   137  		AuthConfigs: make(map[string]types.AuthConfig),
   138  	}
   139  	err := configFile.LegacyLoadFromReader(configData)
   140  	return &configFile, err
   141  }
   142  
   143  // LoadFromReader is a convenience function that creates a ConfigFile object from
   144  // a reader
   145  func LoadFromReader(configData io.Reader) (*ConfigFile, error) {
   146  	configFile := ConfigFile{
   147  		AuthConfigs: make(map[string]types.AuthConfig),
   148  	}
   149  	err := configFile.LoadFromReader(configData)
   150  	return &configFile, err
   151  }
   152  
   153  // Load reads the configuration files in the given directory, and sets up
   154  // the auth config information and returns values.
   155  // FIXME: use the internal golang config parser
   156  func Load(configDir string) (*ConfigFile, error) {
   157  	if configDir == "" {
   158  		configDir = ConfigDir()
   159  	}
   160  
   161  	configFile := ConfigFile{
   162  		AuthConfigs: make(map[string]types.AuthConfig),
   163  		filename:    filepath.Join(configDir, ConfigFileName),
   164  	}
   165  
   166  	// Try happy path first - latest config file
   167  	if _, err := os.Stat(configFile.filename); err == nil {
   168  		file, err := os.Open(configFile.filename)
   169  		if err != nil {
   170  			return &configFile, fmt.Errorf("%s - %v", configFile.filename, err)
   171  		}
   172  		defer file.Close()
   173  		err = configFile.LoadFromReader(file)
   174  		if err != nil {
   175  			err = fmt.Errorf("%s - %v", configFile.filename, err)
   176  		}
   177  		return &configFile, err
   178  	} else if !os.IsNotExist(err) {
   179  		// if file is there but we can't stat it for any reason other
   180  		// than it doesn't exist then stop
   181  		return &configFile, fmt.Errorf("%s - %v", configFile.filename, err)
   182  	}
   183  
   184  	// Can't find latest config file so check for the old one
   185  	confFile := filepath.Join(homedir.Get(), oldConfigfile)
   186  	if _, err := os.Stat(confFile); err != nil {
   187  		return &configFile, nil //missing file is not an error
   188  	}
   189  	file, err := os.Open(confFile)
   190  	if err != nil {
   191  		return &configFile, fmt.Errorf("%s - %v", confFile, err)
   192  	}
   193  	defer file.Close()
   194  	err = configFile.LegacyLoadFromReader(file)
   195  	if err != nil {
   196  		return &configFile, fmt.Errorf("%s - %v", confFile, err)
   197  	}
   198  
   199  	if configFile.HTTPHeaders == nil {
   200  		configFile.HTTPHeaders = map[string]string{}
   201  	}
   202  	return &configFile, nil
   203  }
   204  
   205  // SaveToWriter encodes and writes out all the authorization information to
   206  // the given writer
   207  func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
   208  	// Encode sensitive data into a new/temp struct
   209  	tmpAuthConfigs := make(map[string]types.AuthConfig, len(configFile.AuthConfigs))
   210  	for k, authConfig := range configFile.AuthConfigs {
   211  		authCopy := authConfig
   212  		// encode and save the authstring, while blanking out the original fields
   213  		authCopy.Auth = encodeAuth(&authCopy)
   214  		authCopy.Username = ""
   215  		authCopy.Password = ""
   216  		authCopy.ServerAddress = ""
   217  		tmpAuthConfigs[k] = authCopy
   218  	}
   219  
   220  	saveAuthConfigs := configFile.AuthConfigs
   221  	configFile.AuthConfigs = tmpAuthConfigs
   222  	defer func() { configFile.AuthConfigs = saveAuthConfigs }()
   223  
   224  	data, err := json.MarshalIndent(configFile, "", "\t")
   225  	if err != nil {
   226  		return err
   227  	}
   228  	_, err = writer.Write(data)
   229  	return err
   230  }
   231  
   232  // Save encodes and writes out all the authorization information
   233  func (configFile *ConfigFile) Save() error {
   234  	if configFile.Filename() == "" {
   235  		return fmt.Errorf("Can't save config with empty filename")
   236  	}
   237  
   238  	if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil {
   239  		return err
   240  	}
   241  	f, err := os.OpenFile(configFile.filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   242  	if err != nil {
   243  		return err
   244  	}
   245  	defer f.Close()
   246  	return configFile.SaveToWriter(f)
   247  }
   248  
   249  // Filename returns the name of the configuration file
   250  func (configFile *ConfigFile) Filename() string {
   251  	return configFile.filename
   252  }
   253  
   254  // encodeAuth creates a base64 encoded string to containing authorization information
   255  func encodeAuth(authConfig *types.AuthConfig) string {
   256  	if authConfig.Username == "" && authConfig.Password == "" {
   257  		return ""
   258  	}
   259  
   260  	authStr := authConfig.Username + ":" + authConfig.Password
   261  	msg := []byte(authStr)
   262  	encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
   263  	base64.StdEncoding.Encode(encoded, msg)
   264  	return string(encoded)
   265  }
   266  
   267  // decodeAuth decodes a base64 encoded string and returns username and password
   268  func decodeAuth(authStr string) (string, string, error) {
   269  	if authStr == "" {
   270  		return "", "", nil
   271  	}
   272  
   273  	decLen := base64.StdEncoding.DecodedLen(len(authStr))
   274  	decoded := make([]byte, decLen)
   275  	authByte := []byte(authStr)
   276  	n, err := base64.StdEncoding.Decode(decoded, authByte)
   277  	if err != nil {
   278  		return "", "", err
   279  	}
   280  	if n > decLen {
   281  		return "", "", fmt.Errorf("Something went wrong decoding auth config")
   282  	}
   283  	arr := strings.SplitN(string(decoded), ":", 2)
   284  	if len(arr) != 2 {
   285  		return "", "", fmt.Errorf("Invalid auth configuration file")
   286  	}
   287  	password := strings.Trim(arr[1], "\x00")
   288  	return arr[0], password, nil
   289  }