Skip to content

Commit

Permalink
UPSTREAM: <carry>: Add configuration to RestrictSubjectBindings admis…
Browse files Browse the repository at this point in the history
…sion plugin

to allow external components to specify that the oauth stack
will/will not be present on the cluster. When the oauth stack is
not present, this admission plugin is not effective and should not
configure and start informers for APIs served by the oauth-apiserver.

Signed-off-by: Bryce Palmer <[email protected]>
  • Loading branch information
everettraven committed Jan 24, 2025
1 parent 8ac36bf commit edf1675
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// +k8s:deepcopy-gen=package,register

// Package v1alpha is the v1alpha1 version of the API.
package v1alpha1
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package v1alpha1

import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)

var (
GroupVersion = schema.GroupVersion{Group: "authorization.openshift.io", Version: "v1alpha1"}
internalGroupVersion = schema.GroupVersion{Group: "authorization.openshift.io", Version: runtime.APIVersionInternal}
)

var (
localSchemeBuilder = runtime.NewSchemeBuilder(
addKnownTypes,
)
Install = localSchemeBuilder.AddToScheme
)

func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(GroupVersion,
&RestrictSubjectBindingsAdmissionConfig{},
)

scheme.AddKnownTypes(internalGroupVersion,
&RestrictSubjectBindingsAdmissionConfig{},
)
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// RestrictSubjectBindingsAdmissionConfig is the type
// used for configuring the authorization.openshift.io/RestrictSubjectBindings
// admission plugin.
type RestrictSubjectBindingsAdmissionConfig struct {
metav1.TypeMeta `json:",inline"`

// openshiftOAuthDesiredState specifies the desired state
// of the OpenShift oauth-apiserver based on observed configuration.
//
// Allowed values are Desired and NotDesired.
//
// When set to Desired, the authorization.openshift.io/RestrictSubjectBindings
// admission plugin will be configured with the expectation that the OpenShift
// oauth-apiserver will eventually be running and serving it's APIs.
//
// When set to NotDesired, the authorization.openshift.io/RestrictSubjectBindings
// admission plugin will be configured with the expectation that the OpenShift
// oauth-apiserver will not be running.
OpenShiftOAuthDesiredState OpenShiftOAuthState `json:"openshiftOAuthDesiredState"`
}

type OpenShiftOAuthState string

const (
OpenShiftOAuthStateDesired = "Desired"
OpenShiftOAuthStateNotDesired = "NotDesired"
)

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/apis/rbac"

Expand All @@ -21,16 +22,53 @@ import (
userclient "github.com/openshift/client-go/user/clientset/versioned"
userinformer "github.com/openshift/client-go/user/informers/externalversions"
"github.com/openshift/library-go/pkg/apiserver/admission/admissionrestconfig"
"github.com/openshift/library-go/pkg/config/helpers"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/authorization/apis/restrictusers/v1alpha1"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/authorization/restrictusers/usercache"
)

func Register(plugins *admission.Plugins) {
plugins.Register("authorization.openshift.io/RestrictSubjectBindings",
func(config io.Reader) (admission.Interface, error) {
return NewRestrictUsersAdmission()
cfg, err := readConfig(config)
if err != nil {
return nil, err
}

return NewRestrictUsersAdmission(cfg)
})
}

func defaultConfig() *v1alpha1.RestrictSubjectBindingsAdmissionConfig {
return &v1alpha1.RestrictSubjectBindingsAdmissionConfig{
OpenShiftOAuthDesiredState: v1alpha1.OpenShiftOAuthStateDesired,
}
}

