abc941097c
This change adds a new virtual aggregated API that can be used by any user to echo back who they are currently authenticated as. This has general utility to end users and can be used in tests to validate if authentication was successful. Signed-off-by: Monis Khan <mok@vmware.com>
132 lines
4.0 KiB
Go
132 lines
4.0 KiB
Go
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package whoamirequest
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
|
"k8s.io/apiserver/pkg/registry/rest"
|
|
|
|
identityapi "go.pinniped.dev/generated/latest/apis/concierge/identity"
|
|
identityapivalidation "go.pinniped.dev/generated/latest/apis/concierge/identity/validation"
|
|
)
|
|
|
|
func NewREST(resource schema.GroupResource) *REST {
|
|
return &REST{
|
|
tableConvertor: rest.NewDefaultTableConvertor(resource),
|
|
}
|
|
}
|
|
|
|
type REST struct {
|
|
tableConvertor rest.TableConvertor
|
|
}
|
|
|
|
// Assert that our *REST implements all the optional interfaces that we expect it to implement.
|
|
var _ interface {
|
|
rest.Creater
|
|
rest.NamespaceScopedStrategy
|
|
rest.Scoper
|
|
rest.Storage
|
|
rest.CategoriesProvider
|
|
rest.Lister
|
|
} = (*REST)(nil)
|
|
|
|
func (*REST) New() runtime.Object {
|
|
return &identityapi.WhoAmIRequest{}
|
|
}
|
|
|
|
func (*REST) NewList() runtime.Object {
|
|
return &identityapi.WhoAmIRequestList{}
|
|
}
|
|
|
|
func (*REST) List(_ context.Context, _ *metainternalversion.ListOptions) (runtime.Object, error) {
|
|
return &identityapi.WhoAmIRequestList{
|
|
ListMeta: metav1.ListMeta{
|
|
ResourceVersion: "0", // this resource version means "from the API server cache"
|
|
},
|
|
Items: []identityapi.WhoAmIRequest{}, // avoid sending nil items list
|
|
}, nil
|
|
}
|
|
|
|
func (r *REST) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
|
return r.tableConvertor.ConvertToTable(ctx, obj, tableOptions)
|
|
}
|
|
|
|
func (*REST) NamespaceScoped() bool {
|
|
return false
|
|
}
|
|
|
|
func (*REST) Categories() []string {
|
|
return []string{"pinniped"}
|
|
}
|
|
|
|
func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
|
whoAmIRequest, ok := obj.(*identityapi.WhoAmIRequest)
|
|
if !ok {
|
|
return nil, apierrors.NewBadRequest(fmt.Sprintf("not a WhoAmIRequest: %#v", obj))
|
|
}
|
|
|
|
if errs := identityapivalidation.ValidateWhoAmIRequest(whoAmIRequest); len(errs) > 0 {
|
|
return nil, apierrors.NewInvalid(identityapi.Kind(whoAmIRequest.Kind), whoAmIRequest.Name, errs)
|
|
}
|
|
|
|
// just a sanity check, not sure how to honor a dry run on a virtual API
|
|
if options != nil {
|
|
if len(options.DryRun) != 0 {
|
|
errs := field.ErrorList{field.NotSupported(field.NewPath("dryRun"), options.DryRun, nil)}
|
|
return nil, apierrors.NewInvalid(identityapi.Kind(whoAmIRequest.Kind), whoAmIRequest.Name, errs)
|
|
}
|
|
}
|
|
|
|
if namespace := genericapirequest.NamespaceValue(ctx); len(namespace) != 0 {
|
|
return nil, apierrors.NewBadRequest(fmt.Sprintf("namespace is not allowed on WhoAmIRequest: %v", namespace))
|
|
}
|
|
|
|
if createValidation != nil {
|
|
if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
userInfo, ok := genericapirequest.UserFrom(ctx)
|
|
if !ok {
|
|
return nil, apierrors.NewInternalError(fmt.Errorf("no user info on request"))
|
|
}
|
|
|
|
auds, _ := authenticator.AudiencesFrom(ctx)
|
|
|
|
out := &identityapi.WhoAmIRequest{
|
|
Status: identityapi.WhoAmIRequestStatus{
|
|
KubernetesUserInfo: identityapi.KubernetesUserInfo{
|
|
User: identityapi.UserInfo{
|
|
Username: userInfo.GetName(),
|
|
UID: userInfo.GetUID(),
|
|
Groups: userInfo.GetGroups(),
|
|
},
|
|
Audiences: auds,
|
|
},
|
|
},
|
|
}
|
|
for k, v := range userInfo.GetExtra() {
|
|
if out.Status.KubernetesUserInfo.User.Extra == nil {
|
|
out.Status.KubernetesUserInfo.User.Extra = map[string]identityapi.ExtraValue{}
|
|
}
|
|
|
|
// this assumes no one is putting secret data in the extra field
|
|
// I think this is a safe assumption since it would leak into audit logs
|
|
out.Status.KubernetesUserInfo.User.Extra[k] = v
|
|
}
|
|
|
|
return out, nil
|
|
}
|