Repository: hashicorp/nomad
File: lib/auth/oidc/provider.go (Go)

Code before:
	"strings"
	"sync"
	"time"

	"github.com/hashicorp/cap/oidc"
	"github.com/hashicorp/nomad/nomad/structs"
)

// providerConfig returns the OIDC provider configuration for an OIDC
// auth-method.
func providerConfig(authMethod *structs.ACLAuthMethod) (*oidc.Config, error) {
	var algs []oidc.Alg
	if len(authMethod.Config.SigningAlgs) > 0 {
		for _, alg := range authMethod.Config.SigningAlgs {
			algs = append(algs, oidc.Alg(alg))
		}
	} else {
		algs = []oidc.Alg{oidc.RS256}
	}

	// if client assertion is enabled, do not send a client secret normally;
	// if it is set to anything, it will be used as an HMAC to sign the client
	// assertion JWT, instead.
	if authMethod.Config.OIDCClientAssertion != nil {
		authMethod.Config.OIDCClientSecret = ""
	}

	return oidc.NewConfig(
		authMethod.Config.OIDCDiscoveryURL,
		authMethod.Config.OIDCClientID,
		oidc.ClientSecret(authMethod.Config.OIDCClientSecret),
		algs,
		authMethod.Config.AllowedRedirectURIs,
		oidc.WithAudiences(authMethod.Config.BoundAudiences...),
		oidc.WithProviderCA(strings.Join(authMethod.Config.DiscoveryCaPem, "\n")),
	)
}

// ProviderCache is a cache for OIDC providers. OIDC providers are something
// you don't want to recreate per-request since they make HTTP requests
// when they're constructed.
//
// The ProviderCache purges a provider under two scenarios: (1) the
// provider config is updated, and it is different and (2) after a set
// amount of time (see cacheExpiry for value) in case the remote provider
// configuration changed.
type ProviderCache struct {
	providers map[string]*oidc.Provider
	mu        sync.RWMutex

	// cancel is used to trigger cancellation of any routines when the cache

Review comment (suggestion):
I chased this method up to `Get` and up to the RPC handler and it looks like this mutates an object we got out of the state store without a copy. Mutating an object in a call named `Get` that isn't even returning that object seems like spooky action at a distance as well. (We do this in a couple places in the state store but only to return a copy of the object "denormalized", and it's heavily documented).

Code after:
	"strings"
	"sync"
	"time"

	"github.com/hashicorp/cap/oidc"
	"github.com/hashicorp/nomad/nomad/structs"
)

// providerConfig returns the OIDC provider configuration for an OIDC
// auth-method.
func providerConfig(authMethod *structs.ACLAuthMethod) (*oidc.Config, error) {
	var algs []oidc.Alg
	if len(authMethod.Config.SigningAlgs) > 0 {
		for _, alg := range authMethod.Config.SigningAlgs {
			algs = append(algs, oidc.Alg(alg))
		}
	} else {
		algs = []oidc.Alg{oidc.RS256}
	}

	// if client assertion is enabled, do not send a client secret normally;
	// if it is set to anything, it will be used as an HMAC to sign the client
	// assertion JWT, instead.
	clientSecret := authMethod.Config.OIDCClientSecret
	if authMethod.Config.OIDCClientAssertion != nil {
		clientSecret = ""
	}

	return oidc.NewConfig(
		authMethod.Config.OIDCDiscoveryURL,
		authMethod.Config.OIDCClientID,
		oidc.ClientSecret(clientSecret),
		algs,
		authMethod.Config.AllowedRedirectURIs,
		oidc.WithAudiences(authMethod.Config.BoundAudiences...),
		oidc.WithProviderCA(strings.Join(authMethod.Config.DiscoveryCaPem, "\n")),
	)
}

// ProviderCache is a cache for OIDC providers. OIDC providers are something
// you don't want to recreate per-request since they make HTTP requests
// when they're constructed.
//
// The ProviderCache purges a provider under two scenarios: (1) the
// provider config is updated, and it is different and (2) after a set
// amount of time (see cacheExpiry for value) in case the remote provider
// configuration changed.
type ProviderCache struct {
	providers map[string]*oidc.Provider
	mu        sync.RWMutex