ContainerImage.Pinniped/internal/controller/authenticator/authncache/cache.go

138 lines
3.5 KiB
Go
Raw Normal View History

2021-01-07 22:58:09 +00:00
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package authncache implements a cache of active authenticators.
package authncache
import (
"context"
"fmt"
"sort"
"sync"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog/v2"
2021-01-07 22:58:09 +00:00
loginapi "go.pinniped.dev/generated/1.20/apis/concierge/login"
"go.pinniped.dev/internal/groupsuffix"
"go.pinniped.dev/internal/plog"
)
var (
// ErrNoSuchAuthenticator is returned by Cache.AuthenticateTokenCredentialRequest() when the requested authenticator is not configured.
ErrNoSuchAuthenticator = fmt.Errorf("no such authenticator")
)
// Cache implements the authenticator.Token interface by multiplexing across a dynamic set of authenticators
// loaded from authenticator resources.
type Cache struct {
cache sync.Map
apiGroupSuffix string
}
type Key struct {
APIGroup string
Kind string
Namespace string
Name string
}
type Value interface {
authenticator.Token
}
// New returns an empty cache.
func New(apiGroupSuffix string) *Cache {
return &Cache{apiGroupSuffix: apiGroupSuffix}
}
// Get an authenticator by key.
func (c *Cache) Get(key Key) Value {
res, _ := c.cache.Load(key)
if res == nil {
return nil
}
return res.(Value)
}
// Store an authenticator into the cache.
func (c *Cache) Store(key Key, value Value) {
c.cache.Store(key, value)
}
// Delete an authenticator from the cache.
func (c *Cache) Delete(key Key) {
c.cache.Delete(key)
}
// Keys currently stored in the cache.
func (c *Cache) Keys() []Key {
var result []Key
c.cache.Range(func(key, _ interface{}) bool {
result = append(result, key.(Key))
return true
})
// Sort the results for consistency.
sort.Slice(result, func(i, j int) bool {
return result[i].APIGroup < result[j].APIGroup ||
result[i].Kind < result[j].Kind ||
result[i].Namespace < result[j].Namespace ||
result[i].Name < result[j].Name
})
return result
}
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.Authenticator.Name,
Kind: req.Spec.Authenticator.Kind,
}
if req.Spec.Authenticator.APIGroup != nil {
// The key must always be API group pinniped.dev because that's what the cache filler will always use.
apiGroup, replaced := groupsuffix.Unreplace(*req.Spec.Authenticator.APIGroup, c.apiGroupSuffix)
if !replaced {
return nil, ErrNoSuchAuthenticator
}
key.APIGroup = apiGroup
}
val := c.Get(key)
if val == nil {
plog.Debug(
"authenticator does not exist",
"authenticator", klog.KRef(key.Namespace, key.Name),
"kind", key.Kind,
"apiGroup", key.APIGroup,
)
return nil, ErrNoSuchAuthenticator
}
// 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 authenticator.
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
}
type valuelessContext struct{ context.Context }
func (valuelessContext) Value(interface{}) interface{} { return nil }