201 lines
5.2 KiB
Go
201 lines
5.2 KiB
Go
|
//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)}
|
||
|
})
|
||
|
}
|