github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/config-fix.go (about) 1 // Copyright (c) 2015-2022 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "fmt" 22 "os" 23 "path/filepath" 24 "runtime" 25 "strings" 26 27 "github.com/minio/mc/pkg/probe" 28 "github.com/minio/pkg/v2/console" 29 "github.com/minio/pkg/v2/quick" 30 ) 31 32 func fixConfig() { 33 // Migrate config location on windows 34 fixConfigLocation() 35 // Fix config V3 36 fixConfigV3() 37 // Fix config V6 38 fixConfigV6() 39 // Fix config V6 for hosts 40 fixConfigV6ForHosts() 41 42 /* No more fixing job. Here after we bump the version for changes always. 43 */ 44 } 45 46 // ConfigAnyVersion is a generic structure to parse any 47 // config.json version file and only extracts its version number 48 type ConfigAnyVersion struct { 49 Version string 50 } 51 52 // ///////////////// Broken Config V3 /////////////////// 53 type brokenHostConfigV3 struct { 54 AccessKeyID string 55 SecretAccessKey string 56 } 57 58 type brokenConfigV3 struct { 59 Version string 60 ACL string 61 Access string 62 Aliases map[string]string 63 Hosts map[string]brokenHostConfigV3 64 } 65 66 // newConfigV3 - get new config broken version 3. 67 func newBrokenConfigV3() *brokenConfigV3 { 68 conf := new(brokenConfigV3) 69 conf.Version = "3" 70 conf.Aliases = make(map[string]string) 71 conf.Hosts = make(map[string]brokenHostConfigV3) 72 return conf 73 } 74 75 // Fix config version `3`. Some v3 config files are written without 76 // proper hostConfig JSON tags. They may also contain unused ACL and 77 // Access fields. Rewrite the hostConfig with proper fields using JSON 78 // tags and drop the unused (ACL, Access) fields. 79 func fixConfigV3() { 80 if !isMcConfigExists() { 81 return 82 } 83 84 // Check if this is the correct version to fix 85 configAllVersions, e := quick.LoadConfig(mustGetMcConfigPath(), nil, &ConfigAnyVersion{}) 86 fatalIf(probe.NewError(e), "Unable to load config.") 87 if configAllVersions.Version() != "3" { 88 return 89 } 90 91 brokenCfgV3 := newBrokenConfigV3() 92 brokenMcCfgV3, e := quick.LoadConfig(mustGetMcConfigPath(), nil, brokenCfgV3) 93 fatalIf(probe.NewError(e), "Unable to load config.") 94 95 cfgV3 := newConfigV3() 96 isMutated := false 97 for k, v := range brokenMcCfgV3.Data().(*brokenConfigV3).Aliases { 98 cfgV3.Aliases[k] = v 99 } 100 101 for host, brokenHostCfgV3 := range brokenMcCfgV3.Data().(*brokenConfigV3).Hosts { 102 103 // If any of these fields contains any real value anytime, 104 // it means we have already fixed the broken configuration. 105 // We don't have to regenerate again. 106 if brokenHostCfgV3.AccessKeyID != "" && brokenHostCfgV3.SecretAccessKey != "" { 107 isMutated = true 108 } 109 110 // Use the correct hostConfig with JSON tags in it. 111 cfgV3.Hosts[host] = hostConfigV3(brokenHostCfgV3) 112 } 113 114 // We blindly drop ACL and Access fields from the broken config v3. 115 116 if isMutated { 117 mcNewConfigV3, e := quick.NewConfig(cfgV3, nil) 118 fatalIf(probe.NewError(e), "Unable to initialize quick config for config version `3`.") 119 120 e = mcNewConfigV3.Save(mustGetMcConfigPath()) 121 fatalIf(probe.NewError(e), "Unable to save config version `3`.") 122 123 console.Infof("Successfully fixed %s broken config for version `3`.\n", mustGetMcConfigPath()) 124 } 125 } 126 127 // If the host key does not have http(s), fix it. 128 func fixConfigV6ForHosts() { 129 if !isMcConfigExists() { 130 return 131 } 132 133 // Check the current config version 134 configAllVersions, e := quick.LoadConfig(mustGetMcConfigPath(), nil, &ConfigAnyVersion{}) 135 fatalIf(probe.NewError(e), "Unable to load config.") 136 if configAllVersions.Version() != "6" { 137 return 138 } 139 140 brokenMcCfgV6, e := quick.LoadConfig(mustGetMcConfigPath(), nil, newConfigV6()) 141 fatalIf(probe.NewError(e), "Unable to load config.") 142 143 newCfgV6 := newConfigV6() 144 isMutated := false 145 146 // Copy aliases. 147 for k, v := range brokenMcCfgV6.Data().(*configV6).Aliases { 148 newCfgV6.Aliases[k] = v 149 } 150 151 url := &ClientURL{} 152 // Copy hosts. 153 for host, hostCfgV6 := range brokenMcCfgV6.Data().(*configV6).Hosts { 154 // Already fixed - Copy and move on. 155 if strings.HasPrefix(host, "https") || strings.HasPrefix(host, "http") { 156 newCfgV6.Hosts[host] = hostCfgV6 157 continue 158 } 159 160 // If host entry does not contain "http(s)", introduce a new entry and delete the old one. 161 if host == "s3.amazonaws.com" || host == "storage.googleapis.com" || 162 host == "localhost:9000" || host == "127.0.0.1:9000" || 163 host == "play.min.io:9000" || host == "dl.min.io:9000" { 164 console.Infoln("Found broken host entries, replacing " + host + " with https://" + host + ".") 165 url.Host = host 166 url.Scheme = "https" 167 url.SchemeSeparator = "://" 168 newCfgV6.Hosts[url.String()] = hostCfgV6 169 isMutated = true 170 continue 171 } 172 } 173 174 if isMutated { 175 // Save the new config back to the disk. 176 mcCfgV6, e := quick.NewConfig(newCfgV6, nil) 177 fatalIf(probe.NewError(e), "Unable to initialize quick config for config version `v6`.") 178 179 e = mcCfgV6.Save(mustGetMcConfigPath()) 180 fatalIf(probe.NewError(e), "Unable to save config version `v6`.") 181 } 182 } 183 184 // fixConfigV6 - fix all the unnecessary glob URLs present in existing config version 6. 185 func fixConfigV6() { 186 if !isMcConfigExists() { 187 return 188 } 189 190 configAllVersions, e := quick.LoadConfig(mustGetMcConfigPath(), nil, &ConfigAnyVersion{}) 191 fatalIf(probe.NewError(e), "Unable to load config.") 192 if configAllVersions.Version() != "6" { 193 return 194 } 195 196 config, e := quick.NewConfig(newConfigV6(), nil) 197 fatalIf(probe.NewError(e), "Unable to initialize config.") 198 199 e = config.Load(mustGetMcConfigPath()) 200 fatalIf(probe.NewError(e).Trace(mustGetMcConfigPath()), "Unable to load config.") 201 202 newConfig := new(configV6) 203 isMutated := false 204 newConfig.Aliases = make(map[string]string) 205 newConfig.Hosts = make(map[string]hostConfigV6) 206 newConfig.Version = "6" 207 newConfig.Aliases = config.Data().(*configV6).Aliases 208 for host, hostCfg := range config.Data().(*configV6).Hosts { 209 if strings.Contains(host, "*") { 210 fatalIf(errInvalidArgument(), 211 fmt.Sprintf("Glob style `*` pattern matching is no longer supported. Please fix `%s` entry manually.", host)) 212 } 213 if strings.Contains(host, "*s3*") || strings.Contains(host, "*.s3*") { 214 console.Infoln("Found glob url, replacing " + host + " with s3.amazonaws.com") 215 newConfig.Hosts["s3.amazonaws.com"] = hostCfg 216 isMutated = true 217 continue 218 } 219 if strings.Contains(host, "s3*") { 220 console.Infoln("Found glob url, replacing " + host + " with s3.amazonaws.com") 221 newConfig.Hosts["s3.amazonaws.com"] = hostCfg 222 isMutated = true 223 continue 224 } 225 if strings.Contains(host, "*amazonaws.com") || strings.Contains(host, "*.amazonaws.com") { 226 console.Infoln("Found glob url, replacing " + host + " with s3.amazonaws.com") 227 newConfig.Hosts["s3.amazonaws.com"] = hostCfg 228 isMutated = true 229 continue 230 } 231 if strings.Contains(host, "*storage.googleapis.com") { 232 console.Infoln("Found glob url, replacing " + host + " with storage.googleapis.com") 233 newConfig.Hosts["storage.googleapis.com"] = hostCfg 234 isMutated = true 235 continue 236 } 237 if strings.Contains(host, "localhost:*") { 238 console.Infoln("Found glob url, replacing " + host + " with localhost:9000") 239 newConfig.Hosts["localhost:9000"] = hostCfg 240 isMutated = true 241 continue 242 } 243 if strings.Contains(host, "127.0.0.1:*") { 244 console.Infoln("Found glob url, replacing " + host + " with 127.0.0.1:9000") 245 newConfig.Hosts["127.0.0.1:9000"] = hostCfg 246 isMutated = true 247 continue 248 } 249 // Other entries are hopefully OK. Copy them blindly. 250 newConfig.Hosts[host] = hostCfg 251 } 252 253 if isMutated { 254 newConf, e := quick.NewConfig(newConfig, nil) 255 fatalIf(probe.NewError(e), "Unable to initialize newly fixed config.") 256 257 e = newConf.Save(mustGetMcConfigPath()) 258 fatalIf(probe.NewError(e).Trace(mustGetMcConfigPath()), "Unable to save newly fixed config path.") 259 console.Infof("Successfully fixed %s broken config for version `6`.\n", mustGetMcConfigPath()) 260 } 261 } 262 263 // fixConfigLocation will resolve the possible duplicate location of Windows config files. 264 // If there is duplicate configs, it will use the currently enabled config location and 265 // move it to the 'normalized' location. 266 // See https://github.com/minio/mc/pull/2898 267 func fixConfigLocation() { 268 if runtime.GOOS != "windows" || mcCustomConfigDir != mustGetMcConfigDir() { 269 return 270 } 271 if !strings.HasSuffix(strings.ToLower(filepath.Base(os.Args[0])), ".exe") { 272 // Most likely scenario, command was called as 'mc'. 273 // If there is a config at legacyLoc+".exe", rename it. 274 legacyLoc := mcCustomConfigDir + ".exe" 275 unusedLoc := mcCustomConfigDir + ".unused" 276 s, e := os.Stat(legacyLoc) 277 if e != nil || !s.IsDir() { 278 return 279 } 280 _ = os.Rename(legacyLoc, unusedLoc) 281 return 282 } 283 284 // mc was called with '.exe'; 285 // config can have changed location. 286 _, e := os.Stat(mcCustomConfigDir) 287 wantExists := !os.IsNotExist(e) 288 289 legFileName := mcCustomConfigDir + ".exe" 290 stat, e := os.Stat(legFileName) 291 legExists := !os.IsNotExist(e) && stat.IsDir() 292 switch { 293 case legExists && wantExists: 294 // Both exist and mc was called with legacy path (.exe) 295 // Rename the 'mc' config and move the legacy location one to where we want it. 296 backupdir := fmt.Sprintf("%s.unused\\", mcCustomConfigDir) 297 _ = os.RemoveAll(backupdir) 298 e := os.Rename(mcCustomConfigDir, backupdir) 299 fatalIf(probe.NewError(e), fmt.Sprintln("Renaming unused config", mcCustomConfigDir, "->", backupdir, "failed. Please rename/remove file.")) 300 fallthrough 301 case !wantExists && legExists: 302 e := os.Rename(legFileName, mcCustomConfigDir) 303 fatalIf(probe.NewError(e), fmt.Sprintln("Migrating config location", legFileName, "->", mcCustomConfigDir, "failed. Please move config file.")) 304 default: 305 // Legacy does not exist. 306 } 307 }