2020-09-16 14:19:51 +00:00
|
|
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
2020-09-14 14:36:06 +00:00
|
|
|
|
|
|
|
// Package idpcache implements a cache of active identity providers.
|
|
|
|
package idpcache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
2020-09-21 16:37:54 +00:00
|
|
|
"k8s.io/apiserver/pkg/authentication/user"
|
2020-09-14 14:36:06 +00:00
|
|
|
|
2020-09-21 16:37:54 +00:00
|
|
|
loginapi "go.pinniped.dev/generated/1.19/apis/login"
|
2020-09-14 14:36:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2020-09-21 16:37:54 +00:00
|
|
|
// ErrNoSuchIDP is returned by Cache.AuthenticateTokenCredentialRequest() when the requested IDP is not configured.
|
|
|
|
ErrNoSuchIDP = fmt.Errorf("no such identity provider")
|
|
|
|
|
|
|
|
// ErrNoIDPs is returned by Cache.AuthenticateTokenCredentialRequest() when there are no IDPs configured.
|
2020-09-14 14:36:06 +00:00
|
|
|
ErrNoIDPs = fmt.Errorf("no identity providers are loaded")
|
|
|
|
|
2020-09-21 16:37:54 +00:00
|
|
|
// ErrIndeterminateIDP is returned by Cache.AuthenticateTokenCredentialRequest() when the correct IDP cannot be determined.
|
2020-09-14 14:36:06 +00:00
|
|
|
ErrIndeterminateIDP = fmt.Errorf("could not uniquely match against an identity provider")
|
|
|
|
)
|
|
|
|
|
|
|
|
// Cache implements the authenticator.Token interface by multiplexing across a dynamic set of identity providers
|
|
|
|
// loaded from IDP resources.
|
|
|
|
type Cache struct {
|
|
|
|
cache sync.Map
|
|
|
|
}
|
|
|
|
|
2020-09-21 16:37:54 +00:00
|
|
|
type Key struct {
|
|
|
|
APIGroup string
|
|
|
|
Kind string
|
|
|
|
Namespace string
|
|
|
|
Name string
|
|
|
|
}
|
|
|
|
|
|
|
|
type Value interface {
|
|
|
|
authenticator.Token
|
|
|
|
}
|
|
|
|
|
2020-09-14 14:36:06 +00:00
|
|
|
// New returns an empty cache.
|
|
|
|
func New() *Cache {
|
|
|
|
return &Cache{}
|
|
|
|
}
|
|
|
|
|
2020-09-21 16:37:54 +00:00
|
|
|
// Get an identity provider by key.
|
|
|
|
func (c *Cache) Get(key Key) Value {
|
|
|
|
res, _ := c.cache.Load(key)
|
|
|
|
if res == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return res.(Value)
|
|
|
|
}
|
|
|
|
|
2020-09-14 14:36:06 +00:00
|
|
|
// Store an identity provider into the cache.
|
2020-09-21 16:37:54 +00:00
|
|
|
func (c *Cache) Store(key Key, value Value) {
|
2020-09-14 14:36:06 +00:00
|
|
|
c.cache.Store(key, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete an identity provider from the cache.
|
2020-09-21 16:37:54 +00:00
|
|
|
func (c *Cache) Delete(key Key) {
|
2020-09-14 14:36:06 +00:00
|
|
|
c.cache.Delete(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Keys currently stored in the cache.
|
2020-09-21 16:37:54 +00:00
|
|
|
func (c *Cache) Keys() []Key {
|
|
|
|
var result []Key
|
2020-09-14 14:36:06 +00:00
|
|
|
c.cache.Range(func(key, _ interface{}) bool {
|
2020-09-21 16:37:54 +00:00
|
|
|
result = append(result, key.(Key))
|
2020-09-14 14:36:06 +00:00
|
|
|
return true
|
|
|
|
})
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2020-09-21 16:37:54 +00:00
|
|
|
func (c *Cache) AuthenticateTokenCredentialRequest(ctx context.Context, req *loginapi.TokenCredentialRequest) (user.Info, error) {
|
|
|
|
// Map the incoming request to a cache key.
|
|
|
|
key := Key{
|
|
|
|
Namespace: req.Namespace,
|
|
|
|
Name: req.Spec.IdentityProvider.Name,
|
|
|
|
Kind: req.Spec.IdentityProvider.Kind,
|
|
|
|
}
|
|
|
|
if req.Spec.IdentityProvider.APIGroup != nil {
|
|
|
|
key.APIGroup = *req.Spec.IdentityProvider.APIGroup
|
|
|
|
}
|
2020-09-14 14:36:06 +00:00
|
|
|
|
2020-09-21 16:37:54 +00:00
|
|
|
// If the IDP is unspecified (legacy requests), choose the single loaded IDP or fail if there is not exactly
|
|
|
|
// one IDP configured.
|
|
|
|
if key.Name == "" || key.Kind == "" || key.APIGroup == "" {
|
|
|
|
keys := c.Keys()
|
|
|
|
if len(keys) == 0 {
|
|
|
|
return nil, ErrNoIDPs
|
|
|
|
}
|
|
|
|
if len(keys) > 1 {
|
|
|
|
return nil, ErrIndeterminateIDP
|
|
|
|
}
|
|
|
|
key = keys[0]
|
2020-09-14 14:36:06 +00:00
|
|
|
}
|
|
|
|
|
2020-09-21 16:37:54 +00:00
|
|
|
val := c.Get(key)
|
|
|
|
if val == nil {
|
|
|
|
return nil, ErrNoSuchIDP
|
2020-09-14 14:36:06 +00:00
|
|
|
}
|
|
|
|
|
2020-09-21 16:37:54 +00:00
|
|
|
// The incoming context could have an audience. Since we do not want to handle audiences right now, do not pass it
|
|
|
|
// through directly to the authentication webhook.
|
|
|
|
ctx = valuelessContext{ctx}
|
|
|
|
|
|
|
|
// Call the selected IDP.
|
|
|
|
resp, authenticated, err := val.AuthenticateToken(ctx, req.Spec.Token)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !authenticated {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the user.Info from the response (if it is non-nil).
|
|
|
|
var respUser user.Info
|
|
|
|
if resp != nil {
|
|
|
|
respUser = resp.User
|
|
|
|
}
|
|
|
|
return respUser, nil
|
2020-09-14 14:36:06 +00:00
|
|
|
}
|
2020-09-21 16:37:54 +00:00
|
|
|
|
|
|
|
type valuelessContext struct{ context.Context }
|
|
|
|
|
|
|
|
func (valuelessContext) Value(interface{}) interface{} { return nil }
|