Skip to content

Commit a0a3b1f

Browse files
Merge pull request #22765 from deads2k/sally-rbr
add RBR from CRDs
2 parents 84e1f47 + ee1fe05 commit a0a3b1f

16 files changed

Lines changed: 398 additions & 95 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
apiVersion: apiextensions.k8s.io/v1beta1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: rolebindingrestrictions.authorization.openshift.io
5+
spec:
6+
group: authorization.openshift.io
7+
names:
8+
kind: RoleBindingRestriction
9+
listKind: RoleBindingRestrictionList
10+
plural: rolebindingrestrictions
11+
singular: rolebindingrestriction
12+
subresources:
13+
status: {}
14+
scope: Namespaced
15+
versions:
16+
- name: v1
17+
served: true
18+
storage: true
19+
validation:
20+
openAPIV3Schema:
21+
properties:
22+
apiVersion:
23+
description: 'APIVersion defines the versioned schema of this representation
24+
of an object. Servers should convert recognized schemas to the latest
25+
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
26+
type: string
27+
kind:
28+
description: 'Kind is a string value representing the REST resource this
29+
object represents. Servers may infer this from the endpoint the client
30+
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
31+
type: string
32+
metadata:
33+
description: Standard object's metadata.
34+
type: object
35+
spec:
36+
description: Spec defines the matcher.
37+
properties:
38+
grouprestriction:
39+
description: GroupRestriction matches against group subjects.
40+
nullable: true
41+
properties:
42+
groups:
43+
description: Groups is a list of groups used to match against an
44+
individual user's groups. If the user is a member of one of the
45+
whitelisted groups, the user is allowed to be bound to a role.
46+
items:
47+
type: string
48+
type: array
49+
nullable: true
50+
labels:
51+
description: Selectors specifies a list of label selectors over
52+
group labels.
53+
items:
54+
type: object
55+
type: array
56+
nullable: true
57+
type: object
58+
serviceaccountrestriction:
59+
description: ServiceAccountRestriction matches against service-account
60+
subjects.
61+
nullable: true
62+
properties:
63+
namespaces:
64+
description: Namespaces specifies a list of literal namespace names.
65+
items:
66+
type: string
67+
type: array
68+
serviceaccounts:
69+
description: ServiceAccounts specifies a list of literal service-account
70+
names.
71+
items:
72+
properties:
73+
name:
74+
description: Name is the name of the service account.
75+
type: string
76+
namespace:
77+
description: Namespace is the namespace of the service account. Service
78+
accounts from inside the whitelisted namespaces are allowed
79+
to be bound to roles. If Namespace is empty, then the namespace
80+
of the RoleBindingRestriction in which the ServiceAccountReference
81+
is embedded is used.
82+
type: string
83+
type: object
84+
type: array
85+
type: object
86+
userrestriction:
87+
description: UserRestriction matches against user subjects.
88+
nullable: true
89+
properties:
90+
groups:
91+
description: Groups specifies a list of literal group names.
92+
items:
93+
type: string
94+
type: array
95+
nullable: true
96+
labels:
97+
description: Selectors specifies a list of label selectors over
98+
user labels.
99+
items:
100+
type: object
101+
type: array
102+
nullable: true
103+
users:
104+
description: Users specifies a list of literal user names.
105+
items:
106+
type: string
107+
type: array
108+
type: object
109+
type: object

hack/test-cmd.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ os::cmd::try_until_success "oc get service kubernetes --namespace default --conf
9999
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
100100
# wait for the CRD to be available
101101
os::cmd::try_until_success "oc get clusterresourcequotas --config='${ADMIN_KUBECONFIG}'" $(( 160 * second )) 0.25
102+
os::cmd::try_until_success "oc get rolebindingrestrictions --config='${ADMIN_KUBECONFIG}'" $(( 160 * second )) 0.25
102103
os::test::junit::declare_suite_end
103104
os::log::debug "localup server health checks done at: $( date )"
104105