func readConfig(reader io.Reader) (*v1alpha1.RestrictSubjectBindingsAdmissionConfig, error) {
obj, err := helpers.ReadYAMLToInternal(reader, v1alpha1.Install)
if err != nil {
return nil, err
}
if obj == nil {
return nil, nil
}
config, ok := obj.(*v1alpha1.RestrictSubjectBindingsAdmissionConfig)
if !ok {
return nil, fmt.Errorf("unexpected config object: %#v", obj)
}

// validate config
switch config.OpenShiftOAuthDesiredState {
case v1alpha1.OpenShiftOAuthStateDesired, v1alpha1.OpenShiftOAuthStateNotDesired:
// valid, do nothing
default:
return nil, fmt.Errorf("config is invalid, openshiftOAuthDesiredState must be one of Desired,NotDesired but was %s", config.OpenShiftOAuthDesiredState)
}

return config, nil
}

type GroupCache interface {
GroupsFor(string) ([]*userv1.Group, error)
HasSynced() bool
Expand All @@ -46,18 +84,27 @@ type restrictUsersAdmission struct {
userClient userclient.Interface
kubeClient kubernetes.Interface
groupCache GroupCache
oauthState v1alpha1.OpenShiftOAuthState
}

var _ = admissionrestconfig.WantsRESTClientConfig(&restrictUsersAdmission{})
var _ = WantsUserInformer(&restrictUsersAdmission{})
var _ = initializer.WantsExternalKubeClientSet(&restrictUsersAdmission{})
var _ = admission.ValidationInterface(&restrictUsersAdmission{})
var (
_ = admissionrestconfig.WantsRESTClientConfig(&restrictUsersAdmission{})
_ = WantsUserInformer(&restrictUsersAdmission{})
_ = initializer.WantsExternalKubeClientSet(&restrictUsersAdmission{})
_ = admission.ValidationInterface(&restrictUsersAdmission{})
)

// NewRestrictUsersAdmission configures an admission plugin that enforces
// restrictions on adding role bindings in a project.
func NewRestrictUsersAdmission() (admission.Interface, error) {
func NewRestrictUsersAdmission(cfg *v1alpha1.RestrictSubjectBindingsAdmissionConfig) (admission.Interface, error) {
return &restrictUsersAdmission{
Handler: admission.NewHandler(admission.Create, admission.Update),
oauthState: func() v1alpha1.OpenShiftOAuthState {
if cfg != nil {
return cfg.OpenShiftOAuthDesiredState
}
return v1alpha1.OpenShiftOAuthStateDesired
}(),
}, nil
}

Expand Down Expand Up @@ -87,6 +134,15 @@ func (q *restrictUsersAdmission) SetRESTClientConfig(restClientConfig rest.Confi
}

func (q *restrictUsersAdmission) SetUserInformer(userInformers userinformer.SharedInformerFactory) {
if q.oauthState == v1alpha1.OpenShiftOAuthStateNotDesired {
return
}

if err := userInformers.User().V1().Groups().Informer().AddIndexers(cache.Indexers{
usercache.ByUserIndexName: usercache.ByUserIndexKeys,
}); err != nil {
return
}
q.groupCache = usercache.NewGroupCache(userInformers.User().V1().Groups())
}

Expand Down Expand Up @@ -116,6 +172,10 @@ func subjectsDelta(elementsToIgnore, elements []rbac.Subject) []rbac.Subject {
// each subject in the binding must be matched by some rolebinding restriction
// in the namespace.
func (q *restrictUsersAdmission) Validate(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) (err error) {
if q.oauthState == v1alpha1.OpenShiftOAuthStateNotDesired {
klog.V(2).Info("admission plugin authorization.openshift.io/RestrictSubjectBindings is configured to act as if the OpenShift oauth-apiserver is not present. This admission plugin relies on the OpenShift oauth-apiserver to function as expected and should be disabled when it is not present. Acting as if disabled and not enforcing subject bindings.")
return nil
}

// We only care about rolebindings
if a.GetResource().GroupResource() != rbac.Resource("rolebindings") {
Expand Down Expand Up @@ -226,7 +286,7 @@ func (q *restrictUsersAdmission) ValidateInitialization() error {
if q.userClient == nil {
return errors.New("RestrictUsersAdmission plugin requires an OpenShift user client")
}
if q.groupCache == nil {
if q.groupCache == nil && q.oauthState == v1alpha1.OpenShiftOAuthStateDesired {
return errors.New("RestrictUsersAdmission plugin requires a group cache")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ func TestAdmission(t *testing.T) {
fakeUserClient := fakeuserclient.NewSimpleClientset(tc.userObjects...)
fakeAuthorizationClient := fakeauthorizationclient.NewSimpleClientset(tc.authorizationObjects...)

plugin, err := NewRestrictUsersAdmission()
plugin, err := NewRestrictUsersAdmission(nil)
if err != nil {
t.Errorf("unexpected error initializing admission plugin: %v", err)
}
Expand Down
13 changes: 5 additions & 8 deletions openshift-kube-apiserver/openshiftkubeapiserver/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ import (
clientgoinformers "k8s.io/client-go/informers"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/authorization/restrictusers"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/authorization/restrictusers/usercache"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/autoscaling/managednode"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/autoscaling/managementcpusoverride"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/scheduler/nodeenv"
Expand Down Expand Up @@ -109,7 +107,8 @@ func OpenShiftKubeAPIServerConfigPatch(genericConfig *genericapiserver.Config, k
// END HANDLER CHAIN

openshiftAPIServiceReachabilityCheck := newOpenshiftAPIServiceReachabilityCheck(genericConfig.PublicAddress)
oauthAPIServiceReachabilityCheck := newOAuthPIServiceReachabilityCheck(genericConfig.PublicAddress)
oauthAPIServiceReachabilityCheck := newOAuthAPIServiceReachabilityCheck(genericConfig.PublicAddress)

genericConfig.ReadyzChecks = append(genericConfig.ReadyzChecks, openshiftAPIServiceReachabilityCheck, oauthAPIServiceReachabilityCheck)

genericConfig.AddPostStartHookOrDie("openshift.io-startkubeinformers", func(context genericapiserver.PostStartHookContext) error {
Expand Down Expand Up @@ -176,11 +175,6 @@ func newInformers(loopbackClientConfig *rest.Config) (*kubeAPIServerInformers, e
OpenshiftUserInformers: userinformer.NewSharedInformerFactory(userClient, defaultInformerResyncPeriod),
OpenshiftConfigInformers: configv1informer.NewSharedInformerFactory(configClient, defaultInformerResyncPeriod),
}
if err := ret.OpenshiftUserInformers.User().V1().Groups().Informer().AddIndexers(cache.Indexers{
usercache.ByUserIndexName: usercache.ByUserIndexKeys,
}); err != nil {
return nil, err
}

return ret, nil
}
Expand All @@ -195,12 +189,15 @@ type kubeAPIServerInformers struct {
func (i *kubeAPIServerInformers) getOpenshiftQuotaInformers() quotainformer.SharedInformerFactory {
return i.OpenshiftQuotaInformers
}

func (i *kubeAPIServerInformers) getOpenshiftSecurityInformers() securityv1informer.SharedInformerFactory {
return i.OpenshiftSecurityInformers
}

func (i *kubeAPIServerInformers) getOpenshiftUserInformers() userinformer.SharedInformerFactory {
return i.OpenshiftUserInformers
}

func (i *kubeAPIServerInformers) getOpenshiftInfraInformers() configv1informer.SharedInformerFactory {
return i.OpenshiftConfigInformers
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func newOpenshiftAPIServiceReachabilityCheck(ipForKubernetesDefaultService net.I
return newAggregatedAPIServiceReachabilityCheck(ipForKubernetesDefaultService, "openshift-apiserver", "api")
}

func newOAuthPIServiceReachabilityCheck(ipForKubernetesDefaultService net.IP) *aggregatedAPIServiceAvailabilityCheck {
func newOAuthAPIServiceReachabilityCheck(ipForKubernetesDefaultService net.IP) *aggregatedAPIServiceAvailabilityCheck {
return newAggregatedAPIServiceReachabilityCheck(ipForKubernetesDefaultService, "openshift-oauth-apiserver", "api")
}

Expand Down

0 comments on commit edf1675

Please sign in to comment.