mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-09-19 14:34:27 +00:00
WIP aggregated api for oidcclientsecretrequest
Signed-off-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
@@ -24,6 +24,7 @@ type Config struct {
|
||||
// NamesConfigSpec configures the names of some Kubernetes resources for the Supervisor.
|
||||
type NamesConfigSpec struct {
|
||||
DefaultTLSCertificateSecret string `json:"defaultTLSCertificateSecret"`
|
||||
APIService string `json:"apiService"`
|
||||
}
|
||||
|
||||
type Endpoints struct {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package groupsuffix
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1"
|
||||
loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1"
|
||||
oauthv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/virtual/oauth/v1alpha1"
|
||||
)
|
||||
|
||||
type GroupData schema.GroupVersion
|
||||
@@ -32,3 +33,16 @@ func ConciergeAggregatedGroups(apiGroupSuffix string) (login, identity GroupData
|
||||
Version: identityv1alpha1.SchemeGroupVersion.Version,
|
||||
}
|
||||
}
|
||||
|
||||
func SupervisorAggregatedGroups(apiGroupSuffix string) (oauth GroupData) {
|
||||
oauthVirtualSupervisorAPIGroup, ok1 := Replace(oauthv1alpha1.GroupName, apiGroupSuffix)
|
||||
|
||||
if !ok1 {
|
||||
panic("static group input is invalid")
|
||||
}
|
||||
|
||||
return GroupData{
|
||||
Group: oauthVirtualSupervisorAPIGroup,
|
||||
Version: oauthv1alpha1.SchemeGroupVersion.Version,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package clientsecretrequest provides REST functionality for the CredentialRequest resource.
|
||||
package clientsecretrequest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/utils/trace"
|
||||
|
||||
oauthapi "go.pinniped.dev/generated/latest/apis/supervisor/virtual/oauth"
|
||||
)
|
||||
|
||||
func NewREST() *REST {
|
||||
return &REST{}
|
||||
}
|
||||
|
||||
type REST struct {
|
||||
}
|
||||
|
||||
// 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)(nil)
|
||||
|
||||
func (*REST) New() runtime.Object {
|
||||
return &oauthapi.OIDCClientSecretRequest{}
|
||||
}
|
||||
|
||||
func (*REST) NamespaceScoped() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (*REST) Categories() []string {
|
||||
// because we haven't implemented lister, adding it to categories breaks things.
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||
t := trace.FromContext(ctx).Nest("create", trace.Field{
|
||||
Key: "kind",
|
||||
Value: "OIDCClientSecretRequest",
|
||||
})
|
||||
defer t.Log()
|
||||
|
||||
_, err := validateRequest(obj, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oauthapi.OIDCClientSecretRequest{
|
||||
Status: oauthapi.OIDCClientSecretRequestStatus{
|
||||
GeneratedSecret: "not-a-real-secret",
|
||||
TotalClientSecrets: 20,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func validateRequest(obj runtime.Object, t *trace.Trace) (*oauthapi.OIDCClientSecretRequest, error) {
|
||||
clientSecretRequest, ok := obj.(*oauthapi.OIDCClientSecretRequest)
|
||||
if !ok {
|
||||
traceValidationFailure(t, "not an OIDCClientSecretRequest")
|
||||
return nil, apierrors.NewBadRequest(fmt.Sprintf("not an OIDCClientSecretRequest: %#v", obj))
|
||||
}
|
||||
|
||||
return clientSecretRequest, nil
|
||||
}
|
||||
|
||||
func traceValidationFailure(t *trace.Trace, msg string) {
|
||||
t.Step("failure",
|
||||
trace.Field{Key: "failureType", Value: "request validation"},
|
||||
trace.Field{Key: "msg", Value: msg},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package apiserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/client-go/pkg/version"
|
||||
|
||||
"go.pinniped.dev/internal/controllerinit"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/internal/registry/clientsecretrequest"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
GenericConfig *genericapiserver.RecommendedConfig
|
||||
ExtraConfig ExtraConfig
|
||||
}
|
||||
|
||||
type ExtraConfig struct {
|
||||
BuildControllersPostStartHook controllerinit.RunnerBuilder
|
||||
Scheme *runtime.Scheme
|
||||
NegotiatedSerializer runtime.NegotiatedSerializer
|
||||
OauthVirtualSupervisorGroupVersion schema.GroupVersion
|
||||
}
|
||||
|
||||
type PinnipedServer struct {
|
||||
GenericAPIServer *genericapiserver.GenericAPIServer
|
||||
}
|
||||
|
||||
type completedConfig struct {
|
||||
GenericConfig genericapiserver.CompletedConfig
|
||||
ExtraConfig *ExtraConfig
|
||||
}
|
||||
|
||||
type CompletedConfig struct {
|
||||
// Embed a private pointer that cannot be instantiated outside of this package.
|
||||
*completedConfig
|
||||
}
|
||||
|
||||
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
|
||||
func (c *Config) Complete() CompletedConfig {
|
||||
completedCfg := completedConfig{
|
||||
c.GenericConfig.Complete(),
|
||||
&c.ExtraConfig,
|
||||
}
|
||||
|
||||
versionInfo := version.Get()
|
||||
completedCfg.GenericConfig.Version = &versionInfo
|
||||
|
||||
return CompletedConfig{completedConfig: &completedCfg}
|
||||
}
|
||||
|
||||
// New returns a new instance of AdmissionServer from the given config.
|
||||
func (c completedConfig) New() (*PinnipedServer, error) {
|
||||
genericServer, err := c.GenericConfig.New("pinniped-supervisor", genericapiserver.NewEmptyDelegate()) // completion is done in Complete, no need for a second time
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("completion error: %w", err)
|
||||
}
|
||||
|
||||
s := &PinnipedServer{
|
||||
GenericAPIServer: genericServer,
|
||||
}
|
||||
|
||||
var errs []error //nolint: prealloc
|
||||
for _, f := range []func() (schema.GroupVersionResource, rest.Storage){
|
||||
func() (schema.GroupVersionResource, rest.Storage) {
|
||||
clientSecretReqGVR := c.ExtraConfig.OauthVirtualSupervisorGroupVersion.WithResource("oidcclientsecretrequests")
|
||||
clientSecretReqStorage := clientsecretrequest.NewREST()
|
||||
return clientSecretReqGVR, clientSecretReqStorage
|
||||
},
|
||||
} {
|
||||
gvr, storage := f()
|
||||
errs = append(errs,
|
||||
s.GenericAPIServer.InstallAPIGroup(
|
||||
&genericapiserver.APIGroupInfo{
|
||||
PrioritizedVersions: []schema.GroupVersion{gvr.GroupVersion()},
|
||||
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{gvr.Version: {gvr.Resource: storage}},
|
||||
OptionsExternalVersion: &schema.GroupVersion{Version: "v1"},
|
||||
Scheme: c.ExtraConfig.Scheme,
|
||||
ParameterCodec: metav1.ParameterCodec,
|
||||
NegotiatedSerializer: c.ExtraConfig.NegotiatedSerializer,
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
if err := errors.NewAggregate(errs); err != nil {
|
||||
return nil, fmt.Errorf("could not install API groups: %w", err)
|
||||
}
|
||||
|
||||
shutdown := &sync.WaitGroup{}
|
||||
s.GenericAPIServer.AddPostStartHookOrDie("start-controllers",
|
||||
func(postStartContext genericapiserver.PostStartHookContext) error {
|
||||
plog.Debug("start-controllers post start hook starting")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
defer cancel()
|
||||
|
||||
<-postStartContext.StopCh
|
||||
}()
|
||||
|
||||
runControllers, err := c.ExtraConfig.BuildControllersPostStartHook(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create run controller func: %w", err)
|
||||
}
|
||||
|
||||
shutdown.Add(1)
|
||||
go func() {
|
||||
defer shutdown.Done()
|
||||
|
||||
runControllers(ctx)
|
||||
}()
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
s.GenericAPIServer.AddPreShutdownHookOrDie("stop-controllers",
|
||||
func() error {
|
||||
plog.Debug("stop-controllers pre shutdown hook starting")
|
||||
defer plog.Debug("stop-controllers pre shutdown hook completed")
|
||||
|
||||
shutdown.Wait()
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package scheme contains code to construct a proper runtime.Scheme for the Concierge aggregated
|
||||
// API.
|
||||
package scheme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
oauthapi "go.pinniped.dev/generated/latest/apis/supervisor/virtual/oauth"
|
||||
oauthv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/virtual/oauth/v1alpha1"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
|
||||
"go.pinniped.dev/internal/groupsuffix"
|
||||
)
|
||||
|
||||
// New returns a runtime.Scheme for use by the Supervisor aggregated API running with the provided
|
||||
// apiGroupSuffix.
|
||||
func New(apiGroupSuffix string) (_ *runtime.Scheme, oauth schema.GroupVersion) {
|
||||
// standard set up of the server side scheme
|
||||
scheme := runtime.NewScheme()
|
||||
|
||||
// add the options to empty v1
|
||||
metav1.AddToGroupVersion(scheme, metav1.Unversioned)
|
||||
|
||||
// nothing fancy is required if using the standard group suffix
|
||||
if apiGroupSuffix == groupsuffix.PinnipedDefaultSuffix {
|
||||
schemeBuilder := runtime.NewSchemeBuilder(
|
||||
oauthv1alpha1.AddToScheme,
|
||||
oauthapi.AddToScheme,
|
||||
)
|
||||
utilruntime.Must(schemeBuilder.AddToScheme(scheme))
|
||||
return scheme, oauthv1alpha1.SchemeGroupVersion
|
||||
}
|
||||
|
||||
oauthVirtualSupervisorGroupData := groupsuffix.SupervisorAggregatedGroups(apiGroupSuffix)
|
||||
|
||||
addToSchemeAtNewGroup(scheme, oauthv1alpha1.GroupName, oauthVirtualSupervisorGroupData.Group, oauthv1alpha1.AddToScheme, oauthapi.AddToScheme)
|
||||
|
||||
// manually register conversions and defaulting into the correct scheme since we cannot directly call AddToScheme
|
||||
schemeBuilder := runtime.NewSchemeBuilder(
|
||||
oauthv1alpha1.RegisterConversions,
|
||||
oauthv1alpha1.RegisterDefaults,
|
||||
)
|
||||
utilruntime.Must(schemeBuilder.AddToScheme(scheme))
|
||||
|
||||
// we do not have any defaulting functions for *loginv1alpha1.OIDCClientSecretRequest
|
||||
// today, but we may have some in the future. Calling AddTypeDefaultingFunc overwrites
|
||||
// any previously registered defaulting function. Thus to make sure that we catch
|
||||
// a situation where we add a defaulting func, we attempt to call it here with a nil
|
||||
// *oauthv1alpha1.OIDCClientSecretRequest. This will do nothing when there is no
|
||||
// defaulting func registered, but it will almost certainly panic if one is added.
|
||||
scheme.Default((*oauthv1alpha1.OIDCClientSecretRequest)(nil))
|
||||
|
||||
return scheme, schema.GroupVersion(oauthVirtualSupervisorGroupData)
|
||||
}
|
||||
|
||||
func addToSchemeAtNewGroup(scheme *runtime.Scheme, oldGroup, newGroup string, funcs ...func(*runtime.Scheme) error) {
|
||||
// we need a temporary place to register our types to avoid double registering them
|
||||
tmpScheme := runtime.NewScheme()
|
||||
schemeBuilder := runtime.NewSchemeBuilder(funcs...)
|
||||
utilruntime.Must(schemeBuilder.AddToScheme(tmpScheme))
|
||||
|
||||
for gvk := range tmpScheme.AllKnownTypes() {
|
||||
if gvk.GroupVersion() == metav1.Unversioned {
|
||||
continue // metav1.AddToGroupVersion registers types outside of our aggregated API group that we need to ignore
|
||||
}
|
||||
|
||||
if gvk.Group != oldGroup {
|
||||
panic(fmt.Errorf("tmp scheme has type not in the old aggregated API group %s: %s", oldGroup, gvk)) // programmer error
|
||||
}
|
||||
|
||||
obj, err := tmpScheme.New(gvk)
|
||||
if err != nil {
|
||||
panic(err) // programmer error, scheme internal code is broken
|
||||
}
|
||||
newGVK := schema.GroupVersionKind{
|
||||
Group: newGroup,
|
||||
Version: gvk.Version,
|
||||
Kind: gvk.Kind,
|
||||
}
|
||||
|
||||
// register the existing type but with the new group in the correct scheme
|
||||
scheme.AddKnownTypeWithName(newGVK, obj)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package scheme
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
oauthapi "go.pinniped.dev/generated/latest/apis/supervisor/virtual/oauth"
|
||||
oauthv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/virtual/oauth/v1alpha1"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
// the standard group
|
||||
regularOAuthGV := schema.GroupVersion{
|
||||
Group: "oauth.virtual.supervisor.pinniped.dev",
|
||||
Version: "v1alpha1",
|
||||
}
|
||||
regularOAuthGVInternal := schema.GroupVersion{
|
||||
Group: "oauth.virtual.supervisor.pinniped.dev",
|
||||
Version: runtime.APIVersionInternal,
|
||||
}
|
||||
|
||||
// the canonical other group
|
||||
otherOAuthGV := schema.GroupVersion{
|
||||
Group: "oauth.virtual.supervisor.walrus.tld",
|
||||
Version: "v1alpha1",
|
||||
}
|
||||
otherOAuthGVInternal := schema.GroupVersion{
|
||||
Group: "oauth.virtual.supervisor.walrus.tld",
|
||||
Version: runtime.APIVersionInternal,
|
||||
}
|
||||
|
||||
// kube's core internal
|
||||
internalGV := schema.GroupVersion{
|
||||
Group: "",
|
||||
Version: runtime.APIVersionInternal,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
apiGroupSuffix string
|
||||
want map[schema.GroupVersionKind]reflect.Type
|
||||
wantOAuthGroupVersion schema.GroupVersion
|
||||
}{
|
||||
{
|
||||
name: "regular api group",
|
||||
apiGroupSuffix: "pinniped.dev",
|
||||
want: map[schema.GroupVersionKind]reflect.Type{
|
||||
// all the types that are in the aggregated API group
|
||||
|
||||
regularOAuthGV.WithKind("OIDCClientSecretRequest"): reflect.TypeOf(&oauthv1alpha1.OIDCClientSecretRequest{}).Elem(),
|
||||
|
||||
regularOAuthGVInternal.WithKind("OIDCClientSecretRequest"): reflect.TypeOf(&oauthapi.OIDCClientSecretRequest{}).Elem(),
|
||||
|
||||
regularOAuthGV.WithKind("CreateOptions"): reflect.TypeOf(&metav1.CreateOptions{}).Elem(),
|
||||
regularOAuthGV.WithKind("DeleteOptions"): reflect.TypeOf(&metav1.DeleteOptions{}).Elem(),
|
||||
regularOAuthGV.WithKind("GetOptions"): reflect.TypeOf(&metav1.GetOptions{}).Elem(),
|
||||
regularOAuthGV.WithKind("ListOptions"): reflect.TypeOf(&metav1.ListOptions{}).Elem(),
|
||||
regularOAuthGV.WithKind("PatchOptions"): reflect.TypeOf(&metav1.PatchOptions{}).Elem(),
|
||||
regularOAuthGV.WithKind("UpdateOptions"): reflect.TypeOf(&metav1.UpdateOptions{}).Elem(),
|
||||
regularOAuthGV.WithKind("WatchEvent"): reflect.TypeOf(&metav1.WatchEvent{}).Elem(),
|
||||
|
||||
regularOAuthGVInternal.WithKind("WatchEvent"): reflect.TypeOf(&metav1.InternalEvent{}).Elem(),
|
||||
|
||||
// the types below this line do not really matter to us because they are in the core group
|
||||
|
||||
internalGV.WithKind("WatchEvent"): reflect.TypeOf(&metav1.InternalEvent{}).Elem(),
|
||||
|
||||
metav1.Unversioned.WithKind("APIGroup"): reflect.TypeOf(&metav1.APIGroup{}).Elem(),
|
||||
metav1.Unversioned.WithKind("APIGroupList"): reflect.TypeOf(&metav1.APIGroupList{}).Elem(),
|
||||
metav1.Unversioned.WithKind("APIResourceList"): reflect.TypeOf(&metav1.APIResourceList{}).Elem(),
|
||||
metav1.Unversioned.WithKind("APIVersions"): reflect.TypeOf(&metav1.APIVersions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("CreateOptions"): reflect.TypeOf(&metav1.CreateOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("DeleteOptions"): reflect.TypeOf(&metav1.DeleteOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("GetOptions"): reflect.TypeOf(&metav1.GetOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("ListOptions"): reflect.TypeOf(&metav1.ListOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("PatchOptions"): reflect.TypeOf(&metav1.PatchOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("Status"): reflect.TypeOf(&metav1.Status{}).Elem(),
|
||||
metav1.Unversioned.WithKind("UpdateOptions"): reflect.TypeOf(&metav1.UpdateOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("WatchEvent"): reflect.TypeOf(&metav1.WatchEvent{}).Elem(),
|
||||
},
|
||||
wantOAuthGroupVersion: regularOAuthGV,
|
||||
},
|
||||
{
|
||||
name: "other api group",
|
||||
apiGroupSuffix: "walrus.tld",
|
||||
want: map[schema.GroupVersionKind]reflect.Type{
|
||||
// all the types that are in the aggregated API group
|
||||
|
||||
otherOAuthGV.WithKind("OIDCClientSecretRequest"): reflect.TypeOf(&oauthv1alpha1.OIDCClientSecretRequest{}).Elem(),
|
||||
|
||||
otherOAuthGVInternal.WithKind("OIDCClientSecretRequest"): reflect.TypeOf(&oauthapi.OIDCClientSecretRequest{}).Elem(),
|
||||
|
||||
otherOAuthGV.WithKind("CreateOptions"): reflect.TypeOf(&metav1.CreateOptions{}).Elem(),
|
||||
otherOAuthGV.WithKind("DeleteOptions"): reflect.TypeOf(&metav1.DeleteOptions{}).Elem(),
|
||||
otherOAuthGV.WithKind("GetOptions"): reflect.TypeOf(&metav1.GetOptions{}).Elem(),
|
||||
otherOAuthGV.WithKind("ListOptions"): reflect.TypeOf(&metav1.ListOptions{}).Elem(),
|
||||
otherOAuthGV.WithKind("PatchOptions"): reflect.TypeOf(&metav1.PatchOptions{}).Elem(),
|
||||
otherOAuthGV.WithKind("UpdateOptions"): reflect.TypeOf(&metav1.UpdateOptions{}).Elem(),
|
||||
otherOAuthGV.WithKind("WatchEvent"): reflect.TypeOf(&metav1.WatchEvent{}).Elem(),
|
||||
|
||||
otherOAuthGVInternal.WithKind("WatchEvent"): reflect.TypeOf(&metav1.InternalEvent{}).Elem(),
|
||||
|
||||
// the types below this line do not really matter to us because they are in the core group
|
||||
|
||||
internalGV.WithKind("WatchEvent"): reflect.TypeOf(&metav1.InternalEvent{}).Elem(),
|
||||
|
||||
metav1.Unversioned.WithKind("APIGroup"): reflect.TypeOf(&metav1.APIGroup{}).Elem(),
|
||||
metav1.Unversioned.WithKind("APIGroupList"): reflect.TypeOf(&metav1.APIGroupList{}).Elem(),
|
||||
metav1.Unversioned.WithKind("APIResourceList"): reflect.TypeOf(&metav1.APIResourceList{}).Elem(),
|
||||
metav1.Unversioned.WithKind("APIVersions"): reflect.TypeOf(&metav1.APIVersions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("CreateOptions"): reflect.TypeOf(&metav1.CreateOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("DeleteOptions"): reflect.TypeOf(&metav1.DeleteOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("GetOptions"): reflect.TypeOf(&metav1.GetOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("ListOptions"): reflect.TypeOf(&metav1.ListOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("PatchOptions"): reflect.TypeOf(&metav1.PatchOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("Status"): reflect.TypeOf(&metav1.Status{}).Elem(),
|
||||
metav1.Unversioned.WithKind("UpdateOptions"): reflect.TypeOf(&metav1.UpdateOptions{}).Elem(),
|
||||
metav1.Unversioned.WithKind("WatchEvent"): reflect.TypeOf(&metav1.WatchEvent{}).Elem(),
|
||||
},
|
||||
wantOAuthGroupVersion: otherOAuthGV,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
scheme, oauthGV := New(tt.apiGroupSuffix)
|
||||
require.Equal(t, tt.want, scheme.AllKnownTypes())
|
||||
require.Equal(t, tt.wantOAuthGroupVersion, oauthGV)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -22,18 +22,26 @@ import (
|
||||
"github.com/joshlf/go-acl"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/pkg/version"
|
||||
"k8s.io/client-go/rest"
|
||||
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
||||
"k8s.io/utils/clock"
|
||||
|
||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
|
||||
pinnipedclientset "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned"
|
||||
pinnipedinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions"
|
||||
"go.pinniped.dev/internal/apiserviceref"
|
||||
"go.pinniped.dev/internal/config/supervisor"
|
||||
"go.pinniped.dev/internal/controller/apicerts"
|
||||
"go.pinniped.dev/internal/controller/supervisorconfig"
|
||||
"go.pinniped.dev/internal/controller/supervisorconfig/activedirectoryupstreamwatcher"
|
||||
"go.pinniped.dev/internal/controller/supervisorconfig/generator"
|
||||
@@ -45,6 +53,7 @@ import (
|
||||
"go.pinniped.dev/internal/crypto/ptls"
|
||||
"go.pinniped.dev/internal/deploymentref"
|
||||
"go.pinniped.dev/internal/downward"
|
||||
"go.pinniped.dev/internal/dynamiccert"
|
||||
"go.pinniped.dev/internal/groupsuffix"
|
||||
"go.pinniped.dev/internal/kubeclient"
|
||||
"go.pinniped.dev/internal/leaderelection"
|
||||
@@ -53,6 +62,8 @@ import (
|
||||
"go.pinniped.dev/internal/oidc/provider/manager"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/internal/secret"
|
||||
"go.pinniped.dev/internal/supervisor/apiserver"
|
||||
supervisorscheme "go.pinniped.dev/internal/supervisor/scheme"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -116,14 +127,18 @@ func prepareControllers(
|
||||
dynamicJWKSProvider jwks.DynamicJWKSProvider,
|
||||
dynamicTLSCertProvider provider.DynamicTLSCertProvider,
|
||||
dynamicUpstreamIDPProvider provider.DynamicUpstreamIDPProvider,
|
||||
dynamicServingCertProvider dynamiccert.Private,
|
||||
secretCache *secret.Cache,
|
||||
supervisorDeployment *appsv1.Deployment,
|
||||
kubeClient kubernetes.Interface,
|
||||
pinnipedClient pinnipedclientset.Interface,
|
||||
aggregatorClient aggregatorclient.Interface,
|
||||
kubeInformers kubeinformers.SharedInformerFactory,
|
||||
pinnipedInformers pinnipedinformers.SharedInformerFactory,
|
||||
leaderElector controllerinit.RunnerWrapper,
|
||||
podInfo *downward.PodInfo,
|
||||
) controllerinit.RunnerBuilder {
|
||||
oauthSupervisorGroupData := groupsuffix.SupervisorAggregatedGroups(*cfg.APIGroupSuffix)
|
||||
federationDomainInformer := pinnipedInformers.Config().V1alpha1().FederationDomains()
|
||||
secretInformer := kubeInformers.Core().V1().Secrets()
|
||||
|
||||
@@ -291,30 +306,69 @@ func prepareControllers(
|
||||
secretInformer,
|
||||
controllerlib.WithInformer,
|
||||
),
|
||||
singletonWorker)
|
||||
singletonWorker).
|
||||
WithController(
|
||||
apicerts.NewCertsManagerController(
|
||||
podInfo.Namespace,
|
||||
"pinniped-supervisor-api-tls-serving-certificate",
|
||||
cfg.Labels,
|
||||
kubeClient,
|
||||
secretInformer,
|
||||
controllerlib.WithInformer,
|
||||
controllerlib.WithInitialEvent,
|
||||
31536000*time.Second,
|
||||
"Pinniped Aggregation CA",
|
||||
cfg.NamesConfig.APIService,
|
||||
),
|
||||
singletonWorker,
|
||||
).
|
||||
WithController(
|
||||
apicerts.NewAPIServiceUpdaterController(
|
||||
podInfo.Namespace,
|
||||
"pinniped-supervisor-api-tls-serving-certificate",
|
||||
oauthSupervisorGroupData.APIServiceName(),
|
||||
aggregatorClient,
|
||||
secretInformer,
|
||||
controllerlib.WithInformer,
|
||||
),
|
||||
singletonWorker,
|
||||
).
|
||||
WithController(
|
||||
apicerts.NewCertsObserverController(
|
||||
podInfo.Namespace,
|
||||
"pinniped-supervisor-api-tls-serving-certificate",
|
||||
dynamicServingCertProvider,
|
||||
secretInformer,
|
||||
controllerlib.WithInformer,
|
||||
),
|
||||
singletonWorker,
|
||||
).
|
||||
WithController(
|
||||
apicerts.NewCertsExpirerController(
|
||||
podInfo.Namespace,
|
||||
"pinniped-supervisor-api-tls-serving-certificate",
|
||||
kubeClient,
|
||||
secretInformer,
|
||||
controllerlib.WithInformer,
|
||||
23328000*time.Second,
|
||||
apicerts.TLSCertificateChainSecretKey,
|
||||
plog.New(),
|
||||
),
|
||||
singletonWorker,
|
||||
)
|
||||
|
||||
return controllerinit.Prepare(controllerManager.Start, leaderElector, kubeInformers, pinnipedInformers)
|
||||
}
|
||||
|
||||
func startControllers(ctx context.Context, shutdown *sync.WaitGroup, buildControllers controllerinit.RunnerBuilder) error {
|
||||
runControllers, err := buildControllers(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create run controller func: %w", err)
|
||||
}
|
||||
|
||||
shutdown.Add(1)
|
||||
go func() {
|
||||
defer shutdown.Done()
|
||||
|
||||
runControllers(ctx)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func runSupervisor(ctx context.Context, podInfo *downward.PodInfo, cfg *supervisor.Config) error {
|
||||
serverInstallationNamespace := podInfo.Namespace
|
||||
oauthSupervisorGroupData := groupsuffix.SupervisorAggregatedGroups(*cfg.APIGroupSuffix)
|
||||
|
||||
apiServiceRef, err := apiserviceref.New(oauthSupervisorGroupData.APIServiceName())
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create API service ref: %w", err)
|
||||
}
|
||||
|
||||
dref, supervisorDeployment, supervisorPod, err := deploymentref.New(podInfo)
|
||||
if err != nil {
|
||||
@@ -323,6 +377,7 @@ func runSupervisor(ctx context.Context, podInfo *downward.PodInfo, cfg *supervis
|
||||
|
||||
opts := []kubeclient.Option{
|
||||
dref,
|
||||
apiServiceRef,
|
||||
kubeclient.WithMiddleware(groupsuffix.New(*cfg.APIGroupSuffix)),
|
||||
}
|
||||
|
||||
@@ -358,6 +413,8 @@ func runSupervisor(ctx context.Context, podInfo *downward.PodInfo, cfg *supervis
|
||||
_, _ = writer.Write([]byte("ok"))
|
||||
}))
|
||||
|
||||
dynamicServingCertProvider := dynamiccert.NewServingCert("supervisor-serving-cert")
|
||||
|
||||
dynamicJWKSProvider := jwks.NewDynamicJWKSProvider()
|
||||
dynamicTLSCertProvider := provider.NewDynamicTLSCertProvider()
|
||||
dynamicUpstreamIDPProvider := provider.NewDynamicUpstreamIDPProvider()
|
||||
@@ -372,25 +429,47 @@ func runSupervisor(ctx context.Context, podInfo *downward.PodInfo, cfg *supervis
|
||||
clientWithoutLeaderElection.Kubernetes.CoreV1().Secrets(serverInstallationNamespace), // writes to kube storage are allowed for non-leaders
|
||||
)
|
||||
|
||||
// Get the "real" name of the oauth virtual supervisor API group (i.e., the API group name with the
|
||||
// injected suffix).
|
||||
scheme, oauthGV := supervisorscheme.New(*cfg.APIGroupSuffix)
|
||||
|
||||
buildControllersFunc := prepareControllers(
|
||||
cfg,
|
||||
oidProvidersManager,
|
||||
dynamicJWKSProvider,
|
||||
dynamicTLSCertProvider,
|
||||
dynamicUpstreamIDPProvider,
|
||||
dynamicServingCertProvider,
|
||||
&secretCache,
|
||||
supervisorDeployment,
|
||||
client.Kubernetes,
|
||||
client.PinnipedSupervisor,
|
||||
client.Aggregation,
|
||||
kubeInformers,
|
||||
pinnipedInformers,
|
||||
leaderElector,
|
||||
podInfo,
|
||||
)
|
||||
|
||||
shutdown := &sync.WaitGroup{}
|
||||
|
||||
if err := startControllers(ctx, shutdown, buildControllersFunc); err != nil {
|
||||
return err
|
||||
// Get the aggregated API server config.
|
||||
aggregatedAPIServerConfig, err := getAggregatedAPIServerConfig(
|
||||
dynamicServingCertProvider,
|
||||
buildControllersFunc,
|
||||
*cfg.APIGroupSuffix,
|
||||
10250,
|
||||
scheme,
|
||||
oauthGV,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not configure aggregated API server: %w", err)
|
||||
}
|
||||
|
||||
// Complete the aggregated API server config and make a server instance.
|
||||
server, err := aggregatedAPIServerConfig.Complete().New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create aggregated API server: %w", err)
|
||||
}
|
||||
|
||||
if e := cfg.Endpoints.HTTP; e.Network != supervisor.NetworkDisabled {
|
||||
@@ -465,11 +544,73 @@ func runSupervisor(ctx context.Context, podInfo *downward.PodInfo, cfg *supervis
|
||||
plog.Debug("supervisor started")
|
||||
defer plog.Debug("supervisor exiting")
|
||||
|
||||
// Run the server. Its post-start hook will start the controllers.
|
||||
err = server.GenericAPIServer.PrepareRun().Run(ctx.Done())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shutdown.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a configuration for the aggregated API server.
|
||||
func getAggregatedAPIServerConfig(
|
||||
dynamicCertProvider dynamiccert.Private,
|
||||
buildControllers controllerinit.RunnerBuilder,
|
||||
apiGroupSuffix string,
|
||||
aggregatedAPIServerPort int64,
|
||||
scheme *runtime.Scheme,
|
||||
oauthVirtualSupervisorGroupVersion schema.GroupVersion,
|
||||
) (*apiserver.Config, error) {
|
||||
codecs := serializer.NewCodecFactory(scheme)
|
||||
|
||||
// this is unused for now but it is a safe value that we could use in the future
|
||||
defaultEtcdPathPrefix := fmt.Sprintf("/pinniped-concierge-registry/%s", apiGroupSuffix)
|
||||
|
||||
recommendedOptions := genericoptions.NewRecommendedOptions(
|
||||
defaultEtcdPathPrefix,
|
||||
codecs.LegacyCodec(oauthVirtualSupervisorGroupVersion),
|
||||
)
|
||||
recommendedOptions.Etcd = nil // turn off etcd storage because we don't need it yet
|
||||
recommendedOptions.SecureServing.ServerCert.GeneratedCert = dynamicCertProvider
|
||||
|
||||
// This port is configurable. It should be safe to cast because the config reader already validated it.
|
||||
recommendedOptions.SecureServing.BindPort = int(aggregatedAPIServerPort)
|
||||
|
||||
// secure TLS for connections coming from and going to the Kube API server
|
||||
// this is best effort because not all options provide the right hooks to override TLS config
|
||||
// since our only client is the Kube API server, this uses the most secure TLS config
|
||||
if err := ptls.SecureRecommendedOptions(recommendedOptions, kubeclient.Secure); err != nil {
|
||||
return nil, fmt.Errorf("failed to secure recommended options: %w", err)
|
||||
}
|
||||
|
||||
serverConfig := genericapiserver.NewRecommendedConfig(codecs)
|
||||
// Note that among other things, this ApplyTo() function copies
|
||||
// `recommendedOptions.SecureServing.ServerCert.GeneratedCert` into
|
||||
// `serverConfig.SecureServing.Cert` thus making `dynamicCertProvider`
|
||||
// the cert provider for the running server. The provider will be called
|
||||
// by the API machinery periodically. When the provider returns nil certs,
|
||||
// the API server will return "the server is currently unable to
|
||||
// handle the request" error responses for all incoming requests.
|
||||
// If the provider later starts returning certs, then the API server
|
||||
// will use them to handle the incoming requests successfully.
|
||||
if err := recommendedOptions.ApplyTo(serverConfig); err != nil {
|
||||
return nil, fmt.Errorf("failed to apply recommended options: %w", err)
|
||||
}
|
||||
|
||||
apiServerConfig := &apiserver.Config{
|
||||
GenericConfig: serverConfig,
|
||||
ExtraConfig: apiserver.ExtraConfig{
|
||||
BuildControllersPostStartHook: buildControllers,
|
||||
Scheme: scheme,
|
||||
NegotiatedSerializer: codecs,
|
||||
OauthVirtualSupervisorGroupVersion: oauthVirtualSupervisorGroupVersion,
|
||||
},
|
||||
}
|
||||
return apiServerConfig, nil
|
||||
}
|
||||
|
||||
func maybeSetupUnixPerms(endpoint *supervisor.Endpoint, pod *corev1.Pod) func() error {
|
||||
if endpoint.Network != supervisor.NetworkUnix {
|
||||
return func() error { return nil }
|
||||
|
||||
Reference in New Issue
Block a user