github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/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/opts" 15 "github.com/docker/docker/api/types" 16 "github.com/pkg/errors" 17 ) 18 19 const ( 20 // This constant is only used for really old config files when the 21 // URL wasn't saved as part of the config file and it was just 22 // assumed to be this value. 23 defaultIndexServer = "https://index.docker.io/v1/" 24 ) 25 26 // ConfigFile ~/.docker/config.json file info 27 type ConfigFile struct { 28 AuthConfigs map[string]types.AuthConfig `json:"auths"` 29 HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"` 30 PsFormat string `json:"psFormat,omitempty"` 31 ImagesFormat string `json:"imagesFormat,omitempty"` 32 NetworksFormat string `json:"networksFormat,omitempty"` 33 PluginsFormat string `json:"pluginsFormat,omitempty"` 34 VolumesFormat string `json:"volumesFormat,omitempty"` 35 StatsFormat string `json:"statsFormat,omitempty"` 36 DetachKeys string `json:"detachKeys,omitempty"` 37 CredentialsStore string `json:"credsStore,omitempty"` 38 CredentialHelpers map[string]string `json:"credHelpers,omitempty"` 39 Filename string `json:"-"` // Note: for internal use only 40 ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"` 41 ServicesFormat string `json:"servicesFormat,omitempty"` 42 TasksFormat string `json:"tasksFormat,omitempty"` 43 SecretFormat string `json:"secretFormat,omitempty"` 44 ConfigFormat string `json:"configFormat,omitempty"` 45 NodesFormat string `json:"nodesFormat,omitempty"` 46 PruneFilters []string `json:"pruneFilters,omitempty"` 47 Proxies map[string]ProxyConfig `json:"proxies,omitempty"` 48 Experimental string `json:"experimental,omitempty"` 49 StackOrchestrator string `json:"stackOrchestrator,omitempty"` 50 Kubernetes *KubernetesConfig `json:"kubernetes,omitempty"` 51 } 52 53 // ProxyConfig contains proxy configuration settings 54 type ProxyConfig struct { 55 HTTPProxy string `json:"httpProxy,omitempty"` 56 HTTPSProxy string `json:"httpsProxy,omitempty"` 57 NoProxy string `json:"noProxy,omitempty"` 58 FTPProxy string `json:"ftpProxy,omitempty"` 59 } 60 61 // KubernetesConfig contains Kubernetes orchestrator settings 62 type KubernetesConfig struct { 63 AllNamespaces string `json:"allNamespaces,omitempty"` 64 } 65 66 // New initializes an empty configuration file for the given filename 'fn' 67 func New(fn string) *ConfigFile { 68 return &ConfigFile{ 69 AuthConfigs: make(map[string]types.AuthConfig), 70 HTTPHeaders: make(map[string]string), 71 Filename: fn, 72 } 73 } 74 75 // LegacyLoadFromReader reads the non-nested configuration data given and sets up the 76 // auth config information with given directory and populates the receiver object 77 func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error { 78 b, err := ioutil.ReadAll(configData) 79 if err != nil { 80 return err 81 } 82 83 if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil { 84 arr := strings.Split(string(b), "\n") 85 if len(arr) < 2 { 86 return errors.Errorf("The Auth config file is empty") 87 } 88 authConfig := types.AuthConfig{} 89 origAuth := strings.Split(arr[0], " = ") 90 if len(origAuth) != 2 { 91 return errors.Errorf("Invalid Auth config file") 92 } 93 authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) 94 if err != nil { 95 return err 96 } 97 authConfig.ServerAddress = defaultIndexServer 98 configFile.AuthConfigs[defaultIndexServer] = authConfig 99 } else { 100 for k, authConfig := range configFile.AuthConfigs { 101 authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) 102 if err != nil { 103 return err 104 } 105 authConfig.Auth = "" 106 authConfig.ServerAddress = k 107 configFile.AuthConfigs[k] = authConfig 108 } 109 } 110 return nil 111 } 112 113 // LoadFromReader reads the configuration data given and sets up the auth config 114 // information with given directory and populates the receiver object 115 func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error { 116 if err := json.NewDecoder(configData).Decode(&configFile); err != nil { 117 return err 118 } 119 var err error 120 for addr, ac := range configFile.AuthConfigs { 121 ac.Username, ac.Password, err = decodeAuth(ac.Auth) 122 if err != nil { 123 return err 124 } 125 ac.Auth = "" 126 ac.ServerAddress = addr 127 configFile.AuthConfigs[addr] = ac 128 } 129 return checkKubernetesConfiguration(configFile.Kubernetes) 130 } 131 132 // ContainsAuth returns whether there is authentication configured 133 // in this file or not. 134 func (configFile *ConfigFile) ContainsAuth() bool { 135 return configFile.CredentialsStore != "" || 136 len(configFile.CredentialHelpers) > 0 || 137 len(configFile.AuthConfigs) > 0 138 } 139 140 // GetAuthConfigs returns the mapping of repo to auth configuration 141 func (configFile *ConfigFile) GetAuthConfigs() map[string]types.AuthConfig { 142 return configFile.AuthConfigs 143 } 144 145 // SaveToWriter encodes and writes out all the authorization information to 146 // the given writer 147 func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error { 148 // Encode sensitive data into a new/temp struct 149 tmpAuthConfigs := make(map[string]types.AuthConfig, len(configFile.AuthConfigs)) 150 for k, authConfig := range configFile.AuthConfigs { 151 authCopy := authConfig 152 // encode and save the authstring, while blanking out the original fields 153 authCopy.Auth = encodeAuth(&authCopy) 154 authCopy.Username = "" 155 authCopy.Password = "" 156 authCopy.ServerAddress = "" 157 tmpAuthConfigs[k] = authCopy 158 } 159 160 saveAuthConfigs := configFile.AuthConfigs 161 configFile.AuthConfigs = tmpAuthConfigs 162 defer func() { configFile.AuthConfigs = saveAuthConfigs }() 163 164 data, err := json.MarshalIndent(configFile, "", "\t") 165 if err != nil { 166 return err 167 } 168 _, err = writer.Write(data) 169 return err 170 } 171 172 // Save encodes and writes out all the authorization information 173 func (configFile *ConfigFile) Save() error { 174 if configFile.Filename == "" { 175 return errors.Errorf("Can't save config with empty filename") 176 } 177 178 dir := filepath.Dir(configFile.Filename) 179 if err := os.MkdirAll(dir, 0700); err != nil { 180 return err 181 } 182 temp, err := ioutil.TempFile(dir, filepath.Base(configFile.Filename)) 183 if err != nil { 184 return err 185 } 186 err = configFile.SaveToWriter(temp) 187 temp.Close() 188 if err != nil { 189 os.Remove(temp.Name()) 190 return err 191 } 192 return os.Rename(temp.Name(), configFile.Filename) 193 } 194 195 // ParseProxyConfig computes proxy configuration by retrieving the config for the provided host and 196 // then checking this against any environment variables provided to the container 197 func (configFile *ConfigFile) ParseProxyConfig(host string, runOpts []string) map[string]*string { 198 var cfgKey string 199 200 if _, ok := configFile.Proxies[host]; !ok { 201 cfgKey = "default" 202 } else { 203 cfgKey = host 204 } 205 206 config := configFile.Proxies[cfgKey] 207 permitted := map[string]*string{ 208 "HTTP_PROXY": &config.HTTPProxy, 209 "HTTPS_PROXY": &config.HTTPSProxy, 210 "NO_PROXY": &config.NoProxy, 211 "FTP_PROXY": &config.FTPProxy, 212 } 213 m := opts.ConvertKVStringsToMapWithNil(runOpts) 214 for k := range permitted { 215 if *permitted[k] == "" { 216 continue 217 } 218 if _, ok := m[k]; !ok { 219 m[k] = permitted[k] 220 } 221 if _, ok := m[strings.ToLower(k)]; !ok { 222 m[strings.ToLower(k)] = permitted[k] 223 } 224 } 225 return m 226 } 227 228 // encodeAuth creates a base64 encoded string to containing authorization information 229 func encodeAuth(authConfig *types.AuthConfig) string { 230 if authConfig.Username == "" && authConfig.Password == "" { 231 return "" 232 } 233 234 authStr := authConfig.Username + ":" + authConfig.Password 235 msg := []byte(authStr) 236 encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) 237 base64.StdEncoding.Encode(encoded, msg) 238 return string(encoded) 239 } 240 241 // decodeAuth decodes a base64 encoded string and returns username and password 242 func decodeAuth(authStr string) (string, string, error) { 243 if authStr == "" { 244 return "", "", nil 245 } 246 247 decLen := base64.StdEncoding.DecodedLen(len(authStr)) 248 decoded := make([]byte, decLen) 249 authByte := []byte(authStr) 250 n, err := base64.StdEncoding.Decode(decoded, authByte) 251 if err != nil { 252 return "", "", err 253 } 254 if n > decLen { 255 return "", "", errors.Errorf("Something went wrong decoding auth config") 256 } 257 arr := strings.SplitN(string(decoded), ":", 2) 258 if len(arr) != 2 { 259 return "", "", errors.Errorf("Invalid auth configuration file") 260 } 261 password := strings.Trim(arr[1], "\x00") 262 return arr[0], password, nil 263 } 264 265 // GetCredentialsStore returns a new credentials store from the settings in the 266 // configuration file 267 func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store { 268 if helper := getConfiguredCredentialStore(configFile, registryHostname); helper != "" { 269 return newNativeStore(configFile, helper) 270 } 271 return credentials.NewFileStore(configFile) 272 } 273 274 // var for unit testing. 275 var newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store { 276 return credentials.NewNativeStore(configFile, helperSuffix) 277 } 278 279 // GetAuthConfig for a repository from the credential store 280 func (configFile *ConfigFile) GetAuthConfig(registryHostname string) (types.AuthConfig, error) { 281 return configFile.GetCredentialsStore(registryHostname).Get(registryHostname) 282 } 283 284 // getConfiguredCredentialStore returns the credential helper configured for the 285 // given registry, the default credsStore, or the empty string if neither are 286 // configured. 287 func getConfiguredCredentialStore(c *ConfigFile, registryHostname string) string { 288 if c.CredentialHelpers != nil && registryHostname != "" { 289 if helper, exists := c.CredentialHelpers[registryHostname]; exists { 290 return helper 291 } 292 } 293 return c.CredentialsStore 294 } 295 296 // GetAllCredentials returns all of the credentials stored in all of the 297 // configured credential stores. 298 func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig, error) { 299 auths := make(map[string]types.AuthConfig) 300 addAll := func(from map[string]types.AuthConfig) { 301 for reg, ac := range from { 302 auths[reg] = ac 303 } 304 } 305 306 defaultStore := configFile.GetCredentialsStore("") 307 newAuths, err := defaultStore.GetAll() 308 if err != nil { 309 return nil, err 310 } 311 addAll(newAuths) 312 313 // Auth configs from a registry-specific helper should override those from the default store. 314 for registryHostname := range configFile.CredentialHelpers { 315 newAuth, err := configFile.GetAuthConfig(registryHostname) 316 if err != nil { 317 return nil, err 318 } 319 auths[registryHostname] = newAuth 320 } 321 return auths, nil 322 } 323 324 // GetFilename returns the file name that this config file is based on. 325 func (configFile *ConfigFile) GetFilename() string { 326 return configFile.Filename 327 } 328 329 func checkKubernetesConfiguration(kubeConfig *KubernetesConfig) error { 330 if kubeConfig == nil { 331 return nil 332 } 333 switch kubeConfig.AllNamespaces { 334 case "": 335 case "enabled": 336 case "disabled": 337 default: 338 return fmt.Errorf("invalid 'kubernetes.allNamespaces' value, should be 'enabled' or 'disabled': %s", kubeConfig.AllNamespaces) 339 } 340 return nil 341 }