github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/alias-set.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 "bufio" 22 "context" 23 "crypto/tls" 24 "crypto/x509" 25 "fmt" 26 "math/rand" 27 "net" 28 "net/http" 29 "os" 30 "strings" 31 "time" 32 33 "github.com/fatih/color" 34 "github.com/minio/cli" 35 "github.com/minio/mc/pkg/probe" 36 "github.com/minio/minio-go/v7" 37 "github.com/minio/pkg/v2/console" 38 "golang.org/x/term" 39 ) 40 41 const cred = "YellowItalics" 42 43 var aliasSetFlags = []cli.Flag{ 44 cli.StringFlag{ 45 Name: "path", 46 Value: "auto", 47 Usage: "bucket path lookup supported by the server. Valid options are '[auto, on, off]'", 48 }, 49 cli.StringFlag{ 50 Name: "api", 51 Usage: "API signature. Valid options are '[S3v4, S3v2]'", 52 }, 53 } 54 55 var aliasSetCmd = cli.Command{ 56 Name: "set", 57 ShortName: "s", 58 Usage: "set a new alias to configuration file", 59 Action: func(cli *cli.Context) error { 60 return mainAliasSet(cli, false) 61 }, 62 OnUsageError: onUsageError, 63 Before: setGlobalsFromContext, 64 Flags: append(aliasSetFlags, globalFlags...), 65 HideHelpCommand: true, 66 CustomHelpTemplate: `NAME: 67 {{.HelpName}} - {{.Usage}} 68 69 USAGE: 70 {{.HelpName}} ALIAS URL ACCESSKEY SECRETKEY 71 72 FLAGS: 73 {{range .VisibleFlags}}{{.}} 74 {{end}} 75 EXAMPLES: 76 1. Add MinIO service under "myminio" alias. For security reasons turn off bash history momentarily. 77 {{.DisableHistory}} 78 {{.Prompt}} {{.HelpName}} myminio http://localhost:9000 minio minio123 79 {{.EnableHistory}} 80 2. Add MinIO service under "myminio" alias, to use dns style bucket lookup. For security reasons 81 turn off bash history momentarily. 82 {{.DisableHistory}} 83 {{.Prompt}} {{.HelpName}} myminio http://localhost:9000 minio minio123 --api "s3v4" --path "off" 84 {{.EnableHistory}} 85 3. Add Amazon S3 storage service under "mys3" alias. For security reasons turn off bash history momentarily. 86 {{.DisableHistory}} 87 {{.Prompt}} {{.HelpName}} mys3 https://s3.amazonaws.com \ 88 BKIKJAA5BMMU2RHO6IBB V8f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12 89 {{.EnableHistory}} 90 4. Add Amazon S3 storage service under "mys3" alias, prompting for keys. 91 {{.Prompt}} {{.HelpName}} mys3 https://s3.amazonaws.com --api "s3v4" --path "off" 92 Enter Access Key: BKIKJAA5BMMU2RHO6IBB 93 Enter Secret Key: V8f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12 94 5. Add Amazon S3 storage service under "mys3" alias using piped keys. 95 {{.DisableHistory}} 96 {{.Prompt}} echo -e "BKIKJAA5BMMU2RHO6IBB\nV8f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12" | \ 97 {{.HelpName}} mys3 https://s3.amazonaws.com --api "s3v4" --path "off" 98 {{.EnableHistory}} 99 `, 100 } 101 102 // checkAliasSetSyntax - verifies input arguments to 'alias set'. 103 func checkAliasSetSyntax(ctx *cli.Context, accessKey, secretKey string, deprecated bool) { 104 args := ctx.Args() 105 argsNr := len(args) 106 107 if argsNr == 0 { 108 showCommandHelpAndExit(ctx, 1) // last argument is exit code 109 } 110 111 if argsNr > 4 || argsNr < 2 { 112 fatalIf(errInvalidArgument().Trace(ctx.Args().Tail()...), 113 "Incorrect number of arguments for alias set command.") 114 } 115 116 alias := cleanAlias(args.Get(0)) 117 url := args.Get(1) 118 api := ctx.String("api") 119 path := ctx.String("path") 120 bucketLookup := ctx.String("lookup") 121 122 if !isValidAlias(alias) { 123 fatalIf(errInvalidAlias(alias), "Invalid alias.") 124 } 125 126 if !isValidHostURL(url) { 127 fatalIf(errInvalidURL(url), "Invalid URL.") 128 } 129 130 if !isValidAccessKey(accessKey) { 131 fatalIf(errInvalidArgument().Trace(accessKey), 132 "Invalid access key `"+accessKey+"`.") 133 } 134 135 if !isValidSecretKey(secretKey) { 136 fatalIf(errInvalidArgument().Trace(secretKey), 137 "Invalid secret key `"+secretKey+"`.") 138 } 139 140 if api != "" && !isValidAPI(api) { // Empty value set to default "S3v4". 141 fatalIf(errInvalidArgument().Trace(api), 142 "Unrecognized API signature. Valid options are `[S3v4, S3v2]`.") 143 } 144 145 if deprecated { 146 if !isValidLookup(bucketLookup) { 147 fatalIf(errInvalidArgument().Trace(bucketLookup), 148 "Unrecognized bucket lookup. Valid options are `[dns,auto, path]`.") 149 } 150 } else { 151 if !isValidPath(path) { 152 fatalIf(errInvalidArgument().Trace(bucketLookup), 153 "Unrecognized path value. Valid options are `[auto, on, off]`.") 154 } 155 } 156 } 157 158 // setAlias - set an alias config. 159 func setAlias(alias string, aliasCfgV10 aliasConfigV10) aliasMessage { 160 mcCfgV10, err := loadMcConfig() 161 fatalIf(err.Trace(globalMCConfigVersion), "Unable to load config `"+mustGetMcConfigPath()+"`.") 162 163 // Add new host. 164 mcCfgV10.Aliases[alias] = aliasCfgV10 165 166 err = saveMcConfig(mcCfgV10) 167 fatalIf(err.Trace(alias), "Unable to update hosts in config version `"+mustGetMcConfigPath()+"`.") 168 169 return aliasMessage{ 170 Alias: alias, 171 URL: aliasCfgV10.URL, 172 AccessKey: aliasCfgV10.AccessKey, 173 SecretKey: aliasCfgV10.SecretKey, 174 API: aliasCfgV10.API, 175 Path: aliasCfgV10.Path, 176 } 177 } 178 179 // probeS3Signature - auto probe S3 server signature: issue a Stat call 180 // using v4 signature then v2 in case of failure. 181 func probeS3Signature(ctx context.Context, accessKey, secretKey, url string, peerCert *x509.Certificate) (string, *probe.Error) { 182 probeBucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "probe-bsign-") 183 // Test s3 connection for API auto probe 184 s3Config := &Config{ 185 // S3 connection parameters 186 Insecure: globalInsecure, 187 AccessKey: accessKey, 188 SecretKey: secretKey, 189 HostURL: urlJoinPath(url, probeBucketName), 190 Debug: globalDebug, 191 ConnReadDeadline: globalConnReadDeadline, 192 ConnWriteDeadline: globalConnWriteDeadline, 193 UploadLimit: int64(globalLimitUpload), 194 DownloadLimit: int64(globalLimitDownload), 195 } 196 if peerCert != nil { 197 configurePeerCertificate(s3Config, peerCert) 198 } 199 200 probeSignatureType := func(stype string) (string, *probe.Error) { 201 s3Config.Signature = stype 202 s3Client, err := S3New(s3Config) 203 if err != nil { 204 return "", err 205 } 206 207 if _, err := s3Client.Stat(ctx, StatOptions{}); err != nil { 208 e := err.ToGoError() 209 if _, ok := e.(BucketDoesNotExist); ok { 210 // Bucket doesn't exist, means signature probing worked successfully. 211 return stype, nil 212 } 213 // AccessDenied means Stat() is not allowed but credentials are valid. 214 // AccessDenied is only returned when policy doesn't allow HeadBucket 215 // operations. 216 if minio.ToErrorResponse(err.ToGoError()).Code == "AccessDenied" { 217 return stype, nil 218 } 219 220 // For any other errors we fail. 221 return "", err.Trace(s3Config.Signature) 222 } 223 return stype, nil 224 } 225 226 stype, err := probeSignatureType("s3v4") 227 if err != nil { 228 if stype, err = probeSignatureType("s3v2"); err != nil { 229 return "", err.Trace("s3v4", "s3v2") 230 } 231 return stype, nil 232 } 233 return stype, nil 234 } 235 236 // BuildS3Config constructs an S3 Config and does 237 // signature auto-probe when needed. 238 func BuildS3Config(ctx context.Context, alias, url, accessKey, secretKey, api, path string, peerCert *x509.Certificate) (*Config, *probe.Error) { 239 s3Config := NewS3Config(alias, url, &aliasConfigV10{ 240 AccessKey: accessKey, 241 SecretKey: secretKey, 242 URL: url, 243 Path: path, 244 }) 245 246 if peerCert != nil { 247 configurePeerCertificate(s3Config, peerCert) 248 } 249 250 // If api is provided we do not auto probe signature, this is 251 // required in situations when signature type is provided by the user. 252 if api != "" { 253 s3Config.Signature = api 254 return s3Config, nil 255 } 256 // Probe S3 signature version 257 api, err := probeS3Signature(ctx, accessKey, secretKey, url, peerCert) 258 if err != nil { 259 return nil, err.Trace(url, accessKey, api, path) 260 } 261 262 s3Config.Signature = api 263 // Success. 264 return s3Config, nil 265 } 266 267 // fetchAliasKeys - returns the user accessKey and secretKey 268 func fetchAliasKeys(args cli.Args) (string, string) { 269 accessKey := "" 270 secretKey := "" 271 console.SetColor(cred, color.New(color.FgYellow, color.Italic)) 272 isTerminal := term.IsTerminal(int(os.Stdin.Fd())) 273 reader := bufio.NewReader(os.Stdin) 274 275 argsNr := len(args) 276 277 if argsNr == 2 { 278 if isTerminal { 279 fmt.Printf("%s", console.Colorize(cred, "Enter Access Key: ")) 280 } 281 value, _, _ := reader.ReadLine() 282 accessKey = string(value) 283 } else { 284 accessKey = args.Get(2) 285 } 286 287 if argsNr == 2 || argsNr == 3 { 288 if isTerminal { 289 fmt.Printf("%s", console.Colorize(cred, "Enter Secret Key: ")) 290 bytePassword, _ := term.ReadPassword(int(os.Stdin.Fd())) 291 fmt.Printf("\n") 292 secretKey = string(bytePassword) 293 } else { 294 value, _, _ := reader.ReadLine() 295 secretKey = string(value) 296 } 297 } else { 298 secretKey = args.Get(3) 299 } 300 301 return accessKey, secretKey 302 } 303 304 func mainAliasSet(cli *cli.Context, deprecated bool) error { 305 console.SetColor("AliasMessage", color.New(color.FgGreen)) 306 var ( 307 args = cli.Args() 308 alias = cleanAlias(args.Get(0)) 309 url = trimTrailingSeparator(args.Get(1)) 310 api = cli.String("api") 311 path = cli.String("path") 312 313 peerCert *x509.Certificate 314 err *probe.Error 315 ) 316 317 // Support deprecated lookup flag 318 if deprecated { 319 lookup := strings.ToLower(strings.TrimSpace(cli.String("lookup"))) 320 switch lookup { 321 case "", "auto": 322 path = "auto" 323 case "path": 324 path = "on" 325 case "dns": 326 path = "off" 327 default: 328 } 329 } 330 331 accessKey, secretKey := fetchAliasKeys(args) 332 checkAliasSetSyntax(cli, accessKey, secretKey, deprecated) 333 334 ctx, cancelAliasAdd := context.WithCancel(globalContext) 335 defer cancelAliasAdd() 336 337 if !globalInsecure && !globalJSON && term.IsTerminal(int(os.Stdout.Fd())) { 338 peerCert, err = promptTrustSelfSignedCert(ctx, url, alias) 339 fatalIf(err.Trace(alias, url, accessKey), "Unable to initialize new alias from the provided credentials.") 340 } 341 342 s3Config, err := BuildS3Config(ctx, alias, url, accessKey, secretKey, api, path, peerCert) 343 fatalIf(err.Trace(alias, url, accessKey), "Unable to initialize new alias from the provided credentials.") 344 345 msg := setAlias(alias, aliasConfigV10{ 346 URL: s3Config.HostURL, 347 AccessKey: s3Config.AccessKey, 348 SecretKey: s3Config.SecretKey, 349 API: s3Config.Signature, 350 Path: path, 351 }) // Add an alias with specified credentials. 352 353 msg.op = "set" 354 if deprecated { 355 msg.op = "add" 356 } 357 358 printMsg(msg) 359 return nil 360 } 361 362 // configurePeerCertificate adds the peer certificate to the 363 // TLS root CAs of s3Config. Once configured, any client 364 // initialized with this config trusts the given peer certificate. 365 func configurePeerCertificate(s3Config *Config, peerCert *x509.Certificate) { 366 tr, ok := s3Config.Transport.(*http.Transport) 367 if !ok { 368 return 369 } 370 switch { 371 case tr == nil: 372 if globalRootCAs != nil { 373 globalRootCAs.AddCert(peerCert) 374 } 375 tr = &http.Transport{ 376 Proxy: http.ProxyFromEnvironment, 377 DialContext: (&net.Dialer{ 378 Timeout: 10 * time.Second, 379 KeepAlive: 15 * time.Second, 380 }).DialContext, 381 MaxIdleConnsPerHost: 256, 382 IdleConnTimeout: 90 * time.Second, 383 TLSHandshakeTimeout: 10 * time.Second, 384 ExpectContinueTimeout: 10 * time.Second, 385 DisableCompression: true, 386 TLSClientConfig: &tls.Config{RootCAs: globalRootCAs}, 387 } 388 case tr.TLSClientConfig == nil || tr.TLSClientConfig.RootCAs == nil: 389 if globalRootCAs != nil { 390 globalRootCAs.AddCert(peerCert) 391 } 392 tr.TLSClientConfig = &tls.Config{RootCAs: globalRootCAs} 393 default: 394 tr.TLSClientConfig.RootCAs.AddCert(peerCert) 395 } 396 s3Config.Transport = tr 397 }