Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: rolebindingrestrictions.authorization.openshift.io
spec:
group: authorization.openshift.io
names:
kind: RoleBindingRestriction
listKind: RoleBindingRestrictionList
plural: rolebindingrestrictions
singular: rolebindingrestriction
subresources:
status: {}
scope: Namespaced
versions:
- name: v1
served: true
storage: true
validation:
openAPIV3Schema:
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
type: string
metadata:
description: Standard object's metadata.
type: object
spec:
description: Spec defines the matcher.
properties:
grouprestriction:
description: GroupRestriction matches against group subjects.
nullable: true
properties:
groups:
description: Groups is a list of groups used to match against an
individual user's groups. If the user is a member of one of the
whitelisted groups, the user is allowed to be bound to a role.
items:
type: string
type: array
nullable: true
labels:
description: Selectors specifies a list of label selectors over
group labels.
items:
type: object
type: array
nullable: true
type: object
serviceaccountrestriction:
description: ServiceAccountRestriction matches against service-account
subjects.
nullable: true
properties:
namespaces:
description: Namespaces specifies a list of literal namespace names.
items:
type: string
type: array
serviceaccounts:
description: ServiceAccounts specifies a list of literal service-account
names.
items:
properties:
name:
description: Name is the name of the service account.
type: string
namespace:
description: Namespace is the namespace of the service account. Service
accounts from inside the whitelisted namespaces are allowed
to be bound to roles. If Namespace is empty, then the namespace
of the RoleBindingRestriction in which the ServiceAccountReference
is embedded is used.
type: string
type: object
type: array
type: object
userrestriction:
description: UserRestriction matches against user subjects.
nullable: true
properties:
groups:
description: Groups specifies a list of literal group names.
items:
type: string
type: array
nullable: true
labels:
description: Selectors specifies a list of label selectors over
user labels.
items:
type: object
type: array
nullable: true
users:
description: Users specifies a list of literal user names.
items:
type: string
type: array
type: object
type: object
1 change: 1 addition & 0 deletions hack/test-cmd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ os::cmd::try_until_success "oc get service kubernetes --namespace default --conf
os::cmd::try_until_success "oc login --server=${KUBERNETES_MASTER} --certificate-authority ${MASTER_CONFIG_DIR}/server-ca.crt -u test-user -p anything" $(( 160 * second )) 0.25
# wait for the CRD to be available
os::cmd::try_until_success "oc get clusterresourcequotas --config='${ADMIN_KUBECONFIG}'" $(( 160 * second )) 0.25
os::cmd::try_until_success "oc get rolebindingrestrictions --config='${ADMIN_KUBECONFIG}'" $(( 160 * second )) 0.25
os::test::junit::declare_suite_end
os::log::debug "localup server health checks done at: $( date )"

Expand Down
2 changes: 2 additions & 0 deletions pkg/admission/customresourcevalidation/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/admission"

