From ef9781018dbecc319d53bfda46e7633be693e6d1 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Sat, 17 Feb 2024 10:44:40 -0800 Subject: [PATCH] Add support for STS properly in metrics client (#271) --- .github/workflows/vulncheck.yml | 2 +- api.go | 26 +++++++++----- metrics_client.go | 60 +++++++++++++++++++++++++-------- metrics_client_test.go | 23 ------------- 4 files changed, 64 insertions(+), 47 deletions(-) diff --git a/.github/workflows/vulncheck.yml b/.github/workflows/vulncheck.yml index 4c8ee2b0..b3b52006 100644 --- a/.github/workflows/vulncheck.yml +++ b/.github/workflows/vulncheck.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: [ 1.21.5 ] + go-version: [ 1.21.7 ] steps: - name: Check out code into the Go module directory uses: actions/checkout@v3 diff --git a/api.go b/api.go index 8b65e5da..d4001e46 100644 --- a/api.go +++ b/api.go @@ -95,16 +95,18 @@ const ( // Options for New method type Options struct { - Creds *credentials.Credentials - Secure bool + Creds *credentials.Credentials + Secure bool + Transport http.RoundTripper // Add future fields here } // New - instantiate minio admin client +// Deprecated: please use NewWithOptions func New(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*AdminClient, error) { creds := credentials.NewStaticV4(accessKeyID, secretAccessKey, "") - clnt, err := privateNew(endpoint, creds, secure) + clnt, err := privateNew(endpoint, &Options{Creds: creds, Secure: secure}) if err != nil { return nil, err } @@ -113,14 +115,14 @@ func New(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*Ad // NewWithOptions - instantiate minio admin client with options. func NewWithOptions(endpoint string, opts *Options) (*AdminClient, error) { - clnt, err := privateNew(endpoint, opts.Creds, opts.Secure) + clnt, err := privateNew(endpoint, opts) if err != nil { return nil, err } return clnt, nil } -func privateNew(endpoint string, creds *credentials.Credentials, secure bool) (*AdminClient, error) { +func privateNew(endpoint string, opts *Options) (*AdminClient, error) { // Initialize cookies to preserve server sent cookies if any and replay // them upon each request. jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) @@ -129,7 +131,7 @@ func privateNew(endpoint string, creds *credentials.Credentials, secure bool) (* } // construct endpoint. - endpointURL, err := getEndpointURL(endpoint, secure) + endpointURL, err := getEndpointURL(endpoint, opts.Secure) if err != nil { return nil, err } @@ -137,18 +139,23 @@ func privateNew(endpoint string, creds *credentials.Credentials, secure bool) (* clnt := new(AdminClient) // Save the credentials. - clnt.credsProvider = creds + clnt.credsProvider = opts.Creds // Remember whether we are using https or not - clnt.secure = secure + clnt.secure = opts.Secure // Save endpoint URL, user agent for future uses. clnt.endpointURL = endpointURL + tr := opts.Transport + if tr == nil { + tr = DefaultTransport(opts.Secure) + } + // Instantiate http client and bucket location cache. clnt.httpClient = &http.Client{ Jar: jar, - Transport: DefaultTransport(secure), + Transport: tr, } // Add locked pseudo-random number generator. @@ -169,6 +176,7 @@ func (adm *AdminClient) SetAppInfo(appName string, appVersion string) { } // SetCustomTransport - set new custom transport. +// Deprecated: please use Options{Transport: tr} to provide custom transport. func (adm *AdminClient) SetCustomTransport(customHTTPTransport http.RoundTripper) { // Set this to override default transport // ``http.DefaultTransport``. diff --git a/metrics_client.go b/metrics_client.go index c735e812..231b56a7 100644 --- a/metrics_client.go +++ b/metrics_client.go @@ -27,6 +27,7 @@ import ( "time" jwtgo "github.com/golang-jwt/jwt/v4" + "github.com/minio/minio-go/v7/pkg/credentials" ) const ( @@ -37,8 +38,8 @@ const ( // MetricsClient implements MinIO metrics operations type MetricsClient struct { - /// JWT token for authentication - jwtToken string + /// Credentials for authentication + creds *credentials.Credentials // Indicate whether we are using https or not secure bool // Parsed endpoint url provided by the user. @@ -53,25 +54,33 @@ type metricsRequestData struct { relativePath string // URL path relative to admin API base endpoint } -// NewMetricsClient - instantiate minio metrics client honoring Prometheus format -func NewMetricsClient(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*MetricsClient, error) { - jwtToken, err := getPrometheusToken(accessKeyID, secretAccessKey) - if err != nil { - return nil, err +// NewMetricsClientWithOptions - instantiate minio metrics client honoring Prometheus format +func NewMetricsClientWithOptions(endpoint string, opts *Options) (*MetricsClient, error) { + if opts == nil { + return nil, ErrInvalidArgument("empty options not allowed") } - endpointURL, err := getEndpointURL(endpoint, secure) + endpointURL, err := getEndpointURL(endpoint, opts.Secure) if err != nil { return nil, err } - clnt, err := privateNewMetricsClient(endpointURL, jwtToken, secure) + clnt, err := privateNewMetricsClient(endpointURL, opts) if err != nil { return nil, err } return clnt, nil } +// NewMetricsClient - instantiate minio metrics client honoring Prometheus format +// Deprecated: please use NewMetricsClientWithOptions +func NewMetricsClient(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*MetricsClient, error) { + return NewMetricsClientWithOptions(endpoint, &Options{ + Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), + Secure: secure, + }) +} + // getPrometheusToken creates a JWT from MinIO access and secret keys func getPrometheusToken(accessKey, secretKey string) (string, error) { jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.RegisteredClaims{ @@ -87,13 +96,19 @@ func getPrometheusToken(accessKey, secretKey string) (string, error) { return token, nil } -func privateNewMetricsClient(endpointURL *url.URL, jwtToken string, secure bool) (*MetricsClient, error) { +func privateNewMetricsClient(endpointURL *url.URL, opts *Options) (*MetricsClient, error) { clnt := new(MetricsClient) - clnt.jwtToken = jwtToken - clnt.secure = secure + clnt.creds = opts.Creds + clnt.secure = opts.Secure clnt.endpointURL = endpointURL + + tr := opts.Transport + if tr == nil { + tr = DefaultTransport(opts.Secure) + } + clnt.httpClient = &http.Client{ - Transport: DefaultTransport(secure), + Transport: tr, } return clnt, nil } @@ -104,7 +119,23 @@ func (client *MetricsClient) executeGetRequest(ctx context.Context, reqData metr if err != nil { return nil, err } - req.Header.Add("Authorization", "Bearer "+client.jwtToken) + + v, err := client.creds.Get() + if err != nil { + return nil, err + } + + accessKeyID := v.AccessKeyID + secretAccessKey := v.SecretAccessKey + + jwtToken, err := getPrometheusToken(accessKeyID, secretAccessKey) + if err != nil { + return nil, err + } + + req.Header.Add("Authorization", "Bearer "+jwtToken) + req.Header.Set("X-Amz-Security-Token", v.SessionToken) + return client.httpClient.Do(req) } @@ -133,6 +164,7 @@ func (client *MetricsClient) makeTargetURL(r metricsRequestData) (*url.URL, erro } // SetCustomTransport - set new custom transport. +// Deprecated: please use Options{Transport: tr} to provide custom transport. func (client *MetricsClient) SetCustomTransport(customHTTPTransport http.RoundTripper) { // Set this to override default transport // ``http.DefaultTransport``. diff --git a/metrics_client_test.go b/metrics_client_test.go index 307d9e71..2a6e257a 100644 --- a/metrics_client_test.go +++ b/metrics_client_test.go @@ -75,29 +75,6 @@ func TestMakeTargetUrlReturnsErrorOnURLParse(t *testing.T) { } } -func TestPrivteNewMetricsClientInstantiatesMetricsClientWithRequiredFields(t *testing.T) { - endpointURL := &url.URL{} - jwtToken := "someToken" - secure := true - - clnt, err := privateNewMetricsClient(endpointURL, jwtToken, secure) - if err != nil { - t.Errorf("error not expected, got: %v", err) - } - if clnt.endpointURL != endpointURL { - t.Errorf("clnt.endpointURL: %s not equal to endpointURL: %s", clnt.endpointURL, endpointURL) - } - if clnt.jwtToken != jwtToken { - t.Errorf("clnt.jwtToken: %s not equal to jwtToken: %s", clnt.jwtToken, jwtToken) - } - if clnt.secure != secure { - t.Errorf("clnt.secure: %v not equal to secure: %v", clnt.secure, secure) - } - if clnt.httpClient.Transport == nil { - t.Errorf("clnt.Transport expecting not nil") - } -} - func TestGetPrometheusTokenReturnsValidJwtTokenFromAccessAndSecretKey(t *testing.T) { accessKey := "myaccessKey" secretKey := "mysecretKey"