2020-10-08 02:18:34 +00:00
|
|
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2020-10-08 18:28:21 +00:00
|
|
|
package manager
|
2020-10-08 02:18:34 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
2020-10-23 23:25:44 +00:00
|
|
|
"strings"
|
2020-10-08 02:18:34 +00:00
|
|
|
"sync"
|
|
|
|
|
2020-12-11 01:18:02 +00:00
|
|
|
"go.pinniped.dev/internal/secret"
|
|
|
|
|
2020-12-10 19:35:32 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/dynamiccodec"
|
|
|
|
|
2020-12-03 01:39:45 +00:00
|
|
|
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
2020-10-08 02:18:34 +00:00
|
|
|
|
|
|
|
"go.pinniped.dev/internal/oidc"
|
2020-11-05 01:06:47 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/auth"
|
2020-11-20 15:42:43 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/callback"
|
2020-11-11 20:29:14 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/csrftoken"
|
2020-10-08 02:18:34 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/discovery"
|
2020-10-17 00:51:40 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/jwks"
|
2020-10-08 18:28:21 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/provider"
|
2020-12-03 20:34:58 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/token"
|
2020-11-10 15:22:16 +00:00
|
|
|
"go.pinniped.dev/internal/plog"
|
2020-11-17 18:46:54 +00:00
|
|
|
"go.pinniped.dev/pkg/oidcclient/nonce"
|
|
|
|
"go.pinniped.dev/pkg/oidcclient/pkce"
|
2020-10-08 02:18:34 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Manager can manage multiple active OIDC providers. It acts as a request router for them.
|
|
|
|
//
|
|
|
|
// It is thread-safe.
|
|
|
|
type Manager struct {
|
2020-10-17 00:51:40 +00:00
|
|
|
mu sync.RWMutex
|
|
|
|
providers []*provider.OIDCProvider
|
|
|
|
providerHandlers map[string]http.Handler // map of all routes for all providers
|
|
|
|
nextHandler http.Handler // the next handler in a chain, called when this manager didn't know how to handle a request
|
|
|
|
dynamicJWKSProvider jwks.DynamicJWKSProvider // in-memory cache of per-issuer JWKS data
|
2020-11-13 23:59:51 +00:00
|
|
|
idpListGetter oidc.IDPListGetter // in-memory cache of upstream IDPs
|
2020-12-11 01:27:02 +00:00
|
|
|
cache secret.Cache // in-memory cache of cryptographic material
|
2020-12-03 01:39:45 +00:00
|
|
|
secretsClient corev1client.SecretInterface
|
2020-10-08 02:18:34 +00:00
|
|
|
}
|
|
|
|
|
2020-10-08 18:28:21 +00:00
|
|
|
// NewManager returns an empty Manager.
|
2020-10-08 02:18:34 +00:00
|
|
|
// nextHandler will be invoked for any requests that could not be handled by this manager's providers.
|
2020-10-17 00:51:40 +00:00
|
|
|
// dynamicJWKSProvider will be used as an in-memory cache for per-issuer JWKS data.
|
2020-11-05 01:06:47 +00:00
|
|
|
// idpListGetter will be used as an in-memory cache of currently configured upstream IDPs.
|
2020-12-03 01:39:45 +00:00
|
|
|
func NewManager(
|
|
|
|
nextHandler http.Handler,
|
|
|
|
dynamicJWKSProvider jwks.DynamicJWKSProvider,
|
|
|
|
idpListGetter oidc.IDPListGetter,
|
2020-12-11 01:27:02 +00:00
|
|
|
cache secret.Cache,
|
2020-12-03 01:39:45 +00:00
|
|
|
secretsClient corev1client.SecretInterface,
|
|
|
|
) *Manager {
|
2020-10-17 00:51:40 +00:00
|
|
|
return &Manager{
|
|
|
|
providerHandlers: make(map[string]http.Handler),
|
|
|
|
nextHandler: nextHandler,
|
|
|
|
dynamicJWKSProvider: dynamicJWKSProvider,
|
2020-11-05 01:06:47 +00:00
|
|
|
idpListGetter: idpListGetter,
|
2020-12-11 01:27:02 +00:00
|
|
|
cache: cache,
|
2020-12-03 01:39:45 +00:00
|
|
|
secretsClient: secretsClient,
|
2020-10-17 00:51:40 +00:00
|
|
|
}
|
2020-10-08 02:18:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetProviders adds or updates all the given providerHandlers using each provider's issuer string
|
|
|
|
// as the name of the provider to decide if it is an add or update operation.
|
|
|
|
//
|
|
|
|
// It also removes any providerHandlers that were previously added but were not passed in to
|
|
|
|
// the current invocation.
|
|
|
|
//
|
|
|
|
// This method assumes that all of the OIDCProvider arguments have already been validated
|
|
|
|
// by someone else before they are passed to this method.
|
2020-10-08 21:40:56 +00:00
|
|
|
func (m *Manager) SetProviders(oidcProviders ...*provider.OIDCProvider) {
|
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
|
|
|
|
m.providers = oidcProviders
|
|
|
|
m.providerHandlers = make(map[string]http.Handler)
|
|
|
|
|
2020-12-11 01:27:02 +00:00
|
|
|
var csrfCookieEncoder = dynamiccodec.New(m.cache.GetCSRFCookieEncoderHashKey, m.cache.GetCSRFCookieEncoderBlockKey)
|
2020-12-11 01:18:02 +00:00
|
|
|
|
2020-10-08 02:18:34 +00:00
|
|
|
for _, incomingProvider := range oidcProviders {
|
2020-12-11 01:27:02 +00:00
|
|
|
providerCache := m.cache.GetOIDCProviderCacheFor(incomingProvider.Issuer())
|
2020-12-11 01:18:02 +00:00
|
|
|
|
2020-12-11 01:27:02 +00:00
|
|
|
if providerCache == nil { // TODO remove when populated from `Secret` values
|
2020-12-11 01:18:02 +00:00
|
|
|
providerCache = &secret.OIDCProviderCache{}
|
|
|
|
providerCache.SetTokenHMACKey([]byte("some secret - must have at least 32 bytes")) // TODO fetch from `Secret`
|
|
|
|
providerCache.SetStateEncoderHashKey([]byte("fake-state-hash-secret")) // TODO fetch from `Secret`
|
|
|
|
providerCache.SetStateEncoderBlockKey([]byte("16-bytes-STATE01")) // TODO fetch from `Secret`
|
2020-12-11 01:27:02 +00:00
|
|
|
m.cache.SetOIDCProviderCacheFor(incomingProvider.Issuer(), providerCache)
|
2020-12-11 01:18:02 +00:00
|
|
|
}
|
|
|
|
|
2020-12-03 01:39:45 +00:00
|
|
|
issuer := incomingProvider.Issuer()
|
|
|
|
issuerHostWithPath := strings.ToLower(incomingProvider.IssuerHost()) + "/" + incomingProvider.IssuerPath()
|
2020-12-11 16:01:07 +00:00
|
|
|
oidcTimeouts := oidc.DefaultOIDCTimeoutsConfiguration()
|
2020-10-17 00:51:40 +00:00
|
|
|
|
2020-11-11 22:49:24 +00:00
|
|
|
// Use NullStorage for the authorize endpoint because we do not actually want to store anything until
|
|
|
|
// the upstream callback endpoint is called later.
|
2020-12-11 16:01:07 +00:00
|
|
|
oauthHelperWithNullStorage := oidc.FositeOauth2Helper(oidc.NullStorage{}, issuer, providerCache.GetTokenHMACKey, nil, oidcTimeouts)
|
2020-12-03 01:39:45 +00:00
|
|
|
|
|
|
|
// For all the other endpoints, make another oauth helper with exactly the same settings except use real storage.
|
2020-12-11 16:01:07 +00:00
|
|
|
oauthHelperWithKubeStorage := oidc.FositeOauth2Helper(oidc.NewKubeStorage(m.secretsClient), issuer, providerCache.GetTokenHMACKey, m.dynamicJWKSProvider, oidcTimeouts)
|
2020-12-11 01:18:02 +00:00
|
|
|
|
|
|
|
var upstreamStateEncoder = dynamiccodec.New(providerCache.GetStateEncoderHashKey, providerCache.GetStateEncoderBlockKey)
|
2020-11-11 01:58:00 +00:00
|
|
|
|
2020-12-03 01:39:45 +00:00
|
|
|
m.providerHandlers[(issuerHostWithPath + oidc.WellKnownEndpointPath)] = discovery.NewHandler(issuer)
|
|
|
|
|
|
|
|
m.providerHandlers[(issuerHostWithPath + oidc.JWKSEndpointPath)] = jwks.NewHandler(issuer, m.dynamicJWKSProvider)
|
|
|
|
|
|
|
|
m.providerHandlers[(issuerHostWithPath + oidc.AuthorizationEndpointPath)] = auth.NewHandler(
|
|
|
|
issuer,
|
2020-12-02 16:36:07 +00:00
|
|
|
m.idpListGetter,
|
2020-12-03 01:39:45 +00:00
|
|
|
oauthHelperWithNullStorage,
|
2020-12-02 16:36:07 +00:00
|
|
|
csrftoken.Generate,
|
|
|
|
pkce.Generate,
|
|
|
|
nonce.Generate,
|
2020-12-10 01:24:12 +00:00
|
|
|
upstreamStateEncoder,
|
|
|
|
csrfCookieEncoder,
|
2020-12-02 16:36:07 +00:00
|
|
|
)
|
2020-11-05 01:06:47 +00:00
|
|
|
|
2020-12-03 01:39:45 +00:00
|
|
|
m.providerHandlers[(issuerHostWithPath + oidc.CallbackEndpointPath)] = callback.NewHandler(
|
2020-12-02 16:36:07 +00:00
|
|
|
m.idpListGetter,
|
2020-12-03 01:39:45 +00:00
|
|
|
oauthHelperWithKubeStorage,
|
2020-12-10 01:24:12 +00:00
|
|
|
upstreamStateEncoder,
|
|
|
|
csrfCookieEncoder,
|
2020-12-03 01:39:45 +00:00
|
|
|
issuer+oidc.CallbackEndpointPath,
|
2020-12-02 16:36:07 +00:00
|
|
|
)
|
2020-11-05 01:06:47 +00:00
|
|
|
|
2020-12-03 20:34:58 +00:00
|
|
|
m.providerHandlers[(issuerHostWithPath + oidc.TokenEndpointPath)] = token.NewHandler(
|
|
|
|
oauthHelperWithKubeStorage,
|
|
|
|
)
|
|
|
|
|
2020-12-03 01:39:45 +00:00
|
|
|
plog.Debug("oidc provider manager added or updated issuer", "issuer", issuer)
|
2020-10-08 02:18:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServeHTTP implements the http.Handler interface.
|
2020-10-08 21:40:56 +00:00
|
|
|
func (m *Manager) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
|
|
|
requestHandler := m.findHandler(req)
|
|
|
|
|
2020-11-10 15:22:16 +00:00
|
|
|
plog.Debug(
|
2020-10-08 21:40:56 +00:00
|
|
|
"oidc provider manager examining request",
|
|
|
|
"method", req.Method,
|
|
|
|
"host", req.Host,
|
|
|
|
"path", req.URL.Path,
|
|
|
|
"foundMatchingIssuer", requestHandler != nil,
|
|
|
|
)
|
2020-10-08 02:18:34 +00:00
|
|
|
|
2020-10-08 21:40:56 +00:00
|
|
|
if requestHandler == nil {
|
|
|
|
requestHandler = m.nextHandler // couldn't find an issuer to handle the request
|
2020-10-08 02:18:34 +00:00
|
|
|
}
|
2020-10-08 21:40:56 +00:00
|
|
|
requestHandler.ServeHTTP(resp, req)
|
2020-10-08 02:18:34 +00:00
|
|
|
}
|
|
|
|
|
2020-10-08 21:40:56 +00:00
|
|
|
func (m *Manager) findHandler(req *http.Request) http.Handler {
|
|
|
|
m.mu.RLock()
|
|
|
|
defer m.mu.RUnlock()
|
|
|
|
|
2020-10-23 23:25:44 +00:00
|
|
|
return m.providerHandlers[strings.ToLower(req.Host)+"/"+req.URL.Path]
|
2020-10-08 02:18:34 +00:00
|
|
|
}
|