authorizationv1 "github.com/openshift/api/authorization/v1"
configv1 "github.com/openshift/api/config/v1"
quotav1 "github.com/openshift/api/quota/v1"
securityv1 "github.com/openshift/api/security/v1"
Expand Down Expand Up @@ -48,4 +49,5 @@ func init() {
utilruntime.Must(configv1.Install(supportedObjectsScheme))
utilruntime.Must(quotav1.Install(supportedObjectsScheme))
utilruntime.Must(securityv1.Install(supportedObjectsScheme))
utilruntime.Must(authorizationv1.Install(supportedObjectsScheme))
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/openshift/origin/pkg/admission/customresourcevalidation/image"
"github.com/openshift/origin/pkg/admission/customresourcevalidation/oauth"
"github.com/openshift/origin/pkg/admission/customresourcevalidation/project"
"github.com/openshift/origin/pkg/admission/customresourcevalidation/rolebindingrestriction"
"github.com/openshift/origin/pkg/admission/customresourcevalidation/scheduler"
"github.com/openshift/origin/pkg/admission/customresourcevalidation/securitycontextconstraints"
)
Expand All @@ -27,6 +28,7 @@ var AllCustomResourceValidators = []string{
scheduler.PluginName,
clusterresourcequota.PluginName,
securitycontextconstraints.PluginName,
rolebindingrestriction.PluginName,

// this one is special because we don't work without it.
securitycontextconstraints.DefaultingPluginName,
Expand All @@ -47,6 +49,8 @@ func RegisterCustomResourceValidation(plugins *admission.Plugins) {
clusterresourcequota.Register(plugins)
// This plugin validates the security.openshift.io/v1 SecurityContextConstraints resources.
securitycontextconstraints.Register(plugins)
// This plugin validates the authorization.openshift.io/v1 RoleBindingRestriction resources.
rolebindingrestriction.Register(plugins)

// this one is special because we don't work without it.
securitycontextconstraints.RegisterDefaulting(plugins)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package rolebindingrestriction

import (
"fmt"
"io"

"k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission"

authorizationv1 "github.com/openshift/api/authorization/v1"

"github.com/openshift/origin/pkg/admission/customresourcevalidation"
rbrvalidation "github.com/openshift/origin/pkg/admission/customresourcevalidation/rolebindingrestriction/validation"
)

const PluginName = "authorization.openshift.io/ValidateRoleBindingRestriction"

func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
return customresourcevalidation.NewValidator(
map[schema.GroupResource]bool{
{Group: authorizationv1.GroupName, Resource: "rolebindingrestrictions"}: true,
},
map[schema.GroupVersionKind]customresourcevalidation.ObjectValidator{
authorizationv1.GroupVersion.WithKind("RoleBindingRestriction"): roleBindingRestrictionV1{},
})
})
}

func toRoleBindingRestriction(uncastObj runtime.Object) (*authorizationv1.RoleBindingRestriction, field.ErrorList) {
if uncastObj == nil {
return nil, nil
}

allErrs := field.ErrorList{}

obj, ok := uncastObj.(*authorizationv1.RoleBindingRestriction)
if !ok {
return nil, append(allErrs,
field.NotSupported(field.NewPath("kind"), fmt.Sprintf("%T", uncastObj), []string{"RoleBindingRestriction"}),
field.NotSupported(field.NewPath("apiVersion"), fmt.Sprintf("%T", uncastObj), []string{authorizationv1.GroupVersion.String()}))
}

return obj, nil
}

type roleBindingRestrictionV1 struct {
}

