mirror of
https://github.com/rancher/steve.git
synced 2025-07-05 19:16:38 +00:00
260 lines
10 KiB
Go
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
|
|
}
|