2016-05-25 21:19:04 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2016 The Kubernetes Authors .
2016-05-25 21:19:04 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package validation
2016-06-30 20:34:22 +00:00
import (
"strings"
2018-04-19 11:57:45 +00:00
rbacv1 "k8s.io/api/rbac/v1"
2016-06-30 20:34:22 +00:00
)
2016-05-25 21:19:04 +00:00
// Covers determines whether or not the ownerRules cover the servantRules in terms of allowed actions.
// It returns whether or not the ownerRules cover and a list of the rules that the ownerRules do not cover.
2018-04-19 11:57:45 +00:00
func Covers ( ownerRules , servantRules [ ] rbacv1 . PolicyRule ) ( bool , [ ] rbacv1 . PolicyRule ) {
2016-05-25 21:19:04 +00:00
// 1. Break every servantRule into individual rule tuples: group, verb, resource, resourceName
// 2. Compare the mini-rules against each owner rule. Because the breakdown is down to the most atomic level, we're guaranteed that each mini-servant rule will be either fully covered or not covered by a single owner rule
// 3. Any left over mini-rules means that we are not covered and we have a nice list of them.
// TODO: it might be nice to collapse the list down into something more human readable
2018-04-19 11:57:45 +00:00
subrules := [ ] rbacv1 . PolicyRule { }
2016-05-25 21:19:04 +00:00
for _ , servantRule := range servantRules {
2017-05-26 01:29:45 +00:00
subrules = append ( subrules , BreakdownRule ( servantRule ) ... )
2016-05-25 21:19:04 +00:00
}
2018-04-19 11:57:45 +00:00
uncoveredRules := [ ] rbacv1 . PolicyRule { }
2016-05-25 21:19:04 +00:00
for _ , subrule := range subrules {
covered := false
for _ , ownerRule := range ownerRules {
if ruleCovers ( ownerRule , subrule ) {
covered = true
break
}
}
if ! covered {
uncoveredRules = append ( uncoveredRules , subrule )
}
}
return ( len ( uncoveredRules ) == 0 ) , uncoveredRules
}
2017-05-26 01:29:45 +00:00
// BreadownRule takes a rule and builds an equivalent list of rules that each have at most one verb, one
2016-05-25 21:19:04 +00:00
// resource, and one resource name
2018-04-19 11:57:45 +00:00
func BreakdownRule ( rule rbacv1 . PolicyRule ) [ ] rbacv1 . PolicyRule {
subrules := [ ] rbacv1 . PolicyRule { }
2016-05-25 21:19:04 +00:00
for _ , group := range rule . APIGroups {
for _ , resource := range rule . Resources {
for _ , verb := range rule . Verbs {
if len ( rule . ResourceNames ) > 0 {
for _ , resourceName := range rule . ResourceNames {
2018-04-19 11:57:45 +00:00
subrules = append ( subrules , rbacv1 . PolicyRule { APIGroups : [ ] string { group } , Resources : [ ] string { resource } , Verbs : [ ] string { verb } , ResourceNames : [ ] string { resourceName } } )
2016-05-25 21:19:04 +00:00
}
} else {
2018-04-19 11:57:45 +00:00
subrules = append ( subrules , rbacv1 . PolicyRule { APIGroups : [ ] string { group } , Resources : [ ] string { resource } , Verbs : [ ] string { verb } } )
2016-05-25 21:19:04 +00:00
}
}
}
}
2016-06-30 18:53:15 +00:00
// Non-resource URLs are unique because they only combine with verbs.
2016-05-25 21:19:04 +00:00
for _ , nonResourceURL := range rule . NonResourceURLs {
2016-06-30 18:53:15 +00:00
for _ , verb := range rule . Verbs {
2018-04-19 11:57:45 +00:00
subrules = append ( subrules , rbacv1 . PolicyRule { NonResourceURLs : [ ] string { nonResourceURL } , Verbs : [ ] string { verb } } )
2016-06-30 18:53:15 +00:00
}
2016-05-25 21:19:04 +00:00
}
return subrules
}
func has ( set [ ] string , ele string ) bool {
for _ , s := range set {
if s == ele {
return true
}
}
return false
}
func hasAll ( set , contains [ ] string ) bool {
owning := make ( map [ string ] struct { } , len ( set ) )
for _ , ele := range set {
owning [ ele ] = struct { } { }
}
for _ , ele := range contains {
if _ , ok := owning [ ele ] ; ! ok {
return false
}
}
return true
}
2017-10-11 14:06:37 +00:00
func resourceCoversAll ( setResources , coversResources [ ] string ) bool {
// if we have a star or an exact match on all resources, then we match
2018-04-19 11:57:45 +00:00
if has ( setResources , rbacv1 . ResourceAll ) || hasAll ( setResources , coversResources ) {
2017-10-11 14:06:37 +00:00
return true
}
for _ , path := range coversResources {
// if we have an exact match, then we match.
if has ( setResources , path ) {
continue
}
// if we're not a subresource, then we definitely don't match. fail.
if ! strings . Contains ( path , "/" ) {
return false
}
tokens := strings . SplitN ( path , "/" , 2 )
resourceToCheck := "*/" + tokens [ 1 ]
if ! has ( setResources , resourceToCheck ) {
return false
}
}
return true
}
2016-06-30 20:34:22 +00:00
func nonResourceURLsCoversAll ( set , covers [ ] string ) bool {
for _ , path := range covers {
covered := false
for _ , owner := range set {
if nonResourceURLCovers ( owner , path ) {
covered = true
break
}
}
if ! covered {
return false
}
}
return true
}
func nonResourceURLCovers ( ownerPath , subPath string ) bool {
if ownerPath == subPath {
return true
}
return strings . HasSuffix ( ownerPath , "*" ) && strings . HasPrefix ( subPath , strings . TrimRight ( ownerPath , "*" ) )
}
2016-05-25 21:19:04 +00:00
// ruleCovers determines whether the ownerRule (which may have multiple verbs, resources, and resourceNames) covers
// the subrule (which may only contain at most one verb, resource, and resourceName)
2018-04-19 11:57:45 +00:00
func ruleCovers ( ownerRule , subRule rbacv1 . PolicyRule ) bool {
verbMatches := has ( ownerRule . Verbs , rbacv1 . VerbAll ) || hasAll ( ownerRule . Verbs , subRule . Verbs )
groupMatches := has ( ownerRule . APIGroups , rbacv1 . APIGroupAll ) || hasAll ( ownerRule . APIGroups , subRule . APIGroups )
2017-10-11 14:06:37 +00:00
resourceMatches := resourceCoversAll ( ownerRule . Resources , subRule . Resources )
2016-06-30 20:34:22 +00:00
nonResourceURLMatches := nonResourceURLsCoversAll ( ownerRule . NonResourceURLs , subRule . NonResourceURLs )
2016-05-25 21:19:04 +00:00
resourceNameMatches := false
if len ( subRule . ResourceNames ) == 0 {
resourceNameMatches = ( len ( ownerRule . ResourceNames ) == 0 )
} else {
resourceNameMatches = ( len ( ownerRule . ResourceNames ) == 0 ) || hasAll ( ownerRule . ResourceNames , subRule . ResourceNames )
}
return verbMatches && groupMatches && resourceMatches && resourceNameMatches && nonResourceURLMatches
}