2015-04-22 08:06:58 -04:00
|
|
|
package cliconfig
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/docker/docker/pkg/homedir"
|
2015-06-11 14:29:29 -04:00
|
|
|
"github.com/docker/docker/pkg/system"
|
2015-04-22 08:06:58 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2015-07-30 17:36:53 -04:00
|
|
|
// ConfigFileName is the name of config file
|
2015-07-20 18:39:07 -04:00
|
|
|
ConfigFileName = "config.json"
|
|
|
|
oldConfigfile = ".dockercfg"
|
2015-04-22 08:06:58 -04:00
|
|
|
|
|
|
|
// This constant is only used for really old config files when the
|
|
|
|
// URL wasn't saved as part of the config file and it was just
|
|
|
|
// assumed to be this value.
|
2015-07-20 18:39:07 -04:00
|
|
|
defaultIndexserver = "https://index.docker.io/v1/"
|
2015-04-22 08:06:58 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2015-07-20 18:39:07 -04:00
|
|
|
configDir = os.Getenv("DOCKER_CONFIG")
|
2015-04-22 08:06:58 -04:00
|
|
|
)
|
|
|
|
|
2015-04-28 11:00:18 -04:00
|
|
|
func init() {
|
|
|
|
if configDir == "" {
|
|
|
|
configDir = filepath.Join(homedir.Get(), ".docker")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-20 18:39:07 -04:00
|
|
|
// ConfigDir returns the directory the configuration file is stored in
|
2015-04-28 11:00:18 -04:00
|
|
|
func ConfigDir() string {
|
|
|
|
return configDir
|
|
|
|
}
|
|
|
|
|
2015-07-20 18:39:07 -04:00
|
|
|
// SetConfigDir sets the directory the configuration file is stored in
|
2015-04-28 11:00:18 -04:00
|
|
|
func SetConfigDir(dir string) {
|
|
|
|
configDir = dir
|
|
|
|
}
|
|
|
|
|
2015-07-20 18:39:07 -04:00
|
|
|
// AuthConfig contains authorization information for connecting to a Registry
|
2015-04-22 08:06:58 -04:00
|
|
|
type AuthConfig struct {
|
|
|
|
Username string `json:"username,omitempty"`
|
|
|
|
Password string `json:"password,omitempty"`
|
|
|
|
Auth string `json:"auth"`
|
|
|
|
Email string `json:"email"`
|
|
|
|
ServerAddress string `json:"serveraddress,omitempty"`
|
|
|
|
}
|
|
|
|
|
2015-07-20 18:39:07 -04:00
|
|
|
// ConfigFile ~/.docker/config.json file info
|
2015-04-22 08:06:58 -04:00
|
|
|
type ConfigFile struct {
|
|
|
|
AuthConfigs map[string]AuthConfig `json:"auths"`
|
2015-07-20 18:39:07 -04:00
|
|
|
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
|
2015-05-01 17:23:27 -04:00
|
|
|
PsFormat string `json:"psFormat,omitempty"`
|
2015-04-22 08:06:58 -04:00
|
|
|
filename string // Note: not serialized - for internal use only
|
|
|
|
}
|
|
|
|
|
2015-07-20 18:39:07 -04:00
|
|
|
// NewConfigFile initilizes an empty configuration file for the given filename 'fn'
|
2015-04-22 08:06:58 -04:00
|
|
|
func NewConfigFile(fn string) *ConfigFile {
|
|
|
|
return &ConfigFile{
|
|
|
|
AuthConfigs: make(map[string]AuthConfig),
|
2015-07-20 18:39:07 -04:00
|
|
|
HTTPHeaders: make(map[string]string),
|
2015-04-22 08:06:58 -04:00
|
|
|
filename: fn,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-20 18:39:07 -04:00
|
|
|
// Load reads the configuration files in the given directory, and sets up
|
|
|
|
// the auth config information and return values.
|
2015-04-22 08:06:58 -04:00
|
|
|
// FIXME: use the internal golang config parser
|
|
|
|
func Load(configDir string) (*ConfigFile, error) {
|
|
|
|
if configDir == "" {
|
2015-04-28 11:00:18 -04:00
|
|
|
configDir = ConfigDir()
|
2015-04-22 08:06:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
configFile := ConfigFile{
|
|
|
|
AuthConfigs: make(map[string]AuthConfig),
|
2015-07-20 18:39:07 -04:00
|
|
|
filename: filepath.Join(configDir, ConfigFileName),
|
2015-04-22 08:06:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Try happy path first - latest config file
|
|
|
|
if _, err := os.Stat(configFile.filename); err == nil {
|
|
|
|
file, err := os.Open(configFile.filename)
|
|
|
|
if err != nil {
|
|
|
|
return &configFile, err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
if err := json.NewDecoder(file).Decode(&configFile); err != nil {
|
|
|
|
return &configFile, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for addr, ac := range configFile.AuthConfigs {
|
|
|
|
ac.Username, ac.Password, err = DecodeAuth(ac.Auth)
|
|
|
|
if err != nil {
|
|
|
|
return &configFile, err
|
|
|
|
}
|
|
|
|
ac.Auth = ""
|
|
|
|
ac.ServerAddress = addr
|
|
|
|
configFile.AuthConfigs[addr] = ac
|
|
|
|
}
|
|
|
|
|
|
|
|
return &configFile, nil
|
|
|
|
} else if !os.IsNotExist(err) {
|
|
|
|
// if file is there but we can't stat it for any reason other
|
|
|
|
// than it doesn't exist then stop
|
|
|
|
return &configFile, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Can't find latest config file so check for the old one
|
2015-07-20 18:39:07 -04:00
|
|
|
confFile := filepath.Join(homedir.Get(), oldConfigfile)
|
2015-04-22 08:06:58 -04:00
|
|
|
if _, err := os.Stat(confFile); err != nil {
|
|
|
|
return &configFile, nil //missing file is not an error
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := ioutil.ReadFile(confFile)
|
|
|
|
if err != nil {
|
|
|
|
return &configFile, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
|
|
|
|
arr := strings.Split(string(b), "\n")
|
|
|
|
if len(arr) < 2 {
|
|
|
|
return &configFile, fmt.Errorf("The Auth config file is empty")
|
|
|
|
}
|
|
|
|
authConfig := AuthConfig{}
|
|
|
|
origAuth := strings.Split(arr[0], " = ")
|
|
|
|
if len(origAuth) != 2 {
|
|
|
|
return &configFile, fmt.Errorf("Invalid Auth config file")
|
|
|
|
}
|
|
|
|
authConfig.Username, authConfig.Password, err = DecodeAuth(origAuth[1])
|
|
|
|
if err != nil {
|
|
|
|
return &configFile, err
|
|
|
|
}
|
|
|
|
origEmail := strings.Split(arr[1], " = ")
|
|
|
|
if len(origEmail) != 2 {
|
|
|
|
return &configFile, fmt.Errorf("Invalid Auth config file")
|
|
|
|
}
|
|
|
|
authConfig.Email = origEmail[1]
|
2015-07-20 18:39:07 -04:00
|
|
|
authConfig.ServerAddress = defaultIndexserver
|
|
|
|
configFile.AuthConfigs[defaultIndexserver] = authConfig
|
2015-04-22 08:06:58 -04:00
|
|
|
} else {
|
|
|
|
for k, authConfig := range configFile.AuthConfigs {
|
|
|
|
authConfig.Username, authConfig.Password, err = DecodeAuth(authConfig.Auth)
|
|
|
|
if err != nil {
|
|
|
|
return &configFile, err
|
|
|
|
}
|
|
|
|
authConfig.Auth = ""
|
|
|
|
authConfig.ServerAddress = k
|
|
|
|
configFile.AuthConfigs[k] = authConfig
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &configFile, nil
|
|
|
|
}
|
|
|
|
|
2015-07-20 18:39:07 -04:00
|
|
|
// Save encodes and writes out all the authorization information
|
2015-04-22 08:06:58 -04:00
|
|
|
func (configFile *ConfigFile) Save() error {
|
|
|
|
// Encode sensitive data into a new/temp struct
|
|
|
|
tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs))
|
|
|
|
for k, authConfig := range configFile.AuthConfigs {
|
|
|
|
authCopy := authConfig
|
2015-07-20 18:39:07 -04:00
|
|
|
// encode and save the authstring, while blanking out the original fields
|
2015-04-22 08:06:58 -04:00
|
|
|
authCopy.Auth = EncodeAuth(&authCopy)
|
|
|
|
authCopy.Username = ""
|
|
|
|
authCopy.Password = ""
|
|
|
|
authCopy.ServerAddress = ""
|
|
|
|
tmpAuthConfigs[k] = authCopy
|
|
|
|
}
|
|
|
|
|
|
|
|
saveAuthConfigs := configFile.AuthConfigs
|
|
|
|
configFile.AuthConfigs = tmpAuthConfigs
|
|
|
|
defer func() { configFile.AuthConfigs = saveAuthConfigs }()
|
|
|
|
|
|
|
|
data, err := json.MarshalIndent(configFile, "", "\t")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-06-11 14:29:29 -04:00
|
|
|
if err := system.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil {
|
2015-04-22 08:06:58 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-04-26 12:50:25 -04:00
|
|
|
if err := ioutil.WriteFile(configFile.filename, data, 0600); err != nil {
|
2015-04-22 08:06:58 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-07-20 18:39:07 -04:00
|
|
|
// Filename returns the name of the configuration file
|
|
|
|
func (configFile *ConfigFile) Filename() string {
|
|
|
|
return configFile.filename
|
2015-04-22 08:06:58 -04:00
|
|
|
}
|
|
|
|
|
2015-07-20 18:39:07 -04:00
|
|
|
// EncodeAuth creates a base64 encoded string to containing authorization information
|
2015-04-22 08:06:58 -04:00
|
|
|
func EncodeAuth(authConfig *AuthConfig) string {
|
|
|
|
authStr := authConfig.Username + ":" + authConfig.Password
|
|
|
|
msg := []byte(authStr)
|
|
|
|
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
|
|
|
|
base64.StdEncoding.Encode(encoded, msg)
|
|
|
|
return string(encoded)
|
|
|
|
}
|
|
|
|
|
2015-07-20 18:39:07 -04:00
|
|
|
// DecodeAuth decodes a base64 encoded string and returns username and password
|
2015-04-22 08:06:58 -04:00
|
|
|
func DecodeAuth(authStr string) (string, string, error) {
|
|
|
|
decLen := base64.StdEncoding.DecodedLen(len(authStr))
|
|
|
|
decoded := make([]byte, decLen)
|
|
|
|
authByte := []byte(authStr)
|
|
|
|
n, err := base64.StdEncoding.Decode(decoded, authByte)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
if n > decLen {
|
|
|
|
return "", "", fmt.Errorf("Something went wrong decoding auth config")
|
|
|
|
}
|
|
|
|
arr := strings.SplitN(string(decoded), ":", 2)
|
|
|
|
if len(arr) != 2 {
|
|
|
|
return "", "", fmt.Errorf("Invalid auth configuration file")
|
|
|
|
}
|
|
|
|
password := strings.Trim(arr[1], "\x00")
|
|
|
|
return arr[0], password, nil
|
|
|
|
}
|