134 lines
4.0 KiB
Go
134 lines
4.0 KiB
Go
// 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
|
|
}
|