1
0
mirror of https://github.com/rancher/steve.git synced 2025-07-05 19:16:38 +00:00
steve/pkg/ext/apiserver_authentication.go
Chad Roberts ccbadbd75f
Update to k8s 1.32 libraries (#491)
* Rebase

* Update dynamiclistener to v0.6.2-rc.3
2025-02-12 13:26:58 -05:00

260 lines
10 KiB
Go

package ext
import (
"context"
"crypto/x509"
"fmt"
"net/http"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/apis/apiserver"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
authenticatorunion "k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
"k8s.io/apiserver/pkg/server/options"
"k8s.io/client-go/kubernetes"
)
var _ dynamiccertificates.ControllerRunner = &UnionAuthenticator{}
var _ dynamiccertificates.CAContentProvider = &UnionAuthenticator{}
// UnionAuthenticator chains authenticators together to allow many ways of authenticating
// requests for the extension API server. For example, we might want to use Rancher's
// token authentication and fallback to the default authentication (mTLS) defined
// by Kubernetes.
//
// UnionAuthenticator is both a [dynamiccertificates.ControllerRunner] and a
// [dynamiccertificates.CAContentProvider].
type UnionAuthenticator struct {
unionAuthenticator authenticator.Request
unionCAContentProvider dynamiccertificates.CAContentProvider
}
// NewUnionAuthenticator creates a [UnionAuthenticator].
//
// The authenticators will be tried one by one, in the order they are given, until
// one succeed or all fails.
//
// Here's an example usage:
//
// customAuth := authenticator.RequestFunc(func(req *http.Request) (*Response, bool, error) {
// // use request to determine what the user is, otherwise return false
// })
// default, err := NewDefaultAuthenticator(client)
// if err != nil {
// return err
// }
// auth := NewUnionAuthenticator(customAuth, default)
// err = auth.RunOnce(ctx)
func NewUnionAuthenticator(authenticators ...authenticator.Request) *UnionAuthenticator {
caContentProviders := make([]dynamiccertificates.CAContentProvider, 0, len(authenticators))
for _, auth := range authenticators {
auth, ok := auth.(dynamiccertificates.CAContentProvider)
if !ok {
continue
}
caContentProviders = append(caContentProviders, auth)
}
return &UnionAuthenticator{
unionAuthenticator: authenticatorunion.New(authenticators...),
unionCAContentProvider: dynamiccertificates.NewUnionCAContentProvider(caContentProviders...),
}
}
// AuthenticateRequest implements [authenticator.Request]
func (u *UnionAuthenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
return u.unionAuthenticator.AuthenticateRequest(req)
}
// AuthenticateRequest implements [dynamiccertificates.Notifier]
// This is part of the [dynamiccertificates.CAContentProvider] interface.
func (u *UnionAuthenticator) AddListener(listener dynamiccertificates.Listener) {
u.unionCAContentProvider.AddListener(listener)
}
// AuthenticateRequest implements [dynamiccertificates.CAContentProvider]
func (u *UnionAuthenticator) Name() string {
return u.unionCAContentProvider.Name()
}
// AuthenticateRequest implements [dynamiccertificates.CAContentProvider]
func (u *UnionAuthenticator) CurrentCABundleContent() []byte {
return u.unionCAContentProvider.CurrentCABundleContent()
}
// AuthenticateRequest implements [dynamiccertificates.CAContentProvider]
func (u *UnionAuthenticator) VerifyOptions() (x509.VerifyOptions, bool) {
return u.unionCAContentProvider.VerifyOptions()
}
// AuthenticateRequest implements [dynamiccertificates.CAContentProvider]
func (u *UnionAuthenticator) RunOnce(ctx context.Context) error {
runner, ok := u.unionCAContentProvider.(dynamiccertificates.ControllerRunner)
if !ok {
return nil
}
return runner.RunOnce(ctx)
}
// AuthenticateRequest implements [dynamiccertificates.CAContentProvider]
func (u *UnionAuthenticator) Run(ctx context.Context, workers int) {
runner, ok := u.unionCAContentProvider.(dynamiccertificates.ControllerRunner)
if !ok {
return
}
runner.Run(ctx, workers)
}
const (
authenticationConfigMapNamespace = metav1.NamespaceSystem
authenticationConfigMapName = "extension-apiserver-authentication"
)
var _ dynamiccertificates.ControllerRunner = &DefaultAuthenticator{}
var _ dynamiccertificates.CAContentProvider = &DefaultAuthenticator{}
// DefaultAuthenticator is an [authenticator.Request] that authenticates a user by:
// - making sure the client uses a certificate signed by the CA defined in the
// `extension-apiserver-authentication` configmap in the `kube-system` namespace and
// - making sure the CN of the cert is part of the allow list, also defined in the same configmap
//
// This authentication is better explained in https://kubernetes.io/docs/tasks/extend-kubernetes/configure-aggregation-layer/
//
// This authenticator is a [dynamiccertificates.ControllerRunner] which means
// it will run in the background to dynamically watch the content of the configmap.
//
// When using the DefaultAuthenticator, it is suggested to call RunOnce() to initialize
// the CA state. It is also possible to watch for changes to the CA bundle with the AddListener()
// method. Here's an example usage:
//
// auth, err := NewDefaultAuthenticator(client)
// if err != nil {
// return err
// }
// auth.AddListener(myListener{auth: auth}) // myListener should react to CA bundle changes
// err = auth.RunOnce(ctx)
type DefaultAuthenticator struct {
requestHeaderConfig *authenticatorfactory.RequestHeaderConfig
authenticator authenticator.Request
}
// NewDefaultAuthenticator creates a DefaultAuthenticator
func NewDefaultAuthenticator(client kubernetes.Interface) (*DefaultAuthenticator, error) {
requestHeaderConfig, err := createRequestHeaderConfig(client)
if err != nil {
return nil, fmt.Errorf("requestheaderconfig: %w", err)
}
cfg := authenticatorfactory.DelegatingAuthenticatorConfig{
Anonymous: &apiserver.AnonymousAuthConfig{
Enabled: false,
},
RequestHeaderConfig: requestHeaderConfig,
}
authenticator, _, err := cfg.New()
if err != nil {
return nil, err
}
return &DefaultAuthenticator{
requestHeaderConfig: requestHeaderConfig,
authenticator: authenticator,
}, nil
}
// AuthenticateRequest implements [authenticator.Request]
func (b *DefaultAuthenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
return b.authenticator.AuthenticateRequest(req)
}
// AuthenticateRequest implements [dynamiccertificates.Notifier]
// This is part of the [dynamiccertificates.CAContentProvider] interface.
func (b *DefaultAuthenticator) AddListener(listener dynamiccertificates.Listener) {
b.requestHeaderConfig.CAContentProvider.AddListener(listener)
}
// AuthenticateRequest implements [dynamiccertificates.CAContentProvider]
func (b *DefaultAuthenticator) Name() string {
return b.requestHeaderConfig.CAContentProvider.Name()
}
// AuthenticateRequest implements [dynamiccertificates.CAContentProvider]
func (b *DefaultAuthenticator) CurrentCABundleContent() []byte {
return b.requestHeaderConfig.CAContentProvider.CurrentCABundleContent()
}
// AuthenticateRequest implements [dynamiccertificates.CAContentProvider]
func (b *DefaultAuthenticator) VerifyOptions() (x509.VerifyOptions, bool) {
return b.requestHeaderConfig.CAContentProvider.VerifyOptions()
}
// AuthenticateRequest implements [dynamiccertificates.ControllerRunner]
func (b *DefaultAuthenticator) RunOnce(ctx context.Context) error {
runner, ok := b.requestHeaderConfig.CAContentProvider.(dynamiccertificates.ControllerRunner)
if !ok {
return nil
}
return runner.RunOnce(ctx)
}
// AuthenticateRequest implements [dynamiccertificates.ControllerRunner].
//
// It will be called by the "SecureServing" when starting the extension API server
func (b *DefaultAuthenticator) Run(ctx context.Context, workers int) {
runner, ok := b.requestHeaderConfig.CAContentProvider.(dynamiccertificates.ControllerRunner)
if !ok {
return
}
runner.Run(ctx, workers)
}
// Copied from https://github.com/kubernetes/apiserver/blob/v0.30.1/pkg/server/options/authentication.go#L407
func createRequestHeaderConfig(client kubernetes.Interface) (*authenticatorfactory.RequestHeaderConfig, error) {
dynamicRequestHeaderProvider, err := newDynamicRequestHeaderController(client)
if err != nil {
return nil, fmt.Errorf("unable to create request header authentication config: %v", err)
}
return &authenticatorfactory.RequestHeaderConfig{
CAContentProvider: dynamicRequestHeaderProvider,
UsernameHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.UsernameHeaders)),
UIDHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.UIDHeaders)),
GroupHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.GroupHeaders)),
ExtraHeaderPrefixes: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.ExtraHeaderPrefixes)),
AllowedClientNames: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.AllowedClientNames)),
}, nil
}
// Copied from https://github.com/kubernetes/apiserver/blob/v0.30.1/pkg/server/options/authentication_dynamic_request_header.go#L42
func newDynamicRequestHeaderController(client kubernetes.Interface) (*options.DynamicRequestHeaderController, error) {
requestHeaderCAController, err := dynamiccertificates.NewDynamicCAFromConfigMapController(
"client-ca",
authenticationConfigMapNamespace,
authenticationConfigMapName,
"requestheader-client-ca-file",
client)
if err != nil {
return nil, fmt.Errorf("unable to create DynamicCAFromConfigMap controller: %v", err)
}
requestHeaderAuthRequestController := headerrequest.NewRequestHeaderAuthRequestController(
authenticationConfigMapName,
authenticationConfigMapNamespace,
client,
"requestheader-username-headers",
"requestheader-uid-headers",
"requestheader-group-headers",
"requestheader-extra-headers-prefix",
"requestheader-allowed-names",
)
return &options.DynamicRequestHeaderController{
ConfigMapCAController: requestHeaderCAController,
RequestHeaderAuthRequestController: requestHeaderAuthRequestController,
}, nil
}