From 5b8176c68eabba0bb47835e65e9334811dd8e8ea Mon Sep 17 00:00:00 2001 From: Nikolay Date: Sat, 22 May 2021 16:20:18 +0300 Subject: [PATCH] basic OAuth2 support for remoteWrite and scrape targets (#1316) * adds OAuth2 support for remoteWrite and scrapping * adds tests changes init --- app/vmagent/remotewrite/client.go | 48 ++++++- go.mod | 2 +- go.sum | 4 +- lib/promauth/config.go | 97 +++++++++++++- lib/promauth/config_test.go | 125 ++++++++++++++++++ lib/promauth/testdata/test_secretfile.txt | 1 + lib/promscrape/discovery/consul/api.go | 2 +- lib/promscrape/discovery/kubernetes/api.go | 2 +- lib/promscrape/discovery/openstack/api.go | 2 +- .../clientcredentials/clientcredentials.go | 120 +++++++++++++++++ vendor/modules.txt | 3 +- 11 files changed, 396 insertions(+), 10 deletions(-) create mode 100644 lib/promauth/config_test.go create mode 100644 lib/promauth/testdata/test_secretfile.txt create mode 100644 vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go diff --git a/app/vmagent/remotewrite/client.go b/app/vmagent/remotewrite/client.go index 103e8848b..6cb87769d 100644 --- a/app/vmagent/remotewrite/client.go +++ b/app/vmagent/remotewrite/client.go @@ -44,6 +44,17 @@ var ( "If multiple args are set, then they are applied independently for the corresponding -remoteWrite.url") bearerToken = flagutil.NewArray("remoteWrite.bearerToken", "Optional bearer auth token to use for -remoteWrite.url. "+ "If multiple args are set, then they are applied independently for the corresponding -remoteWrite.url") + + clientID = flagutil.NewArray("remoteWrite.oauth2.clientID", "Optional OAuth2 clientID to use for -remoteWrite.url."+ + "If multiple args are set, then they are applied independently for the corresponding -remoteWrite.url") + clientSecret = flagutil.NewArray("remoteWrite.oauth2.clientSecret", "Optional OAuth2 clientSecret to use for -remoteWrite.url."+ + "If multiple args are set, then they are applied independently for the corresponding -remoteWrite.url") + clientSecretFile = flagutil.NewArray("remoteWrite.oauth2.clientSecretFile", "Optional OAuth2 clientSecretFile to use for -remoteWrite.url."+ + "If multiple args are set, then they are applied independently for the corresponding -remoteWrite.url") + tokenURL = flagutil.NewArray("remoteWrite.oauth2.tokenUrl", "Optional OAuth2 token url to use for -remoteWrite.url."+ + "If multiple args are set, then they are applied independently for the corresponding -remoteWrite.url") + oAuth2Scopes = flagutil.NewArray("remoteWrite.oauth2.scopes", "Optional OAuth2 scopes to use for -remoteWrite.url."+ + "If multiple args are set, then they are applied independently for the corresponding -remoteWrite.url") ) type client struct { @@ -53,6 +64,8 @@ type client struct { fq *persistentqueue.FastQueue hc *http.Client + authCfg *promauth.Config + rl rateLimiter bytesSent *metrics.Counter @@ -72,6 +85,7 @@ func newClient(argIdx int, remoteWriteURL, sanitizedURL string, fq *persistentqu if err != nil { logger.Panicf("FATAL: cannot initialize TLS config: %s", err) } + tr := &http.Transport{ Dial: statDial, TLSClientConfig: tlsCfg, @@ -108,10 +122,18 @@ func newClient(argIdx int, remoteWriteURL, sanitizedURL string, fq *persistentqu } authHeader = "Bearer " + token } + authCfg, err := getAuthConfig(argIdx) + if err != nil { + logger.Fatalf("FATAL: cannot create OAuth2 config for remoteWrite idx: %d, err: %s", argIdx, err) + } + if authCfg != nil && authHeader != "" { + logger.Fatalf("`-remoteWrite.bearerToken`=%q or `-remoteWrite.basicAuth.* cannot be set when `-remoteWrite.oauth2.*` flags are set", token) + } c := &client{ sanitizedURL: sanitizedURL, remoteWriteURL: remoteWriteURL, authHeader: authHeader, + authCfg: authCfg, fq: fq, hc: &http.Client{ Transport: tr, @@ -160,7 +182,7 @@ func getTLSConfig(argIdx int) (*tls.Config, error) { if c.CAFile == "" && c.CertFile == "" && c.KeyFile == "" && c.ServerName == "" && !c.InsecureSkipVerify { return nil, nil } - cfg, err := promauth.NewConfig(".", nil, nil, "", "", c) + cfg, err := promauth.NewConfig(".", nil, nil, "", "", nil, c) if err != nil { return nil, fmt.Errorf("cannot populate TLS config: %w", err) } @@ -168,6 +190,25 @@ func getTLSConfig(argIdx int) (*tls.Config, error) { return tlsCfg, nil } +func getAuthConfig(argIdx int) (*promauth.Config, error) { + + oAuth2Cfg := &promauth.OAuth2Config{ + ClientID: clientID.GetOptionalArg(argIdx), + ClientSecret: clientSecret.GetOptionalArg(argIdx), + ClientSecretFile: clientSecretFile.GetOptionalArg(argIdx), + TokenURL: tokenURL.GetOptionalArg(argIdx), + Scopes: strings.Split(oAuth2Scopes.GetOptionalArg(argIdx), ";"), + } + if oAuth2Cfg.ClientSecretFile == "" && oAuth2Cfg.ClientSecret == "" { + return nil, nil + } + authCfg, err := promauth.NewConfig("", nil, nil, "", "", oAuth2Cfg, nil) + if err != nil { + return nil, fmt.Errorf("cannot populate OAuth2 config for remoteWrite idx: %d, err: %w", argIdx, err) + } + return authCfg, nil +} + func (c *client) runWorker() { var ok bool var block []byte @@ -229,6 +270,11 @@ again: if c.authHeader != "" { req.Header.Set("Authorization", c.authHeader) } + // add oauth2 header on best effort. + // remote storage may return error with incorrect authorization. + if c.authCfg != nil { + req.Header.Set("Authorization", c.authCfg.GetAuthHeader()) + } startTime := time.Now() resp, err := c.hc.Do(req) diff --git a/go.mod b/go.mod index b355ad4ea..814963091 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/valyala/gozstd v1.10.0 github.com/valyala/histogram v1.1.2 github.com/valyala/quicktemplate v1.6.3 - golang.org/x/net v0.0.0-20210510120150-4163338589ed + golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 google.golang.org/api v0.47.0 diff --git a/go.sum b/go.sum index 67344b945..9b20abbb8 100644 --- a/go.sum +++ b/go.sum @@ -533,7 +533,7 @@ github.com/influxdata/flux v0.113.0/go.mod h1:3TJtvbm/Kwuo5/PEo5P6HUzwVg4bXWkb2w github.com/influxdata/httprouter v1.3.1-0.20191122104820-ee83e2772f69/go.mod h1:pwymjR6SrP3gD3pRj9RJwdl1j5s3doEEV8gS4X9qSzA= github.com/influxdata/influxdb v1.8.0/go.mod h1:SIzcnsjaHRFpmlxpJ4S3NT64qtEKYweNTUMb/vh0OMQ= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= -github.com/influxdata/influxdb v1.9.0 h1:9z/aRmTpWT1rIm4EN+qTJTZqgEdLGZ4xRMgvA276UEA= +github.com/influxdata/influxdb v1.9.0 h1:KefL3i2JdNgsZwKRlHkkV+sYs4ejJ+aGF6glBUoKKio= github.com/influxdata/influxdb v1.9.0/go.mod h1:UEe3MeD9AaP5rlPIes102IhYua3FhIWZuOXNHxDjSrI= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxql v1.1.0/go.mod h1:KpVI7okXjK6PRi3Z5B+mtKZli+R1DnZgb3N+tzevNgo= @@ -1033,6 +1033,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/lib/promauth/config.go b/lib/promauth/config.go index b89693301..02812c405 100644 --- a/lib/promauth/config.go +++ b/lib/promauth/config.go @@ -2,6 +2,7 @@ package promauth import ( "bytes" + "context" "crypto/tls" "crypto/x509" "encoding/base64" @@ -11,6 +12,8 @@ import ( "github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime" "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" + "golang.org/x/oauth2" + "golang.org/x/oauth2/clientcredentials" ) // TLSConfig represents TLS config. @@ -46,6 +49,7 @@ type HTTPClientConfig struct { BasicAuth *BasicAuthConfig `yaml:"basic_auth,omitempty"` BearerToken string `yaml:"bearer_token,omitempty"` BearerTokenFile string `yaml:"bearer_token_file,omitempty"` + OAuth2 *OAuth2Config `yaml:"oauth2,omitempty"` TLSConfig *TLSConfig `yaml:"tls_config,omitempty"` } @@ -58,6 +62,69 @@ type ProxyClientConfig struct { TLSConfig *TLSConfig `yaml:"proxy_tls_config,omitempty"` } +// OAuth2Config represent OAuth2 configuration +type OAuth2Config struct { + ClientID string `yaml:"client_id"` + ClientSecretFile string `yaml:"client_secret_file"` + Scopes []string `yaml:"scopes"` + TokenURL string `yaml:"token_url"` + // mu guards tokenSource and client Secret + mu sync.Mutex + ClientSecret string `yaml:"client_secret"` + tokenSource oauth2.TokenSource +} + +func (o *OAuth2Config) refreshTokenSourceLocked() { + cfg := clientcredentials.Config{ + ClientID: o.ClientID, + ClientSecret: o.ClientSecret, + TokenURL: o.TokenURL, + Scopes: o.Scopes, + } + o.tokenSource = cfg.TokenSource(context.Background()) +} + +// validate checks given configs. +func (o *OAuth2Config) validate() error { + if o.TokenURL == "" { + return fmt.Errorf("token url cannot be empty") + } + if o.ClientSecret == "" && o.ClientSecretFile == "" { + return fmt.Errorf("ClientSecret or ClientSecretFile must be set") + } + if o.ClientSecret != "" && o.ClientSecretFile != "" { + return fmt.Errorf("only one option can be set ClientSecret or ClientSecretFile, provided both") + } + return nil +} + +func (o *OAuth2Config) getAuthHeader() (string, error) { + var needUpdate bool + if o.ClientSecretFile != "" { + newSecret, err := readPasswordFromFile(o.ClientSecretFile) + if err != nil { + return "", fmt.Errorf("cannot read OAuth2 config file with path: %s, err: %w", o.ClientSecretFile, err) + } + o.mu.Lock() + if o.ClientSecret != newSecret { + o.ClientSecret = newSecret + needUpdate = true + } + o.mu.Unlock() + } + o.mu.Lock() + defer o.mu.Unlock() + if needUpdate { + o.refreshTokenSourceLocked() + } + t, err := o.tokenSource.Token() + if err != nil { + return "", fmt.Errorf("cannot fetch token for OAuth2 client: %w", err) + } + + return t.Type() + " " + t.AccessToken, nil +} + // Config is auth config. type Config struct { // Optional TLS config @@ -131,16 +198,16 @@ func (ac *Config) NewTLSConfig() *tls.Config { // NewConfig creates auth config for the given hcc. func (hcc *HTTPClientConfig) NewConfig(baseDir string) (*Config, error) { - return NewConfig(baseDir, hcc.Authorization, hcc.BasicAuth, hcc.BearerToken, hcc.BearerTokenFile, hcc.TLSConfig) + return NewConfig(baseDir, hcc.Authorization, hcc.BasicAuth, hcc.BearerToken, hcc.BearerTokenFile, hcc.OAuth2, hcc.TLSConfig) } // NewConfig creates auth config for the given pcc. func (pcc *ProxyClientConfig) NewConfig(baseDir string) (*Config, error) { - return NewConfig(baseDir, pcc.Authorization, pcc.BasicAuth, pcc.BearerToken, pcc.BearerTokenFile, pcc.TLSConfig) + return NewConfig(baseDir, pcc.Authorization, pcc.BasicAuth, pcc.BearerToken, pcc.BearerTokenFile, nil, pcc.TLSConfig) } // NewConfig creates auth config from the given args. -func NewConfig(baseDir string, az *Authorization, basicAuth *BasicAuthConfig, bearerToken, bearerTokenFile string, tlsConfig *TLSConfig) (*Config, error) { +func NewConfig(baseDir string, az *Authorization, basicAuth *BasicAuthConfig, bearerToken, bearerTokenFile string, oauth *OAuth2Config, tlsConfig *TLSConfig) (*Config, error) { var getAuthHeader func() string authDigest := "" if az != nil { @@ -230,6 +297,30 @@ func NewConfig(baseDir string, az *Authorization, basicAuth *BasicAuthConfig, be } authDigest = fmt.Sprintf("bearer(token=%q)", bearerToken) } + if oauth != nil { + if getAuthHeader != nil { + return nil, fmt.Errorf("cannot simultaneously use `authorization`, `basic_auth, `bearer_token` and `ouath2`") + } + if err := oauth.validate(); err != nil { + return nil, err + } + if oauth.ClientSecretFile != "" { + secret, err := readPasswordFromFile(oauth.ClientSecretFile) + if err != nil { + return nil, err + } + oauth.ClientSecret = secret + } + oauth.refreshTokenSourceLocked() + getAuthHeader = func() string { + h, err := oauth.getAuthHeader() + if err != nil { + logger.Errorf("cannot get OAuth2 header: %s", err) + return "" + } + return h + } + } var tlsRootCA *x509.CertPool var tlsCertificate *tls.Certificate tlsServerName := "" diff --git a/lib/promauth/config_test.go b/lib/promauth/config_test.go new file mode 100644 index 000000000..0d438fd3f --- /dev/null +++ b/lib/promauth/config_test.go @@ -0,0 +1,125 @@ +package promauth + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestNewConfig(t *testing.T) { + type args struct { + baseDir string + az *Authorization + basicAuth *BasicAuthConfig + bearerToken string + bearerTokenFile string + oauth *OAuth2Config + tlsConfig *TLSConfig + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "OAuth2 config", + args: args{ + oauth: &OAuth2Config{ + ClientID: "some-id", + ClientSecret: "some-secret", + TokenURL: "http://localhost:8511", + }, + }, + }, + { + name: "OAuth2 config with file", + args: args{ + oauth: &OAuth2Config{ + ClientID: "some-id", + ClientSecretFile: "testdata/test_secretfile.txt", + TokenURL: "http://localhost:8511", + }, + }, + }, + + { + name: "OAuth2 want err", + args: args{ + oauth: &OAuth2Config{ + ClientID: "some-id", + ClientSecret: "some-secret", + ClientSecretFile: "testdata/test_secretfile.txt", + TokenURL: "http://localhost:8511", + }, + }, + wantErr: true, + }, + { + name: "basic Auth config", + args: args{ + basicAuth: &BasicAuthConfig{ + Username: "user", + Password: "password", + }, + }, + }, + { + name: "basic Auth config with file", + args: args{ + basicAuth: &BasicAuthConfig{ + Username: "user", + PasswordFile: "testdata/test_secretfile.txt", + }, + }, + }, + { + name: "want Authorization", + args: args{ + az: &Authorization{ + Type: "Bearer ", + Credentials: "Value", + }, + }, + }, + { + name: "token file", + args: args{ + bearerTokenFile: "testdata/test_secretfile.txt", + }, + }, + { + name: "token with tls", + args: args{ + bearerToken: "some-token", + tlsConfig: &TLSConfig{ + InsecureSkipVerify: true, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.oauth != nil { + r := http.NewServeMux() + r.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"access_token":"some-token","token_type": "Bearer "}`)) + + }) + mock := httptest.NewServer(r) + tt.args.oauth.TokenURL = mock.URL + } + got, err := NewConfig(tt.args.baseDir, tt.args.az, tt.args.basicAuth, tt.args.bearerToken, tt.args.bearerTokenFile, tt.args.oauth, tt.args.tlsConfig) + if (err != nil) != tt.wantErr { + t.Errorf("NewConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != nil { + if ah := got.GetAuthHeader(); ah == "" { + t.Fatalf("unexpected empty auth header") + } + } + + }) + } +} diff --git a/lib/promauth/testdata/test_secretfile.txt b/lib/promauth/testdata/test_secretfile.txt new file mode 100644 index 000000000..ce412a8da --- /dev/null +++ b/lib/promauth/testdata/test_secretfile.txt @@ -0,0 +1 @@ +secret-content \ No newline at end of file diff --git a/lib/promscrape/discovery/consul/api.go b/lib/promscrape/discovery/consul/api.go index 9ffac6945..3e95b4ae8 100644 --- a/lib/promscrape/discovery/consul/api.go +++ b/lib/promscrape/discovery/consul/api.go @@ -50,7 +50,7 @@ func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) { } token = "" } - ac, err := promauth.NewConfig(baseDir, nil, ba, token, "", sdc.TLSConfig) + ac, err := promauth.NewConfig(baseDir, nil, ba, token, "", nil, sdc.TLSConfig) if err != nil { return nil, fmt.Errorf("cannot parse auth config: %w", err) } diff --git a/lib/promscrape/discovery/kubernetes/api.go b/lib/promscrape/discovery/kubernetes/api.go index fa39666a8..ff7db4a45 100644 --- a/lib/promscrape/discovery/kubernetes/api.go +++ b/lib/promscrape/discovery/kubernetes/api.go @@ -43,7 +43,7 @@ func newAPIConfig(sdc *SDConfig, baseDir string, swcFunc ScrapeWorkConstructorFu tlsConfig := promauth.TLSConfig{ CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", } - acNew, err := promauth.NewConfig(".", nil, nil, "", "/var/run/secrets/kubernetes.io/serviceaccount/token", &tlsConfig) + acNew, err := promauth.NewConfig(".", nil, nil, "", "/var/run/secrets/kubernetes.io/serviceaccount/token", nil, &tlsConfig) if err != nil { return nil, fmt.Errorf("cannot initialize service account auth: %w; probably, `kubernetes_sd_config->api_server` is missing in Prometheus configs?", err) } diff --git a/lib/promscrape/discovery/openstack/api.go b/lib/promscrape/discovery/openstack/api.go index 8e29d1d0c..c8b8eb228 100644 --- a/lib/promscrape/discovery/openstack/api.go +++ b/lib/promscrape/discovery/openstack/api.go @@ -81,7 +81,7 @@ func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) { port: sdc.Port, } if sdc.TLSConfig != nil { - ac, err := promauth.NewConfig(baseDir, nil, nil, "", "", sdc.TLSConfig) + ac, err := promauth.NewConfig(baseDir, nil, nil, "", "", nil, sdc.TLSConfig) if err != nil { return nil, err } diff --git a/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go b/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go new file mode 100644 index 000000000..7a0b9ed10 --- /dev/null +++ b/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go @@ -0,0 +1,120 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package clientcredentials implements the OAuth2.0 "client credentials" token flow, +// also known as the "two-legged OAuth 2.0". +// +// This should be used when the client is acting on its own behalf or when the client +// is the resource owner. It may also be used when requesting access to protected +// resources based on an authorization previously arranged with the authorization +// server. +// +// See https://tools.ietf.org/html/rfc6749#section-4.4 +package clientcredentials // import "golang.org/x/oauth2/clientcredentials" + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/internal" +) + +// Config describes a 2-legged OAuth2 flow, with both the +// client application information and the server's endpoint URLs. +type Config struct { + // ClientID is the application's ID. + ClientID string + + // ClientSecret is the application's secret. + ClientSecret string + + // TokenURL is the resource server's token endpoint + // URL. This is a constant specific to each server. + TokenURL string + + // Scope specifies optional requested permissions. + Scopes []string + + // EndpointParams specifies additional parameters for requests to the token endpoint. + EndpointParams url.Values + + // AuthStyle optionally specifies how the endpoint wants the + // client ID & client secret sent. The zero value means to + // auto-detect. + AuthStyle oauth2.AuthStyle +} + +// Token uses client credentials to retrieve a token. +// +// The provided context optionally controls which HTTP client is used. See the oauth2.HTTPClient variable. +func (c *Config) Token(ctx context.Context) (*oauth2.Token, error) { + return c.TokenSource(ctx).Token() +} + +// Client returns an HTTP client using the provided token. +// The token will auto-refresh as necessary. +// +// The provided context optionally controls which HTTP client +// is returned. See the oauth2.HTTPClient variable. +// +// The returned Client and its Transport should not be modified. +func (c *Config) Client(ctx context.Context) *http.Client { + return oauth2.NewClient(ctx, c.TokenSource(ctx)) +} + +// TokenSource returns a TokenSource that returns t until t expires, +// automatically refreshing it as necessary using the provided context and the +// client ID and client secret. +// +// Most users will use Config.Client instead. +func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource { + source := &tokenSource{ + ctx: ctx, + conf: c, + } + return oauth2.ReuseTokenSource(nil, source) +} + +type tokenSource struct { + ctx context.Context + conf *Config +} + +// Token refreshes the token by using a new client credentials request. +// tokens received this way do not include a refresh token +func (c *tokenSource) Token() (*oauth2.Token, error) { + v := url.Values{ + "grant_type": {"client_credentials"}, + } + if len(c.conf.Scopes) > 0 { + v.Set("scope", strings.Join(c.conf.Scopes, " ")) + } + for k, p := range c.conf.EndpointParams { + // Allow grant_type to be overridden to allow interoperability with + // non-compliant implementations. + if _, ok := v[k]; ok && k != "grant_type" { + return nil, fmt.Errorf("oauth2: cannot overwrite parameter %q", k) + } + v[k] = p + } + + tk, err := internal.RetrieveToken(c.ctx, c.conf.ClientID, c.conf.ClientSecret, c.conf.TokenURL, v, internal.AuthStyle(c.conf.AuthStyle)) + if err != nil { + if rErr, ok := err.(*internal.RetrieveError); ok { + return nil, (*oauth2.RetrieveError)(rErr) + } + return nil, err + } + t := &oauth2.Token{ + AccessToken: tk.AccessToken, + TokenType: tk.TokenType, + RefreshToken: tk.RefreshToken, + Expiry: tk.Expiry, + } + return t.WithExtra(tk.Raw), nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 61dafd0a4..72586702c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -235,7 +235,7 @@ golang.org/x/lint/golint # golang.org/x/mod v0.4.2 golang.org/x/mod/module golang.org/x/mod/semver -# golang.org/x/net v0.0.0-20210510120150-4163338589ed +# golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 ## explicit golang.org/x/net/context golang.org/x/net/context/ctxhttp @@ -251,6 +251,7 @@ golang.org/x/net/trace ## explicit golang.org/x/oauth2 golang.org/x/oauth2/authhandler +golang.org/x/oauth2/clientcredentials golang.org/x/oauth2/google golang.org/x/oauth2/google/internal/externalaccount golang.org/x/oauth2/internal