ContainerImage.Pinniped/internal/registry/whoamirequest/rest.go

134 lines
4.0 KiB
Go
Raw Normal View History

// Copyright 2021-2022 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) Destroy() {}
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
}