pkg/admission/customresourcevalidation/attributes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
66
"k8s.io/apiserver/pkg/admission"
77

8+
authorizationv1 "github.com/openshift/api/authorization/v1"
89
configv1 "github.com/openshift/api/config/v1"
910
quotav1 "github.com/openshift/api/quota/v1"
1011
securityv1 "github.com/openshift/api/security/v1"
@@ -48,4 +49,5 @@ func init() {
4849
utilruntime.Must(configv1.Install(supportedObjectsScheme))
4950
utilruntime.Must(quotav1.Install(supportedObjectsScheme))
5051
utilruntime.Must(securityv1.Install(supportedObjectsScheme))
52+
utilruntime.Must(authorizationv1.Install(supportedObjectsScheme))
5153
}

pkg/admission/customresourcevalidation/customresourcevalidationregistration/cr_validation_registration.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/openshift/origin/pkg/admission/customresourcevalidation/image"
1212
"github.com/openshift/origin/pkg/admission/customresourcevalidation/oauth"
1313
"github.com/openshift/origin/pkg/admission/customresourcevalidation/project"
14+
"github.com/openshift/origin/pkg/admission/customresourcevalidation/rolebindingrestriction"
1415
"github.com/openshift/origin/pkg/admission/customresourcevalidation/scheduler"
1516
"github.com/openshift/origin/pkg/admission/customresourcevalidation/securitycontextconstraints"
1617
)
@@ -27,6 +28,7 @@ var AllCustomResourceValidators = []string{
2728
scheduler.PluginName,
2829
clusterresourcequota.PluginName,
2930
securitycontextconstraints.PluginName,
31+
rolebindingrestriction.PluginName,
3032

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

5155
// this one is special because we don't work without it.
5256
securitycontextconstraints.RegisterDefaulting(plugins)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package rolebindingrestriction
2+
3+
import (
4+
"fmt"
5+
"io"
6+
7+
"k8s.io/apimachinery/pkg/api/validation"
8+
"k8s.io/apimachinery/pkg/runtime"
9+
"k8s.io/apimachinery/pkg/runtime/schema"
10+
"k8s.io/apimachinery/pkg/util/validation/field"
11+
"k8s.io/apiserver/pkg/admission"
12+
13+
authorizationv1 "github.com/openshift/api/authorization/v1"
14+
15+
"github.com/openshift/origin/pkg/admission/customresourcevalidation"
16+
rbrvalidation "github.com/openshift/origin/pkg/admission/customresourcevalidation/rolebindingrestriction/validation"
17+
)
18+
19+
const PluginName = "authorization.openshift.io/ValidateRoleBindingRestriction"
20+
21+
func Register(plugins *admission.Plugins) {
22+
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
23+
return customresourcevalidation.NewValidator(
24+
map[schema.GroupResource]bool{
25+
{Group: authorizationv1.GroupName, Resource: "rolebindingrestrictions"}: true,
26+
},
27+
map[schema.GroupVersionKind]customresourcevalidation.ObjectValidator{
28+
authorizationv1.GroupVersion.WithKind("RoleBindingRestriction"): roleBindingRestrictionV1{},
29+
})
30+
})
31+
}
32+
33+
func toRoleBindingRestriction(uncastObj runtime.Object) (*authorizationv1.RoleBindingRestriction, field.ErrorList) {
34+
if uncastObj == nil {
35+
return nil, nil
36+
}
37+
38+
allErrs := field.ErrorList{}
39+
40+
obj, ok := uncastObj.(*authorizationv1.RoleBindingRestriction)
41+
if !ok {
42+
return nil, append(allErrs,
43+
field.NotSupported(field.NewPath("kind"), fmt.Sprintf("%T", uncastObj), []string{"RoleBindingRestriction"}),
44+
field.NotSupported(field.NewPath("apiVersion"), fmt.Sprintf("%T", uncastObj), []string{authorizationv1.GroupVersion.String()}))
45+
}
46+
47+
return obj, nil
48+
}
49+
50+
type roleBindingRestrictionV1 struct {
51+
}
52+
53+
func (roleBindingRestrictionV1) ValidateCreate(obj runtime.Object) field.ErrorList {
54+
roleBindingRestrictionObj, errs := toRoleBindingRestriction(obj)
55+
if len(errs) > 0 {
56+
return errs
57+
}
58+
59+
errs = append(errs, validation.ValidateObjectMeta(&roleBindingRestrictionObj.ObjectMeta, true, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
60+
errs = append(errs, rbrvalidation.ValidateRoleBindingRestriction(roleBindingRestrictionObj)...)
61+
62+
return errs
63+
}
64+
65+
func (roleBindingRestrictionV1) ValidateUpdate(obj runtime.Object, oldObj runtime.Object) field.ErrorList {
66+
roleBindingRestrictionObj, errs := toRoleBindingRestriction(obj)
67+
if len(errs) > 0 {
68+
return errs
69+
}
70+
roleBindingRestrictionOldObj, errs := toRoleBindingRestriction(oldObj)
71+
if len(errs) > 0 {
72+
return errs
73+
}
74+
75+
errs = append(errs, validation.ValidateObjectMeta(&roleBindingRestrictionObj.ObjectMeta, true, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
76+
errs = append(errs, rbrvalidation.ValidateRoleBindingRestrictionUpdate(roleBindingRestrictionObj, roleBindingRestrictionOldObj)...)
77+
78+
return errs
79+
}
80+
81+
func (r roleBindingRestrictionV1) ValidateStatusUpdate(obj runtime.Object, oldObj runtime.Object) field.ErrorList {
82+
return r.ValidateUpdate(obj, oldObj)
83+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package validation
2+
3+
import (
4+
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
5+
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
6+
"k8s.io/apimachinery/pkg/util/validation/field"
7+
"k8s.io/kubernetes/pkg/apis/core/validation"
8+
9+
authorizationv1 "github.com/openshift/api/authorization/v1"
10+
)
11+
12+
func ValidateRoleBindingRestriction(rbr *authorizationv1.RoleBindingRestriction) field.ErrorList {
13+
allErrs := validation.ValidateObjectMeta(&rbr.ObjectMeta, true,
14+
apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
15+
16+
allErrs = append(allErrs,
17+
ValidateRoleBindingRestrictionSpec(&rbr.Spec, field.NewPath("spec"))...)
18+
19+
return allErrs
20+
}
21+
22+
func ValidateRoleBindingRestrictionUpdate(rbr, old *authorizationv1.RoleBindingRestriction) field.ErrorList {
23+
allErrs := ValidateRoleBindingRestriction(rbr)
24+
25+
allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&rbr.ObjectMeta,
26+
&old.ObjectMeta, field.NewPath("metadata"))...)
27+
28+
return allErrs
29+
}
30+
31+
func ValidateRoleBindingRestrictionSpec(spec *authorizationv1.RoleBindingRestrictionSpec, fld *field.Path) field.ErrorList {
32+
allErrs := field.ErrorList{}
33+
const invalidMsg = `must specify exactly one of userrestriction, grouprestriction, or serviceaccountrestriction`
34+
35+
if spec.UserRestriction != nil {
36+
if spec.GroupRestriction != nil {
37+
allErrs = append(allErrs, field.Invalid(fld.Child("grouprestriction"),
38+
"both userrestriction and grouprestriction specified", invalidMsg))
39+
}
40+
if spec.ServiceAccountRestriction != nil {
41+
allErrs = append(allErrs,
42+
field.Invalid(fld.Child("serviceaccountrestriction"),
43+
"both userrestriction and serviceaccountrestriction specified", invalidMsg))
44+
}
45+
} else if spec.GroupRestriction != nil {
46+
if spec.ServiceAccountRestriction != nil {
47+
allErrs = append(allErrs,
48+
field.Invalid(fld.Child("serviceaccountrestriction"),
49+
"both grouprestriction and serviceaccountrestriction specified", invalidMsg))
50+
}
51+
} else if spec.ServiceAccountRestriction == nil {
52+
allErrs = append(allErrs, field.Required(fld.Child("userrestriction"),
53+
invalidMsg))
54+
}
55+
56+
if spec.UserRestriction != nil {
57+
allErrs = append(allErrs, ValidateRoleBindingRestrictionUser(spec.UserRestriction, fld.Child("userrestriction"))...)
58+
}
59+
if spec.GroupRestriction != nil {
60+
allErrs = append(allErrs, ValidateRoleBindingRestrictionGroup(spec.GroupRestriction, fld.Child("grouprestriction"))...)
61+
}
62+
if spec.ServiceAccountRestriction != nil {
63+
allErrs = append(allErrs, ValidateRoleBindingRestrictionServiceAccount(spec.ServiceAccountRestriction, fld.Child("serviceaccountrestriction"))...)
64+
}
65+
66+
return allErrs
67+
}
68+
69+
func ValidateRoleBindingRestrictionUser(user *authorizationv1.UserRestriction, fld *field.Path) field.ErrorList {
70+
allErrs := field.ErrorList{}
71+
const invalidMsg = `must specify at least one user, group, or label selector`
72+
73+
if !(len(user.Users) > 0 || len(user.Groups) > 0 || len(user.Selectors) > 0) {
74+
allErrs = append(allErrs, field.Required(fld.Child("users"), invalidMsg))
75+
}
76+
77+
for i, selector := range user.Selectors {
78+
allErrs = append(allErrs,
79+
unversionedvalidation.ValidateLabelSelector(&selector,
80+
fld.Child("selector").Index(i))...)
81+
}
82+
83+
return allErrs
84+
}
85+
86+
func ValidateRoleBindingRestrictionGroup(group *authorizationv1.GroupRestriction, fld *field.Path) field.ErrorList {
87+
allErrs := field.ErrorList{}
88+
const invalidMsg = `must specify at least one group or label selector`
89+
90+
if !(len(group.Groups) > 0 || len(group.Selectors) > 0) {
91+
allErrs = append(allErrs, field.Required(fld.Child("groups"), invalidMsg))
92+
}
93+
94+
for i, selector := range group.Selectors {
95+
allErrs = append(allErrs,
96+
unversionedvalidation.ValidateLabelSelector(&selector,
97+
fld.Child("selector").Index(i))...)
98+
}
99+
100+
return allErrs
101+
}
102+
103+
func ValidateRoleBindingRestrictionServiceAccount(sa *authorizationv1.ServiceAccountRestriction, fld *field.Path) field.ErrorList {
104+
allErrs := field.ErrorList{}
105+
const invalidMsg = `must specify at least one service account or namespace`
106+
107+
if !(len(sa.ServiceAccounts) > 0 || len(sa.Namespaces) > 0) {
108+
allErrs = append(allErrs,
109+
field.Required(fld.Child("serviceaccounts"), invalidMsg))
110+
}
111+
112+
return allErrs
113+
}

pkg/authorization/apiserver/apiserver.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (c *completedConfig) newV1RESTStorage() (map[string]rest.Storage, error) {
120120
resourceAccessReviewStorage := resourceaccessreview.NewREST(c.GenericConfig.Authorization.Authorizer, c.ExtraConfig.SubjectLocator)
121121
resourceAccessReviewRegistry := resourceaccessreview.NewRegistry(resourceAccessReviewStorage)
122122
localResourceAccessReviewStorage := localresourceaccessreview.NewREST(resourceAccessReviewRegistry)
123-
roleBindingRestrictionStorage, err := rolebindingrestrictionetcd.NewREST(c.GenericConfig.RESTOptionsGetter)
123+
roleBindingRestrictionStorage, err := rolebindingrestrictionetcd.NewREST()
124124
if err != nil {
125125
return nil, fmt.Errorf("error building REST storage: %v", err)
126126
}

0 commit comments

Comments
 (0)