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 }