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 }