1
0
Fork 0
telegraf/plugins/secretstores/oauth2/oauth2.go

201 lines
5.2 KiB
Go
Raw Permalink Normal View History

//go:generate ../../../tools/readme_config_includer/generator
package oauth2
import (
"context"
_ "embed"
"errors"
"fmt"
"net/url"
"strings"
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
"golang.org/x/oauth2/endpoints"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/secretstores"
)
//go:embed sample.conf
var sampleConfig string
type TokenConfig struct {
Key string `toml:"key"`
ClientID config.Secret `toml:"client_id"`
ClientSecret config.Secret `toml:"client_secret"`
Scopes []string `toml:"scopes"`
Params map[string]string `toml:"parameters"`
}
type OAuth2 struct {
Service string `toml:"service"`
Endpoint string `toml:"token_endpoint"`
Tenant string `toml:"tenant_id"`
ExpiryMargin config.Duration `toml:"token_expiry_margin"`
TokenConfigs []TokenConfig `toml:"token"`
Log telegraf.Logger `toml:"-"`
sources map[string]oauth2.TokenSource
cancel context.CancelFunc
}
func (*OAuth2) SampleConfig() string {
return sampleConfig
}
// Init initializes all internals of the secret-store
func (o *OAuth2) Init() error {
ctx, cancel := context.WithCancel(context.Background())
o.cancel = cancel
// Check the service setting and determine the endpoint
var endpoint oauth2.Endpoint
var requireTenant, acceptCustomEndpoint bool
switch strings.ToLower(o.Service) {
case "", "custom":
if o.Endpoint == "" {
return errors.New("'token_endpoint' required for custom service")
}
endpoint.TokenURL = o.Endpoint
endpoint.AuthStyle = oauth2.AuthStyleAutoDetect
acceptCustomEndpoint = true
case "auth0":
if o.Endpoint == "" {
return errors.New("'token_endpoint' required for Auth0")
}
endpoint = oauth2.Endpoint{
TokenURL: o.Endpoint,
AuthStyle: oauth2.AuthStyleInParams,
}
acceptCustomEndpoint = true
case "azuread":
if o.Tenant == "" {
return errors.New("'tenant_id' required for AzureAD")
}
requireTenant = true
endpoint = endpoints.AzureAD(o.Tenant)
default:
return fmt.Errorf("service %q not supported", o.Service)
}
if !requireTenant && o.Tenant != "" {
o.Log.Warnf("'tenant_id' set but ignored by service %q", o.Service)
}
if !acceptCustomEndpoint && o.Endpoint != "" {
return fmt.Errorf("'token_endpoint' cannot be set for service %q", o.Service)
}
// Setup the token sources
o.sources = make(map[string]oauth2.TokenSource, len(o.TokenConfigs))
for _, c := range o.TokenConfigs {
if c.Key == "" {
return errors.New("'key' not specified")
}
if c.ClientID.Empty() {
return fmt.Errorf("'client_id' not specified for key %q", c.Key)
}
if c.ClientSecret.Empty() {
return fmt.Errorf("'client_secret' not specified for key %q", c.Key)
}
// Check service specific parameters
if strings.EqualFold(o.Service, "auth0") {
if audience := c.Params["audience"]; audience == "" {
return fmt.Errorf("'audience' parameter in key %q missing for service Auth0", c.Key)
}
}
if _, found := o.sources[c.Key]; found {
return fmt.Errorf("token with key %q already defined", c.Key)
}
// Get the secrets
cid, err := c.ClientID.Get()
if err != nil {
return fmt.Errorf("getting client ID for %q failed: %w", c.Key, err)
}
csecret, err := c.ClientSecret.Get()
if err != nil {
cid.Destroy()
return fmt.Errorf("getting client secret for %q failed: %w", c.Key, err)
}
// Setup the configuration
cfg := &clientcredentials.Config{
ClientID: cid.String(),
ClientSecret: csecret.String(),
TokenURL: endpoint.TokenURL,
Scopes: c.Scopes,
AuthStyle: endpoint.AuthStyle,
EndpointParams: url.Values{},
}
cid.Destroy()
csecret.Destroy()
// Add the parameters if any
for k, v := range c.Params {
cfg.EndpointParams.Add(k, v)
}
src := cfg.TokenSource(ctx)
o.sources[c.Key] = oauth2.ReuseTokenSourceWithExpiry(nil, src, time.Duration(o.ExpiryMargin))
}
return nil
}
// Get searches for the given key and return the secret
func (o *OAuth2) Get(key string) ([]byte, error) {
src, found := o.sources[key]
if !found {
return nil, fmt.Errorf("token %q not found", key)
}
// Return the token from the token-source. The token will be automatically
// renewed if the token expires.
token, err := src.Token()
if err != nil {
return nil, err
}
if !token.Valid() {
return nil, errors.New("token invalid")
}
return []byte(token.AccessToken), nil
}
// Set sets the given secret for the given key
func (*OAuth2) Set(_, _ string) error {
return errors.New("not supported")
}
// List lists all known secret keys
func (o *OAuth2) List() ([]string, error) {
keys := make([]string, 0, len(o.sources))
for k := range o.sources {
keys = append(keys, k)
}
return keys, nil
}
// GetResolver returns a function to resolve the given key.
func (o *OAuth2) GetResolver(key string) (telegraf.ResolveFunc, error) {
resolver := func() ([]byte, bool, error) {
s, err := o.Get(key)
return s, true, err
}
return resolver, nil
}
// Register the secret-store on load.
func init() {
secretstores.Add("oauth2", func(_ string) telegraf.SecretStore {
return &OAuth2{ExpiryMargin: config.Duration(time.Second)}
})
}