diff --git a/cmd/pinniped/cmd/whoami.go b/cmd/pinniped/cmd/whoami.go index bf973981..de7399f9 100644 --- a/cmd/pinniped/cmd/whoami.go +++ b/cmd/pinniped/cmd/whoami.go @@ -15,18 +15,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/tools/clientcmd" - identityapi "go.pinniped.dev/generated/latest/apis/concierge/identity" identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1" - loginapi "go.pinniped.dev/generated/latest/apis/concierge/login" - loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1" + conciergescheme "go.pinniped.dev/internal/concierge/scheme" "go.pinniped.dev/internal/groupsuffix" "go.pinniped.dev/internal/here" - "go.pinniped.dev/internal/plog" ) //nolint: gochecknoinits @@ -158,7 +153,7 @@ func writeWhoamiOutputYAML(output io.Writer, apiGroupSuffix string, whoAmI *iden } func serialize(output io.Writer, apiGroupSuffix string, whoAmI *identityv1alpha1.WhoAmIRequest, contentType string) error { - scheme, _, identityGV := conciergeschemeNew(apiGroupSuffix) + scheme, _, identityGV := conciergescheme.New(apiGroupSuffix) codecs := serializer.NewCodecFactory(scheme) respInfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), contentType) if !ok { @@ -189,110 +184,3 @@ func prettyStrings(ss []string) string { } return b.String() } - -// conciergeschemeNew is a temporary private function to stand in place for -// "go.pinniped.dev/internal/concierge/scheme".New until the later function is merged to main. -func conciergeschemeNew(apiGroupSuffix string) (_ *runtime.Scheme, login, identity 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( - loginv1alpha1.AddToScheme, - loginapi.AddToScheme, - identityv1alpha1.AddToScheme, - identityapi.AddToScheme, - ) - utilruntime.Must(schemeBuilder.AddToScheme(scheme)) - return scheme, loginv1alpha1.SchemeGroupVersion, identityv1alpha1.SchemeGroupVersion - } - - loginConciergeGroupData, identityConciergeGroupData := groupsuffix.ConciergeAggregatedGroups(apiGroupSuffix) - - addToSchemeAtNewGroup(scheme, loginv1alpha1.GroupName, loginConciergeGroupData.Group, loginv1alpha1.AddToScheme, loginapi.AddToScheme) - addToSchemeAtNewGroup(scheme, identityv1alpha1.GroupName, identityConciergeGroupData.Group, identityv1alpha1.AddToScheme, identityapi.AddToScheme) - - // manually register conversions and defaulting into the correct scheme since we cannot directly call AddToScheme - schemeBuilder := runtime.NewSchemeBuilder( - loginv1alpha1.RegisterConversions, - loginv1alpha1.RegisterDefaults, - identityv1alpha1.RegisterConversions, - identityv1alpha1.RegisterDefaults, - ) - utilruntime.Must(schemeBuilder.AddToScheme(scheme)) - - // we do not want to return errors from the scheme and instead would prefer to defer - // to the REST storage layer for consistency. The simplest way to do this is to force - // a cache miss from the authenticator cache. Kube API groups are validated via the - // IsDNS1123Subdomain func thus we can easily create a group that is guaranteed never - // to be in the authenticator cache. Add a timestamp just to be extra sure. - const authenticatorCacheMissPrefix = "_INVALID_API_GROUP_" - authenticatorCacheMiss := authenticatorCacheMissPrefix + time.Now().UTC().String() - - // we do not have any defaulting functions for *loginv1alpha1.TokenCredentialRequest - // 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 - // *loginv1alpha1.TokenCredentialRequest. This will do nothing when there is no - // defaulting func registered, but it will almost certainly panic if one is added. - scheme.Default((*loginv1alpha1.TokenCredentialRequest)(nil)) - - // on incoming requests, restore the authenticator API group to the standard group - // note that we are responsible for duplicating this logic for every external API version - scheme.AddTypeDefaultingFunc(&loginv1alpha1.TokenCredentialRequest{}, func(obj interface{}) { - credentialRequest := obj.(*loginv1alpha1.TokenCredentialRequest) - - if credentialRequest.Spec.Authenticator.APIGroup == nil { - // force a cache miss because this is an invalid request - plog.Debug("invalid token credential request, nil group", "authenticator", credentialRequest.Spec.Authenticator) - credentialRequest.Spec.Authenticator.APIGroup = &authenticatorCacheMiss - return - } - - restoredGroup, ok := groupsuffix.Unreplace(*credentialRequest.Spec.Authenticator.APIGroup, apiGroupSuffix) - if !ok { - // force a cache miss because this is an invalid request - plog.Debug("invalid token credential request, wrong group", "authenticator", credentialRequest.Spec.Authenticator) - credentialRequest.Spec.Authenticator.APIGroup = &authenticatorCacheMiss - return - } - - credentialRequest.Spec.Authenticator.APIGroup = &restoredGroup - }) - - return scheme, schema.GroupVersion(loginConciergeGroupData), schema.GroupVersion(identityConciergeGroupData) -} - -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) - } -} diff --git a/test/integration/cli_test.go b/test/integration/cli_test.go index 2934b55c..5f93ed40 100644 --- a/test/integration/cli_test.go +++ b/test/integration/cli_test.go @@ -24,19 +24,12 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" "gopkg.in/square/go-jose.v2" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" - identityapi "go.pinniped.dev/generated/latest/apis/concierge/identity" identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1" - loginapi "go.pinniped.dev/generated/latest/apis/concierge/login" - loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1" - "go.pinniped.dev/internal/groupsuffix" - "go.pinniped.dev/internal/plog" + conciergescheme "go.pinniped.dev/internal/concierge/scheme" "go.pinniped.dev/internal/testutil" "go.pinniped.dev/pkg/oidcclient" "go.pinniped.dev/pkg/oidcclient/filesession" @@ -173,7 +166,7 @@ func assertWhoami(ctx context.Context, t *testing.T, useProxy bool, pinnipedExe, func deserializeWhoAmIRequest(t *testing.T, data string, apiGroupSuffix string) *identityv1alpha1.WhoAmIRequest { t.Helper() - scheme, _, _ := conciergeschemeNew(apiGroupSuffix) + scheme, _, _ := conciergescheme.New(apiGroupSuffix) codecs := serializer.NewCodecFactory(scheme) respInfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeYAML) require.True(t, ok) @@ -420,110 +413,3 @@ func oidcLoginCommand(ctx context.Context, t *testing.T, pinnipedExe string, ses cmd.Env = append(os.Environ(), env.ProxyEnv()...) return cmd } - -// conciergeschemeNew is a temporary private function to stand in place for -// "go.pinniped.dev/internal/concierge/scheme".New until the later function is merged to main. -func conciergeschemeNew(apiGroupSuffix string) (_ *runtime.Scheme, login, identity 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( - loginv1alpha1.AddToScheme, - loginapi.AddToScheme, - identityv1alpha1.AddToScheme, - identityapi.AddToScheme, - ) - utilruntime.Must(schemeBuilder.AddToScheme(scheme)) - return scheme, loginv1alpha1.SchemeGroupVersion, identityv1alpha1.SchemeGroupVersion - } - - loginConciergeGroupData, identityConciergeGroupData := groupsuffix.ConciergeAggregatedGroups(apiGroupSuffix) - - addToSchemeAtNewGroup(scheme, loginv1alpha1.GroupName, loginConciergeGroupData.Group, loginv1alpha1.AddToScheme, loginapi.AddToScheme) - addToSchemeAtNewGroup(scheme, identityv1alpha1.GroupName, identityConciergeGroupData.Group, identityv1alpha1.AddToScheme, identityapi.AddToScheme) - - // manually register conversions and defaulting into the correct scheme since we cannot directly call AddToScheme - schemeBuilder := runtime.NewSchemeBuilder( - loginv1alpha1.RegisterConversions, - loginv1alpha1.RegisterDefaults, - identityv1alpha1.RegisterConversions, - identityv1alpha1.RegisterDefaults, - ) - utilruntime.Must(schemeBuilder.AddToScheme(scheme)) - - // we do not want to return errors from the scheme and instead would prefer to defer - // to the REST storage layer for consistency. The simplest way to do this is to force - // a cache miss from the authenticator cache. Kube API groups are validated via the - // IsDNS1123Subdomain func thus we can easily create a group that is guaranteed never - // to be in the authenticator cache. Add a timestamp just to be extra sure. - const authenticatorCacheMissPrefix = "_INVALID_API_GROUP_" - authenticatorCacheMiss := authenticatorCacheMissPrefix + time.Now().UTC().String() - - // we do not have any defaulting functions for *loginv1alpha1.TokenCredentialRequest - // 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 - // *loginv1alpha1.TokenCredentialRequest. This will do nothing when there is no - // defaulting func registered, but it will almost certainly panic if one is added. - scheme.Default((*loginv1alpha1.TokenCredentialRequest)(nil)) - - // on incoming requests, restore the authenticator API group to the standard group - // note that we are responsible for duplicating this logic for every external API version - scheme.AddTypeDefaultingFunc(&loginv1alpha1.TokenCredentialRequest{}, func(obj interface{}) { - credentialRequest := obj.(*loginv1alpha1.TokenCredentialRequest) - - if credentialRequest.Spec.Authenticator.APIGroup == nil { - // force a cache miss because this is an invalid request - plog.Debug("invalid token credential request, nil group", "authenticator", credentialRequest.Spec.Authenticator) - credentialRequest.Spec.Authenticator.APIGroup = &authenticatorCacheMiss - return - } - - restoredGroup, ok := groupsuffix.Unreplace(*credentialRequest.Spec.Authenticator.APIGroup, apiGroupSuffix) - if !ok { - // force a cache miss because this is an invalid request - plog.Debug("invalid token credential request, wrong group", "authenticator", credentialRequest.Spec.Authenticator) - credentialRequest.Spec.Authenticator.APIGroup = &authenticatorCacheMiss - return - } - - credentialRequest.Spec.Authenticator.APIGroup = &restoredGroup - }) - - return scheme, schema.GroupVersion(loginConciergeGroupData), schema.GroupVersion(identityConciergeGroupData) -} - -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) - } -}