github.com/minio/console@v1.3.0/pkg/subnet/utils.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2023 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package subnet
    18  
    19  import (
    20  	"bytes"
    21  	"crypto/tls"
    22  	"encoding/base64"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io"
    26  	"net"
    27  	"net/http"
    28  	"time"
    29  
    30  	"github.com/mattn/go-ieproxy"
    31  	xhttp "github.com/minio/console/pkg/http"
    32  	"github.com/tidwall/gjson"
    33  
    34  	"github.com/minio/madmin-go/v3"
    35  	mc "github.com/minio/mc/cmd"
    36  	"github.com/minio/pkg/v2/env"
    37  )
    38  
    39  const (
    40  	subnetRespBodyLimit = 1 << 20 // 1 MiB
    41  )
    42  
    43  func subnetBaseURL() string {
    44  	return env.Get(ConsoleSubnetURL, "https://subnet.min.io")
    45  }
    46  
    47  func subnetRegisterURL() string {
    48  	return subnetBaseURL() + "/api/cluster/register"
    49  }
    50  
    51  func subnetLoginURL() string {
    52  	return subnetBaseURL() + "/api/auth/login"
    53  }
    54  
    55  func subnetOrgsURL() string {
    56  	return subnetBaseURL() + "/api/auth/organizations"
    57  }
    58  
    59  func subnetMFAURL() string {
    60  	return subnetBaseURL() + "/api/auth/mfa-login"
    61  }
    62  
    63  func subnetAPIKeyURL() string {
    64  	return subnetBaseURL() + "/api/auth/api-key"
    65  }
    66  
    67  func LogWebhookURL() string {
    68  	return subnetBaseURL() + "/api/logs"
    69  }
    70  
    71  func UploadURL(uploadType string, filename string) string {
    72  	return fmt.Sprintf("%s/api/%s/upload?filename=%s", subnetBaseURL(), uploadType, filename)
    73  }
    74  
    75  func UploadAuthHeaders(apiKey string) map[string]string {
    76  	return map[string]string{"x-subnet-api-key": apiKey}
    77  }
    78  
    79  func GenerateRegToken(clusterRegInfo mc.ClusterRegistrationInfo) (string, error) {
    80  	token, e := json.Marshal(clusterRegInfo)
    81  	if e != nil {
    82  		return "", e
    83  	}
    84  
    85  	return base64.StdEncoding.EncodeToString(token), nil
    86  }
    87  
    88  func subnetAuthHeaders(authToken string) map[string]string {
    89  	return map[string]string{"Authorization": "Bearer " + authToken}
    90  }
    91  
    92  func httpDo(client xhttp.ClientI, req *http.Request) (*http.Response, error) {
    93  	return client.Do(req)
    94  }
    95  
    96  func subnetReqDo(client xhttp.ClientI, r *http.Request, headers map[string]string) (string, error) {
    97  	for k, v := range headers {
    98  		r.Header.Add(k, v)
    99  	}
   100  
   101  	ct := r.Header.Get("Content-Type")
   102  	if len(ct) == 0 {
   103  		r.Header.Add("Content-Type", "application/json")
   104  	}
   105  
   106  	resp, e := httpDo(client, r)
   107  	if e != nil {
   108  		return "", e
   109  	}
   110  
   111  	defer resp.Body.Close()
   112  	respBytes, e := io.ReadAll(io.LimitReader(resp.Body, subnetRespBodyLimit))
   113  	if e != nil {
   114  		return "", e
   115  	}
   116  	respStr := string(respBytes)
   117  
   118  	if resp.StatusCode == http.StatusOK {
   119  		return respStr, nil
   120  	}
   121  	return respStr, fmt.Errorf("Request failed with code %d and errors: %s", resp.StatusCode, respStr)
   122  }
   123  
   124  func subnetGetReq(client xhttp.ClientI, reqURL string, headers map[string]string) (string, error) {
   125  	r, e := http.NewRequest(http.MethodGet, reqURL, nil)
   126  	if e != nil {
   127  		return "", e
   128  	}
   129  	return subnetReqDo(client, r, headers)
   130  }
   131  
   132  func subnetPostReq(client xhttp.ClientI, reqURL string, payload interface{}, headers map[string]string) (string, error) {
   133  	body, e := json.Marshal(payload)
   134  	if e != nil {
   135  		return "", e
   136  	}
   137  	r, e := http.NewRequest(http.MethodPost, reqURL, bytes.NewReader(body))
   138  	if e != nil {
   139  		return "", e
   140  	}
   141  	return subnetReqDo(client, r, headers)
   142  }
   143  
   144  func GetClusterRegInfo(admInfo madmin.InfoMessage) mc.ClusterRegistrationInfo {
   145  	return mc.GetClusterRegInfo(admInfo, admInfo.DeploymentID)
   146  }
   147  
   148  func GetSubnetAPIKeyUsingLicense(lic string) (string, error) {
   149  	return getSubnetAPIKeyUsingAuthHeaders(map[string]string{"x-subnet-license": lic})
   150  }
   151  
   152  func getSubnetAPIKeyUsingAuthHeaders(authHeaders map[string]string) (string, error) {
   153  	resp, e := subnetGetReqMC(subnetAPIKeyURL(), authHeaders)
   154  	if e != nil {
   155  		return "", e
   156  	}
   157  	return extractSubnetCred("api_key", gjson.Parse(resp))
   158  }
   159  
   160  func extractSubnetCred(key string, resp gjson.Result) (string, error) {
   161  	result := resp.Get(key)
   162  	if result.Index == 0 {
   163  		return "", fmt.Errorf("Couldn't extract %s from SUBNET response: %s", key, resp)
   164  	}
   165  	return result.String(), nil
   166  }
   167  
   168  func subnetGetReqMC(reqURL string, headers map[string]string) (string, error) {
   169  	r, e := http.NewRequest(http.MethodGet, reqURL, nil)
   170  	if e != nil {
   171  		return "", e
   172  	}
   173  	return subnetReqDoMC(r, headers)
   174  }
   175  
   176  func subnetReqDoMC(r *http.Request, headers map[string]string) (string, error) {
   177  	for k, v := range headers {
   178  		r.Header.Add(k, v)
   179  	}
   180  
   181  	ct := r.Header.Get("Content-Type")
   182  	if len(ct) == 0 {
   183  		r.Header.Add("Content-Type", "application/json")
   184  	}
   185  
   186  	resp, e := httpClientSubnet(0).Do(r)
   187  	if e != nil {
   188  		return "", e
   189  	}
   190  
   191  	defer resp.Body.Close()
   192  	respBytes, e := io.ReadAll(io.LimitReader(resp.Body, subnetRespBodyLimit))
   193  	if e != nil {
   194  		return "", e
   195  	}
   196  	respStr := string(respBytes)
   197  
   198  	if resp.StatusCode == http.StatusOK {
   199  		return respStr, nil
   200  	}
   201  	return respStr, fmt.Errorf("Request failed with code %d with error: %s", resp.StatusCode, respStr)
   202  }
   203  
   204  func httpClientSubnet(reqTimeout time.Duration) *http.Client {
   205  	return &http.Client{
   206  		Timeout: reqTimeout,
   207  		Transport: &http.Transport{
   208  			DialContext: (&net.Dialer{
   209  				Timeout: 10 * time.Second,
   210  			}).DialContext,
   211  			Proxy: ieproxy.GetProxyFunc(),
   212  			TLSClientConfig: &tls.Config{
   213  				// Can't use SSLv3 because of POODLE and BEAST
   214  				// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
   215  				// Can't use TLSv1.1 because of RC4 cipher usage
   216  				MinVersion: tls.VersionTLS12,
   217  			},
   218  			IdleConnTimeout:       90 * time.Second,
   219  			TLSHandshakeTimeout:   10 * time.Second,
   220  			ExpectContinueTimeout: 10 * time.Second,
   221  		},
   222  	}
   223  }