github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/subnet-utils.go (about) 1 // Copyright (c) 2015-2024 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 "bytes" 23 "encoding/base64" 24 "encoding/json" 25 "errors" 26 "fmt" 27 "io" 28 "math" 29 "net/http" 30 "net/http/httputil" 31 "net/url" 32 "os" 33 "strings" 34 "time" 35 36 "github.com/google/uuid" 37 "github.com/minio/cli" 38 "github.com/minio/madmin-go/v3" 39 "github.com/minio/mc/pkg/probe" 40 "github.com/minio/pkg/v2/licverifier" 41 "github.com/minio/pkg/v2/subnet" 42 "github.com/tidwall/gjson" 43 "golang.org/x/term" 44 ) 45 46 const ( 47 subnetRespBodyLimit = 1 << 20 // 1 MiB 48 minioSubscriptionURL = "https://min.io/subscription" 49 subnetPublicKeyPath = "/downloads/license-pubkey.pem" 50 minioDeploymentIDHeader = "x-minio-deployment-id" 51 ) 52 53 var subnetCommonFlags = append(supportGlobalFlags, cli.StringFlag{ 54 Name: "api-key", 55 Usage: "API Key of the account on SUBNET", 56 EnvVar: "_MC_SUBNET_API_KEY", 57 }) 58 59 // SubnetBaseURL - returns the base URL of SUBNET 60 func SubnetBaseURL() string { 61 return subnet.BaseURL(GlobalDevMode) 62 } 63 64 func subnetIssueURL(issueNum int) string { 65 return fmt.Sprintf("%s/issues/%d", SubnetBaseURL(), issueNum) 66 } 67 68 func subnetLogWebhookURL() string { 69 return SubnetBaseURL() + "/api/logs" 70 } 71 72 // SubnetUploadURL - returns the upload URL for the given upload type 73 func SubnetUploadURL(uploadType string) string { 74 return fmt.Sprintf("%s/api/%s/upload", SubnetBaseURL(), uploadType) 75 } 76 77 // SubnetRegisterURL - returns the cluster registration URL 78 func SubnetRegisterURL() string { 79 return SubnetBaseURL() + "/api/cluster/register" 80 } 81 82 func subnetUnregisterURL(depID string) string { 83 return SubnetBaseURL() + "/api/cluster/unregister?deploymentId=" + depID 84 } 85 86 func subnetLicenseRenewURL() string { 87 return SubnetBaseURL() + "/api/cluster/renew-license" 88 } 89 90 func subnetOfflineRegisterURL(regToken string) string { 91 return SubnetBaseURL() + "/cluster/register?token=" + regToken 92 } 93 94 func subnetLoginURL() string { 95 return SubnetBaseURL() + "/api/auth/login" 96 } 97 98 func subnetAPIKeyURL() string { 99 return SubnetBaseURL() + "/api/auth/api-key" 100 } 101 102 func subnetMFAURL() string { 103 return SubnetBaseURL() + "/api/auth/mfa-login" 104 } 105 106 func checkURLReachable(url string) *probe.Error { 107 _, e := subnetHeadReq(url, nil) 108 if e != nil { 109 return probe.NewError(e).Trace(url) 110 } 111 return nil 112 } 113 114 func subnetURLWithAuth(reqURL, apiKey string) (string, map[string]string, error) { 115 if len(apiKey) == 0 { 116 // API key not available in minio/mc config. 117 // Ask the user to log in to get auth token 118 token, e := subnetLogin() 119 if e != nil { 120 return "", nil, e 121 } 122 apiKey, e = getSubnetAPIKeyUsingAuthToken(token) 123 if e != nil { 124 return "", nil, e 125 } 126 } 127 return reqURL, SubnetAPIKeyAuthHeaders(apiKey), nil 128 } 129 130 // SubnetHeaders - type for SUBNET request headers 131 type SubnetHeaders map[string]string 132 133 func (h SubnetHeaders) addDeploymentIDHeader(alias string) { 134 h[minioDeploymentIDHeader] = getAdminInfo(alias).DeploymentID 135 } 136 137 func subnetTokenAuthHeaders(authToken string) map[string]string { 138 return map[string]string{"Authorization": "Bearer " + authToken} 139 } 140 141 // SubnetLicenseAuthHeaders - returns the headers for SUBNET license authentication 142 func SubnetLicenseAuthHeaders(lic string) map[string]string { 143 return map[string]string{"x-subnet-license": lic} 144 } 145 146 // SubnetAPIKeyAuthHeaders - returns the headers for SUBNET API key authentication 147 func SubnetAPIKeyAuthHeaders(apiKey string) SubnetHeaders { 148 return map[string]string{"x-subnet-api-key": apiKey} 149 } 150 151 func getSubnetClient() *http.Client { 152 client := httpClient(0) 153 if GlobalSubnetProxyURL != nil { 154 client.Transport.(*http.Transport).Proxy = http.ProxyURL(GlobalSubnetProxyURL) 155 } 156 return client 157 } 158 159 func subnetHTTPDo(req *http.Request) (resp *http.Response, err error) { 160 resp, err = getSubnetClient().Do(req) 161 if err == nil && globalDebug { 162 dumpHTTPReq(req, resp) 163 } 164 return 165 } 166 167 // dumpHTTP - dump HTTP request and response. 168 func dumpHTTPReq(req *http.Request, resp *http.Response) error { 169 // Starts http dump. 170 _, err := fmt.Fprintln(os.Stderr, "---------START-HTTP---------") 171 if err != nil { 172 return err 173 } 174 175 hdrs := req.Header 176 for _, hdr := range []string{"Authorization", "x-subnet-license", "x-subnet-api-key"} { 177 if val := hdrs.Get(hdr); val != "" { 178 req.Header.Set(hdr, strings.Repeat("*", len(val))) 179 } 180 } 181 182 query := req.URL.Query() 183 for _, q := range []string{"api-key", "api_key"} { 184 if val := query.Get(q); val != "" { 185 query.Add(q, strings.Repeat("*", len(val))) 186 } 187 } 188 req.URL.RawQuery = query.Encode() 189 190 // Only display request header. 191 reqTrace, err := httputil.DumpRequestOut(req, false) 192 if err != nil { 193 return err 194 } 195 196 // Write request to trace output. 197 _, err = fmt.Fprint(os.Stderr, string(reqTrace)) 198 if err != nil { 199 return err 200 } 201 202 respTrace, err := httputil.DumpResponse(resp, true) 203 if err != nil { 204 return err 205 } 206 207 // Write response to trace output. 208 _, err = fmt.Fprint(os.Stderr, strings.TrimSuffix(string(respTrace), "\r\n")) 209 if err != nil { 210 return err 211 } 212 213 // Ends the http dump. 214 _, err = fmt.Fprintln(os.Stderr, "---------END-HTTP---------") 215 return err 216 } 217 218 func subnetReqDo(r *http.Request, headers map[string]string) (string, error) { 219 for k, v := range headers { 220 r.Header.Add(k, v) 221 } 222 223 ct := r.Header.Get("Content-Type") 224 if len(ct) == 0 { 225 r.Header.Add("Content-Type", "application/json") 226 } 227 228 resp, e := subnetHTTPDo(r) 229 if e != nil { 230 return "", e 231 } 232 233 defer resp.Body.Close() 234 respBytes, e := io.ReadAll(io.LimitReader(resp.Body, subnetRespBodyLimit)) 235 if e != nil { 236 return "", e 237 } 238 respStr := string(respBytes) 239 240 if resp.StatusCode == http.StatusOK { 241 return respStr, nil 242 } 243 return respStr, fmt.Errorf("Request failed with code %d with error: %s", resp.StatusCode, respStr) 244 } 245 246 func subnetHeadReq(reqURL string, headers map[string]string) (string, error) { 247 r, e := http.NewRequest(http.MethodHead, reqURL, nil) 248 if e != nil { 249 return "", e 250 } 251 return subnetReqDo(r, headers) 252 } 253 254 func subnetGetReq(reqURL string, headers map[string]string) (string, error) { 255 r, e := http.NewRequest(http.MethodGet, reqURL, nil) 256 if e != nil { 257 return "", e 258 } 259 return subnetReqDo(r, headers) 260 } 261 262 // SubnetPostReq - makes a POST request to SUBNET 263 func SubnetPostReq(reqURL string, payload interface{}, headers map[string]string) (string, error) { 264 body, e := json.Marshal(payload) 265 if e != nil { 266 return "", e 267 } 268 r, e := http.NewRequest(http.MethodPost, reqURL, bytes.NewReader(body)) 269 if e != nil { 270 return "", e 271 } 272 return subnetReqDo(r, headers) 273 } 274 275 func getMinIOSubSysConfig(client *madmin.AdminClient, subSys string) ([]madmin.SubsysConfig, error) { 276 buf, e := client.GetConfigKV(globalContext, subSys) 277 if e != nil { 278 return nil, e 279 } 280 281 return madmin.ParseServerConfigOutput(string(buf)) 282 } 283 284 func getMinIOSubnetConfig(alias string) []madmin.SubsysConfig { 285 if globalSubnetConfig != nil { 286 return globalSubnetConfig 287 } 288 289 client, err := newAdminClient(alias) 290 fatalIf(err, "Unable to initialize admin connection.") 291 292 var e error 293 globalSubnetConfig, e = getMinIOSubSysConfig(client, madmin.SubnetSubSys) 294 if e != nil && e.Error() != "unknown sub-system subnet" { 295 fatal(probe.NewError(e), "Unable to get server config for subnet") 296 } 297 298 return globalSubnetConfig 299 } 300 301 func getKeyFromSubnetConfig(alias, key string) (string, bool) { 302 scfg := getMinIOSubnetConfig(alias) 303 304 // This function only works for fetch config from single target sub-systems 305 // in the server config and is enough for now. 306 if len(scfg) == 0 { 307 return "", false 308 } 309 310 return scfg[0].Lookup(key) 311 } 312 313 func getSubnetAPIKeyFromConfig(alias string) string { 314 // get the subnet api_key config from MinIO if available 315 apiKey, supported := getKeyFromSubnetConfig(alias, "api_key") 316 if supported { 317 return apiKey 318 } 319 320 // otherwise get it from mc config 321 return mcConfig().Aliases[alias].APIKey 322 } 323 324 func setGlobalSubnetProxyFromConfig(alias string) error { 325 if GlobalSubnetProxyURL != nil { 326 // proxy already set 327 return nil 328 } 329 330 var ( 331 proxy string 332 supported bool 333 ) 334 335 if env, ok := os.LookupEnv("_MC_SUBNET_PROXY_URL"); ok { 336 proxy = env 337 supported = env != "" 338 } else { 339 proxy, supported = getKeyFromSubnetConfig(alias, "proxy") 340 } 341 342 // get the subnet proxy config from MinIO if available 343 if supported && len(proxy) > 0 { 344 proxyURL, e := url.Parse(proxy) 345 if e != nil { 346 return e 347 } 348 GlobalSubnetProxyURL = proxyURL 349 } 350 return nil 351 } 352 353 func getSubnetLicenseFromConfig(alias string) string { 354 // get the subnet license config from MinIO if available 355 lic, supported := getKeyFromSubnetConfig(alias, "license") 356 if supported { 357 return lic 358 } 359 360 // otherwise get it from mc config 361 return mcConfig().Aliases[alias].License 362 } 363 364 func mcConfig() *configV10 { 365 loadMcConfig = loadMcConfigFactory() 366 config, err := loadMcConfig() 367 fatalIf(err.Trace(mustGetMcConfigPath()), "Unable to access configuration file.") 368 return config 369 } 370 371 func minioConfigSupportsSubSys(client *madmin.AdminClient, subSys string) bool { 372 help, e := client.HelpConfigKV(globalContext, "", "", false) 373 fatalIf(probe.NewError(e), "Unable to get minio config keys") 374 375 for _, h := range help.KeysHelp { 376 if h.Key == subSys { 377 return true 378 } 379 } 380 381 return false 382 } 383 384 func setSubnetAPIKeyInMcConfig(alias, apiKey string) { 385 aliasCfg := mcConfig().Aliases[alias] 386 if len(apiKey) > 0 { 387 aliasCfg.APIKey = apiKey 388 } 389 390 setAlias(alias, aliasCfg) 391 } 392 393 func setSubnetLicenseInMcConfig(alias, lic string) { 394 aliasCfg := mcConfig().Aliases[alias] 395 if len(lic) > 0 { 396 aliasCfg.License = lic 397 } 398 setAlias(alias, aliasCfg) 399 } 400 401 func setSubnetConfig(alias, subKey, cfgVal string) { 402 client, err := newAdminClient(alias) 403 fatalIf(err, "Unable to initialize admin connection.") 404 405 cfgKey := "subnet " + subKey 406 _, e := client.SetConfigKV(globalContext, cfgKey+"="+cfgVal) 407 fatalIf(probe.NewError(e), "Unable to set "+cfgKey+" config on MinIO") 408 } 409 410 func setSubnetAPIKey(alias, apiKey string) { 411 if len(apiKey) == 0 { 412 fatal(errDummy().Trace(), "API Key must not be empty.") 413 } 414 415 _, apiKeySupported := getKeyFromSubnetConfig(alias, "api_key") 416 if !apiKeySupported { 417 setSubnetAPIKeyInMcConfig(alias, apiKey) 418 return 419 } 420 421 setSubnetConfig(alias, "api_key", apiKey) 422 } 423 424 func setSubnetLicense(alias, lic string) { 425 if len(lic) == 0 { 426 fatal(errDummy().Trace(), "License must not be empty.") 427 } 428 429 _, licSupported := getKeyFromSubnetConfig(alias, "license") 430 if !licSupported { 431 setSubnetLicenseInMcConfig(alias, lic) 432 return 433 } 434 435 setSubnetConfig(alias, "license", lic) 436 } 437 438 // GetClusterRegInfo - returns the cluster registration info 439 func GetClusterRegInfo(admInfo madmin.InfoMessage, clusterName string) ClusterRegistrationInfo { 440 noOfPools := 1 441 noOfDrives := 0 442 for _, srvr := range admInfo.Servers { 443 for _, poolNumber := range srvr.PoolNumbers { 444 if poolNumber > noOfPools { 445 noOfPools = poolNumber 446 } 447 } 448 if len(srvr.PoolNumbers) == 0 { 449 if srvr.PoolNumber != math.MaxInt && srvr.PoolNumber > noOfPools { 450 noOfPools = srvr.PoolNumber 451 } 452 } 453 noOfDrives += len(srvr.Disks) 454 } 455 456 totalSpace, usedSpace := getDriveSpaceInfo(admInfo) 457 458 return ClusterRegistrationInfo{ 459 DeploymentID: admInfo.DeploymentID, 460 ClusterName: clusterName, 461 UsedCapacity: admInfo.Usage.Size, 462 Info: ClusterInfo{ 463 MinioVersion: admInfo.Servers[0].Version, 464 NoOfServerPools: noOfPools, 465 NoOfServers: len(admInfo.Servers), 466 NoOfDrives: noOfDrives, 467 TotalDriveSpace: totalSpace, 468 UsedDriveSpace: usedSpace, 469 NoOfBuckets: admInfo.Buckets.Count, 470 NoOfObjects: admInfo.Objects.Count, 471 }, 472 } 473 } 474 475 func getDriveSpaceInfo(admInfo madmin.InfoMessage) (uint64, uint64) { 476 total := uint64(0) 477 used := uint64(0) 478 for _, srvr := range admInfo.Servers { 479 for _, d := range srvr.Disks { 480 total += d.TotalSpace 481 used += d.UsedSpace 482 } 483 } 484 return total, used 485 } 486 487 func generateRegToken(clusterRegInfo ClusterRegistrationInfo) (string, error) { 488 token, e := json.Marshal(clusterRegInfo) 489 if e != nil { 490 return "", e 491 } 492 493 return base64.StdEncoding.EncodeToString(token), nil 494 } 495 496 func subnetLogin() (string, error) { 497 reader := bufio.NewReader(os.Stdin) 498 fmt.Print("SUBNET username: ") 499 username, _ := reader.ReadString('\n') 500 username = strings.TrimSpace(username) 501 502 if len(username) == 0 { 503 return "", errors.New("Username cannot be empty. If you don't have one, please create one from here: " + minioSubscriptionURL) 504 } 505 506 fmt.Print("Password: ") 507 bytepw, _ := term.ReadPassword(int(os.Stdin.Fd())) 508 fmt.Println() 509 510 loginReq := map[string]string{ 511 "username": username, 512 "password": string(bytepw), 513 } 514 respStr, e := SubnetPostReq(subnetLoginURL(), loginReq, nil) 515 if e != nil { 516 return "", e 517 } 518 519 mfaRequired := gjson.Get(respStr, "mfa_required").Bool() 520 if mfaRequired { 521 mfaToken := gjson.Get(respStr, "mfa_token").String() 522 fmt.Print("OTP received in email: ") 523 byteotp, _ := term.ReadPassword(int(os.Stdin.Fd())) 524 fmt.Println() 525 526 mfaLoginReq := SubnetMFAReq{Username: username, OTP: string(byteotp), Token: mfaToken} 527 respStr, e = SubnetPostReq(subnetMFAURL(), mfaLoginReq, nil) 528 if e != nil { 529 return "", e 530 } 531 } 532 533 token := gjson.Get(respStr, "token_info.access_token") 534 if token.Exists() { 535 return token.String(), nil 536 } 537 return "", fmt.Errorf("access token not found in response") 538 } 539 540 // getSubnetCreds - returns the API key and license. 541 // If only one of them is available, and if `--airgap` is not 542 // passed, it will attempt to fetch the other from SUBNET 543 // and save to config 544 func getSubnetCreds(alias string) (string, string, error) { 545 apiKey := getSubnetAPIKeyFromConfig(alias) 546 lic := getSubnetLicenseFromConfig(alias) 547 548 if (len(apiKey) > 0 && len(lic) > 0) || 549 (len(apiKey) == 0 && len(lic) == 0) || 550 globalAirgapped { 551 return apiKey, lic, nil 552 } 553 554 var e error 555 // Not airgapped, and only one of api-key or license is available 556 // Try to fetch and save the other. 557 if len(apiKey) > 0 { 558 lic, e = getSubnetLicenseUsingAPIKey(alias, apiKey) 559 } else { 560 apiKey, e = getSubnetAPIKeyUsingLicense(lic) 561 if e == nil { 562 setSubnetAPIKey(alias, apiKey) 563 } 564 } 565 566 if e != nil { 567 return "", "", e 568 } 569 570 return apiKey, lic, nil 571 } 572 573 // getSubnetAPIKey - returns the SUBNET API key. 574 // Returns error if the cluster is not registered with SUBNET. 575 func getSubnetAPIKey(alias string) (string, error) { 576 apiKey, lic, e := getSubnetCreds(alias) 577 if e != nil { 578 return "", e 579 } 580 if len(apiKey) == 0 && len(lic) == 0 { 581 e = fmt.Errorf("Please register the cluster first by running 'mc license register %s'", alias) 582 return "", e 583 } 584 return apiKey, nil 585 } 586 587 func getSubnetAPIKeyUsingLicense(lic string) (string, error) { 588 return getSubnetAPIKeyUsingAuthHeaders(SubnetLicenseAuthHeaders(lic)) 589 } 590 591 func getSubnetAPIKeyUsingAuthToken(authToken string) (string, error) { 592 return getSubnetAPIKeyUsingAuthHeaders(subnetTokenAuthHeaders(authToken)) 593 } 594 595 func getSubnetAPIKeyUsingAuthHeaders(authHeaders map[string]string) (string, error) { 596 resp, e := subnetGetReq(subnetAPIKeyURL(), authHeaders) 597 if e != nil { 598 return "", e 599 } 600 return extractSubnetCred("api_key", gjson.Parse(resp)) 601 } 602 603 func getSubnetLicenseUsingAPIKey(alias, apiKey string) (string, error) { 604 regInfo := GetClusterRegInfo(getAdminInfo(alias), alias) 605 _, lic, e := registerClusterOnSubnet(regInfo, alias, apiKey) 606 return lic, e 607 } 608 609 // registerClusterOnSubnet - Registers the given cluster on SUBNET using given API key for auth 610 // If the API key is empty, user will be asked to log in using SUBNET credentials. 611 func registerClusterOnSubnet(clusterRegInfo ClusterRegistrationInfo, alias, apiKey string) (string, string, error) { 612 regURL, headers, e := subnetURLWithAuth(SubnetRegisterURL(), apiKey) 613 if e != nil { 614 return "", "", e 615 } 616 617 regToken, e := generateRegToken(clusterRegInfo) 618 if e != nil { 619 return "", "", e 620 } 621 622 reqPayload := ClusterRegistrationReq{Token: regToken} 623 resp, e := SubnetPostReq(regURL, reqPayload, headers) 624 if e != nil { 625 return "", "", e 626 } 627 628 return extractAndSaveSubnetCreds(alias, resp) 629 } 630 631 func removeSubnetAuthConfig(alias string) { 632 setSubnetConfig(alias, "api_key", "") 633 setSubnetConfig(alias, "license", "") 634 } 635 636 // unregisterClusterFromSubnet - Unregisters the given cluster from SUBNET using given API key for auth 637 func unregisterClusterFromSubnet(depID, apiKey string) error { 638 regURL, headers, e := subnetURLWithAuth(subnetUnregisterURL(depID), apiKey) 639 if e != nil { 640 return e 641 } 642 643 _, e = SubnetPostReq(regURL, nil, headers) 644 return e 645 } 646 647 // validateAndSaveLic - validates the given license in minio config 648 // If the license contains api key and the saveApiKey arg is true, 649 // api key is also saved in the minio config 650 func validateAndSaveLic(lic, alias string, saveAPIKey bool) string { 651 li, e := parseLicense(lic) 652 fatalIf(probe.NewError(e), "Error parsing license") 653 654 if li.ExpiresAt.Before(time.Now()) { 655 fatalIf(errDummy().Trace(), fmt.Sprintf("License has expired on %s", li.ExpiresAt)) 656 } 657 658 if len(li.DeploymentID) > 0 && li.DeploymentID != uuid.Nil.String() && li.DeploymentID != getAdminInfo(alias).DeploymentID { 659 fatalIf(errDummy().Trace(), fmt.Sprintf("License is invalid for the deployment %s", alias)) 660 } 661 662 setSubnetLicense(alias, lic) 663 if len(li.APIKey) > 0 && saveAPIKey { 664 setSubnetAPIKey(alias, li.APIKey) 665 } 666 667 return li.APIKey 668 } 669 670 // extractAndSaveSubnetCreds - extract license from response and set it in minio config 671 func extractAndSaveSubnetCreds(alias, resp string) (string, string, error) { 672 parsedResp := gjson.Parse(resp) 673 674 lic, e := extractSubnetCred("license_v2", parsedResp) 675 if e != nil { 676 return "", "", e 677 } 678 if len(lic) > 0 { 679 apiKey := validateAndSaveLic(lic, alias, true) 680 if len(apiKey) > 0 { 681 return apiKey, lic, nil 682 } 683 } 684 685 apiKey, e := extractSubnetCred("api_key", parsedResp) 686 if e != nil { 687 return "", "", e 688 } 689 if len(apiKey) > 0 { 690 setSubnetAPIKey(alias, apiKey) 691 } 692 693 return apiKey, lic, nil 694 } 695 696 func extractSubnetCred(key string, resp gjson.Result) (string, error) { 697 result := resp.Get(key) 698 if result.Index == 0 { 699 return "", fmt.Errorf("Couldn't extract %s from SUBNET response: %s", key, resp) 700 } 701 return result.String(), nil 702 } 703 704 // parseLicense parses the license with the bundle public key and return it's information 705 func parseLicense(license string) (*licverifier.LicenseInfo, error) { 706 client := getSubnetClient() 707 lv := subnet.LicenseValidator{ 708 Client: *client, 709 ExpiryGracePeriod: 0, 710 } 711 lv.Init(GlobalDevMode) 712 return lv.ParseLicense(license) 713 } 714 715 func prepareSubnetUploadURL(uploadURL, alias, apiKey string) (string, map[string]string) { 716 var e error 717 if len(apiKey) == 0 { 718 // api key not passed as flag. check if it's available in the config 719 apiKey, e = getSubnetAPIKey(alias) 720 fatalIf(probe.NewError(e), "Unable to retrieve SUBNET API key") 721 } 722 723 reqURL, headers, e := subnetURLWithAuth(uploadURL, apiKey) 724 fatalIf(probe.NewError(e).Trace(uploadURL), "Unable to fetch SUBNET authentication") 725 726 return reqURL, headers 727 } 728 729 func getAPIKeyFlag(ctx *cli.Context) (string, error) { 730 apiKey := ctx.String("api-key") 731 732 if len(apiKey) == 0 { 733 return "", nil 734 } 735 736 _, e := uuid.Parse(apiKey) 737 if e != nil { 738 return "", e 739 } 740 741 return apiKey, nil 742 } 743 744 func initSubnetConnectivity(ctx *cli.Context, aliasedURL string, failOnConnErr bool) (string, string) { 745 if ctx.Bool("airgap") && len(ctx.String("api-key")) > 0 { 746 fatal(errDummy().Trace(), "--api-key is not applicable in airgap mode") 747 } 748 749 alias, _ := url2Alias(aliasedURL) 750 751 apiKey, e := getAPIKeyFlag(ctx) 752 fatalIf(probe.NewError(e), "Error in reading --api-key flag:") 753 754 // if `--airgap` is provided no need to test SUBNET connectivity. 755 if !globalAirgapped { 756 e = setGlobalSubnetProxyFromConfig(alias) 757 fatalIf(probe.NewError(e), "Error in setting SUBNET proxy:") 758 759 sbu := SubnetBaseURL() 760 err := checkURLReachable(sbu) 761 if err != nil && failOnConnErr { 762 fatal(err.Trace(aliasedURL), "Unable to reach %s, please use --airgap if there is no connectivity to SUBNET", sbu) 763 } 764 } 765 766 return alias, apiKey 767 }