func (roleBindingRestrictionV1) ValidateCreate(obj runtime.Object) field.ErrorList {
roleBindingRestrictionObj, errs := toRoleBindingRestriction(obj)
if len(errs) > 0 {
return errs
}

errs = append(errs, validation.ValidateObjectMeta(&roleBindingRestrictionObj.ObjectMeta, true, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
errs = append(errs, rbrvalidation.ValidateRoleBindingRestriction(roleBindingRestrictionObj)...)

return errs
}

func (roleBindingRestrictionV1) ValidateUpdate(obj runtime.Object, oldObj runtime.Object) field.ErrorList {
roleBindingRestrictionObj, errs := toRoleBindingRestriction(obj)
if len(errs) > 0 {
return errs
}
roleBindingRestrictionOldObj, errs := toRoleBindingRestriction(oldObj)
if len(errs) > 0 {
return errs
}

errs = append(errs, validation.ValidateObjectMeta(&roleBindingRestrictionObj.ObjectMeta, true, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
errs = append(errs, rbrvalidation.ValidateRoleBindingRestrictionUpdate(roleBindingRestrictionObj, roleBindingRestrictionOldObj)...)

return errs
}

func (r roleBindingRestrictionV1) ValidateStatusUpdate(obj runtime.Object, oldObj runtime.Object) field.ErrorList {
return r.ValidateUpdate(obj, oldObj)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package validation

import (
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/apis/core/validation"

authorizationv1 "github.com/openshift/api/authorization/v1"
)

func ValidateRoleBindingRestriction(rbr *authorizationv1.RoleBindingRestriction) field.ErrorList {
allErrs := validation.ValidateObjectMeta(&rbr.ObjectMeta, true,
apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))

allErrs = append(allErrs,
ValidateRoleBindingRestrictionSpec(&rbr.Spec, field.NewPath("spec"))...)

return allErrs
}

func ValidateRoleBindingRestrictionUpdate(rbr, old *authorizationv1.RoleBindingRestriction) field.ErrorList {
allErrs := ValidateRoleBindingRestriction(rbr)

allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&rbr.ObjectMeta,
&old.ObjectMeta, field.NewPath("metadata"))...)

return allErrs
}

func ValidateRoleBindingRestrictionSpec(spec *authorizationv1.RoleBindingRestrictionSpec, fld *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
const invalidMsg = `must specify exactly one of userrestriction, grouprestriction, or serviceaccountrestriction`

if spec.UserRestriction != nil {
if spec.GroupRestriction != nil {
allErrs = append(allErrs, field.Invalid(fld.Child("grouprestriction"),
"both userrestriction and grouprestriction specified", invalidMsg))
}
if spec.ServiceAccountRestriction != nil {
allErrs = append(allErrs,
field.Invalid(fld.Child("serviceaccountrestriction"),
"both userrestriction and serviceaccountrestriction specified", invalidMsg))
}
} else if spec.GroupRestriction != nil {
if spec.ServiceAccountRestriction != nil {
allErrs = append(allErrs,
field.Invalid(fld.Child("serviceaccountrestriction"),
"both grouprestriction and serviceaccountrestriction specified", invalidMsg))
}
} else if spec.ServiceAccountRestriction == nil {
allErrs = append(allErrs, field.Required(fld.Child("userrestriction"),
invalidMsg))
}

if spec.UserRestriction != nil {
allErrs = append(allErrs, ValidateRoleBindingRestrictionUser(spec.UserRestriction, fld.Child("userrestriction"))...)
}
if spec.GroupRestriction != nil {
allErrs = append(allErrs, ValidateRoleBindingRestrictionGroup(spec.GroupRestriction, fld.Child("grouprestriction"))...)
}
if spec.ServiceAccountRestriction != nil {
allErrs = append(allErrs, ValidateRoleBindingRestrictionServiceAccount(spec.ServiceAccountRestriction, fld.Child("serviceaccountrestriction"))...)
}

return allErrs
}

func ValidateRoleBindingRestrictionUser(user *authorizationv1.UserRestriction, fld *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
const invalidMsg = `must specify at least one user, group, or label selector`

if !(len(user.Users) > 0 || len(user.Groups) > 0 || len(user.Selectors) > 0) {
allErrs = append(allErrs, field.Required(fld.Child("users"), invalidMsg))
}

for i, selector := range user.Selectors {
allErrs = append(allErrs,
unversionedvalidation.ValidateLabelSelector(&selector,
fld.Child("selector").Index(i))...)
}

return allErrs
}

func ValidateRoleBindingRestrictionGroup(group *authorizationv1.GroupRestriction, fld *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
const invalidMsg = `must specify at least one group or label selector`

if !(len(group.Groups) > 0 || len(group.Selectors) > 0) {
allErrs = append(allErrs, field.Required(fld.Child("groups"), invalidMsg))
}

for i, selector := range group.Selectors {
allErrs = append(allErrs,
unversionedvalidation.ValidateLabelSelector(&selector,
fld.Child("selector").Index(i))...)
}

return allErrs
}

func ValidateRoleBindingRestrictionServiceAccount(sa *authorizationv1.ServiceAccountRestriction, fld *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
const invalidMsg = `must specify at least one service account or namespace`

if !(len(sa.ServiceAccounts) > 0 || len(sa.Namespaces) > 0) {
allErrs = append(allErrs,
field.Required(fld.Child("serviceaccounts"), invalidMsg))
}

return allErrs
}
2 changes: 1 addition & 1 deletion pkg/authorization/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (c *completedConfig) newV1RESTStorage() (map[string]rest.Storage, error) {
resourceAccessReviewStorage := resourceaccessreview.NewREST(c.GenericConfig.Authorization.Authorizer, c.ExtraConfig.SubjectLocator)
resourceAccessReviewRegistry := resourceaccessreview.NewRegistry(resourceAccessReviewStorage)
localResourceAccessReviewStorage := localresourceaccessreview.NewREST(resourceAccessReviewRegistry)
roleBindingRestrictionStorage, err := rolebindingrestrictionetcd.NewREST(c.GenericConfig.RESTOptionsGetter)
roleBindingRestrictionStorage, err := rolebindingrestrictionetcd.NewREST()
if err != nil {
return nil, fmt.Errorf("error building REST storage: %v", err)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll clean this up, my bad, can be done in follow-up if necessary

Expand Down
Loading