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