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  }