github.com/minio/madmin-go/v3@v3.0.51/metrics_client.go (about) 1 // 2 // Copyright (c) 2015-2023 MinIO, Inc. 3 // 4 // This file is part of MinIO Object Storage stack 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU Affero General Public License as 8 // published by the Free Software Foundation, either version 3 of the 9 // License, or (at your option) any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU Affero General Public License for more details. 15 // 16 // You should have received a copy of the GNU Affero General Public License 17 // along with this program. If not, see <http://www.gnu.org/licenses/>. 18 // 19 20 package madmin 21 22 import ( 23 "context" 24 "fmt" 25 "net/http" 26 "net/url" 27 "time" 28 29 jwtgo "github.com/golang-jwt/jwt/v4" 30 "github.com/minio/minio-go/v7/pkg/credentials" 31 ) 32 33 const ( 34 defaultPrometheusJWTExpiry = 100 * 365 * 24 * time.Hour 35 libraryMinioURLPrefix = "/minio" 36 prometheusIssuer = "prometheus" 37 ) 38 39 // MetricsClient implements MinIO metrics operations 40 type MetricsClient struct { 41 /// Credentials for authentication 42 creds *credentials.Credentials 43 // Indicate whether we are using https or not 44 secure bool 45 // Parsed endpoint url provided by the user. 46 endpointURL *url.URL 47 // Needs allocation. 48 httpClient *http.Client 49 } 50 51 // metricsRequestData - is container for all the values to make a 52 // request. 53 type metricsRequestData struct { 54 relativePath string // URL path relative to admin API base endpoint 55 } 56 57 // NewMetricsClientWithOptions - instantiate minio metrics client honoring Prometheus format 58 func NewMetricsClientWithOptions(endpoint string, opts *Options) (*MetricsClient, error) { 59 if opts == nil { 60 return nil, ErrInvalidArgument("empty options not allowed") 61 } 62 63 endpointURL, err := getEndpointURL(endpoint, opts.Secure) 64 if err != nil { 65 return nil, err 66 } 67 68 clnt, err := privateNewMetricsClient(endpointURL, opts) 69 if err != nil { 70 return nil, err 71 } 72 return clnt, nil 73 } 74 75 // NewMetricsClient - instantiate minio metrics client honoring Prometheus format 76 // Deprecated: please use NewMetricsClientWithOptions 77 func NewMetricsClient(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*MetricsClient, error) { 78 return NewMetricsClientWithOptions(endpoint, &Options{ 79 Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), 80 Secure: secure, 81 }) 82 } 83 84 // getPrometheusToken creates a JWT from MinIO access and secret keys 85 func getPrometheusToken(accessKey, secretKey string) (string, error) { 86 jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.RegisteredClaims{ 87 ExpiresAt: jwtgo.NewNumericDate(time.Now().UTC().Add(defaultPrometheusJWTExpiry)), 88 Subject: accessKey, 89 Issuer: prometheusIssuer, 90 }) 91 92 token, err := jwt.SignedString([]byte(secretKey)) 93 if err != nil { 94 return "", err 95 } 96 return token, nil 97 } 98 99 func privateNewMetricsClient(endpointURL *url.URL, opts *Options) (*MetricsClient, error) { 100 clnt := new(MetricsClient) 101 clnt.creds = opts.Creds 102 clnt.secure = opts.Secure 103 clnt.endpointURL = endpointURL 104 105 tr := opts.Transport 106 if tr == nil { 107 tr = DefaultTransport(opts.Secure) 108 } 109 110 clnt.httpClient = &http.Client{ 111 Transport: tr, 112 } 113 return clnt, nil 114 } 115 116 // executeGetRequest - instantiates a Get method and performs the request 117 func (client *MetricsClient) executeGetRequest(ctx context.Context, reqData metricsRequestData) (res *http.Response, err error) { 118 req, err := client.newGetRequest(ctx, reqData) 119 if err != nil { 120 return nil, err 121 } 122 123 v, err := client.creds.Get() 124 if err != nil { 125 return nil, err 126 } 127 128 accessKeyID := v.AccessKeyID 129 secretAccessKey := v.SecretAccessKey 130 131 jwtToken, err := getPrometheusToken(accessKeyID, secretAccessKey) 132 if err != nil { 133 return nil, err 134 } 135 136 req.Header.Add("Authorization", "Bearer "+jwtToken) 137 req.Header.Set("X-Amz-Security-Token", v.SessionToken) 138 139 return client.httpClient.Do(req) 140 } 141 142 // newGetRequest - instantiate a new HTTP GET request 143 func (client *MetricsClient) newGetRequest(ctx context.Context, reqData metricsRequestData) (req *http.Request, err error) { 144 targetURL, err := client.makeTargetURL(reqData) 145 if err != nil { 146 return nil, err 147 } 148 149 return http.NewRequestWithContext(ctx, http.MethodGet, targetURL.String(), nil) 150 } 151 152 // makeTargetURL make a new target url. 153 func (client *MetricsClient) makeTargetURL(r metricsRequestData) (*url.URL, error) { 154 if client.endpointURL == nil { 155 return nil, fmt.Errorf("enpointURL cannot be nil") 156 } 157 158 host := client.endpointURL.Host 159 scheme := client.endpointURL.Scheme 160 prefix := libraryMinioURLPrefix 161 162 urlStr := scheme + "://" + host + prefix + r.relativePath 163 return url.Parse(urlStr) 164 } 165 166 // SetCustomTransport - set new custom transport. 167 // Deprecated: please use Options{Transport: tr} to provide custom transport. 168 func (client *MetricsClient) SetCustomTransport(customHTTPTransport http.RoundTripper) { 169 // Set this to override default transport 170 // ``http.DefaultTransport``. 171 // 172 // This transport is usually needed for debugging OR to add your 173 // own custom TLS certificates on the client transport, for custom 174 // CA's and certs which are not part of standard certificate 175 // authority follow this example :- 176 // 177 // tr := &http.Transport{ 178 // TLSClientConfig: &tls.Config{RootCAs: pool}, 179 // DisableCompression: true, 180 // } 181 // api.SetTransport(tr) 182 // 183 if client.httpClient != nil { 184 client.httpClient.Transport = customHTTPTransport 185 } 186 }