consul/internal/go-sso/oidcauth/auth.go

133 lines
3.8 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
// package oidcauth bundles up an opinionated approach to authentication using
// both the OIDC authorization code workflow and simple JWT decoding (via
// static keys, JWKS, and OIDC discovery).
//
// NOTE: This was roughly forked from hashicorp/vault-plugin-auth-jwt
// originally at commit 825c85535e3832d254a74253a8e9ae105357778b with later
// backports of behavior in 0e93b06cecb0477d6ee004e44b04832d110096cf
package oidcauth
import (
"context"
"fmt"
"net/http"
"sync"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/hashicorp/go-hclog"
"github.com/patrickmn/go-cache"
)
// Claims represents a set of claims or assertions computed about a given
// authentication exchange.
type Claims struct {
// Values is a set of key/value string claims about the authentication
// exchange.
Values map[string]string
// Lists is a set of key/value string list claims about the authentication
// exchange.
Lists map[string][]string
}
// Authenticator allows for extracting a set of claims from either an OIDC
// authorization code exchange or a bare JWT.
type Authenticator struct {
config *Config
logger hclog.Logger
// parsedJWTPubKeys is the parsed form of config.JWTValidationPubKeys
parsedJWTPubKeys []interface{}
provider *oidc.Provider
keySet oidc.KeySet
// httpClient should be configured with all relevant root CA certs and be
// reused for all OIDC or JWKS operations. This will be nil for the static
// keys JWT configuration.
httpClient *http.Client
l sync.Mutex
oidcStates *cache.Cache
// backgroundCtx is a cancellable context primarily meant to be used for
// things that may spawn background goroutines and are not tied to a
// request/response lifecycle. Use backgroundCtxCancel to cancel this.
backgroundCtx context.Context
backgroundCtxCancel context.CancelFunc
}
// New creates an authenticator suitable for use with either an OIDC
// authorization code workflow or a bare JWT workflow depending upon the value
// of the config Type.
func New(c *Config, logger hclog.Logger) (*Authenticator, error) {
if err := c.Validate(); err != nil {
return nil, err
}
var parsedJWTPubKeys []interface{}
if c.Type == TypeJWT {
for _, v := range c.JWTValidationPubKeys {
key, err := parsePublicKeyPEM([]byte(v))
if err != nil {
// This shouldn't happen as the keys are already validated in Validate().
return nil, fmt.Errorf("error parsing public key: %v", err)
}
parsedJWTPubKeys = append(parsedJWTPubKeys, key)
}
}
a := &Authenticator{
config: c,
logger: logger,
parsedJWTPubKeys: parsedJWTPubKeys,
}
a.backgroundCtx, a.backgroundCtxCancel = context.WithCancel(context.Background())
if c.Type == TypeOIDC {
a.oidcStates = cache.New(oidcStateTimeout, oidcStateCleanupInterval)
}
var err error
switch c.authType() {
case authOIDCDiscovery, authOIDCFlow:
a.httpClient, err = createHTTPClient(a.config.OIDCDiscoveryCACert)
if err != nil {
return nil, fmt.Errorf("error parsing OIDCDiscoveryCACert: %v", err)
}
provider, err := oidc.NewProvider(
contextWithHttpClient(a.backgroundCtx, a.httpClient),
a.config.OIDCDiscoveryURL,
)
if err != nil {
return nil, fmt.Errorf("error creating provider: %v", err)
}
a.provider = provider
case authJWKS:
a.httpClient, err = createHTTPClient(a.config.JWKSCACert)
if err != nil {
return nil, fmt.Errorf("error parsing JWKSCACert: %v", err)
}
a.keySet = oidc.NewRemoteKeySet(
contextWithHttpClient(a.backgroundCtx, a.httpClient),
a.config.JWKSURL,
)
}
return a, nil
}
// Stop stops any background goroutines and does cleanup.
func (a *Authenticator) Stop() {
a.l.Lock()
defer a.l.Unlock()
if a.backgroundCtxCancel != nil {
a.backgroundCtxCancel()
a.backgroundCtxCancel = nil
}
}