mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-06 07:57:07 +00:00
Merge pull request #468 from nirmata/345_support_usergroup_info
345 support usergroup info
This commit is contained in:
commit
1beb067c58
31 changed files with 1171 additions and 120 deletions
|
@ -43,6 +43,30 @@ spec:
|
|||
required:
|
||||
- resources
|
||||
properties:
|
||||
roles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
clusterRoles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
subjects:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
apiGroup:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
Namespace:
|
||||
type: string
|
||||
resources:
|
||||
type: object
|
||||
required:
|
||||
|
@ -85,6 +109,30 @@ spec:
|
|||
required:
|
||||
- resources
|
||||
properties:
|
||||
roles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
clusterRoles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
subjects:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
apiGroup:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
Namespace:
|
||||
type: string
|
||||
resources:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -43,6 +43,30 @@ spec:
|
|||
required:
|
||||
- resources
|
||||
properties:
|
||||
roles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
clusterRoles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
subjects:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
apiGroup:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
resources:
|
||||
type: object
|
||||
required:
|
||||
|
@ -85,6 +109,30 @@ spec:
|
|||
required:
|
||||
- resources
|
||||
properties:
|
||||
roles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
clusterRoles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
subjects:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
apiGroup:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
Namespace:
|
||||
type: string
|
||||
resources:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -238,13 +286,4 @@ spec:
|
|||
namespace:
|
||||
type: string
|
||||
creationBlocked:
|
||||
type: boolean
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: init-config
|
||||
namespace: kyverno
|
||||
data:
|
||||
# resource types to be skipped by kyverno policy engine
|
||||
resourceFilters: "[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*]"
|
||||
type: boolean
|
3
main.go
3
main.go
|
@ -144,7 +144,8 @@ func main() {
|
|||
// -- annotations on resources with update details on mutation JSON patches
|
||||
// -- generate policy violation resource
|
||||
// -- generate events on policy and resource
|
||||
server, err := webhooks.NewWebhookServer(pclient, client, tlsPair, pInformer.Kyverno().V1alpha1().ClusterPolicies(), pInformer.Kyverno().V1alpha1().ClusterPolicyViolations(), egen, webhookRegistrationClient, pc.GetPolicyStatusAggregator(), configData, cleanUp)
|
||||
server, err := webhooks.NewWebhookServer(pclient, client, tlsPair, pInformer.Kyverno().V1alpha1().ClusterPolicies(), pInformer.Kyverno().V1alpha1().ClusterPolicyViolations(),
|
||||
kubeInformer.Rbac().V1().RoleBindings(),kubeInformer.Rbac().V1().ClusterRoleBindings(),egen, webhookRegistrationClient, pc.GetPolicyStatusAggregator(), configData, cleanUp)
|
||||
if err != nil {
|
||||
glog.Fatalf("Unable to create webhook server: %v\n", err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
|
@ -63,11 +64,17 @@ type Rule struct {
|
|||
|
||||
//MatchResources contains resource description of the resources that the rule is to apply on
|
||||
type MatchResources struct {
|
||||
Roles []string `json:"roles"`
|
||||
ClusterRoles []string `json:"clusterRoles"`
|
||||
Subjects []rbacv1.Subject `json:"subjects"`
|
||||
ResourceDescription `json:"resources"`
|
||||
}
|
||||
|
||||
//ExcludeResources container resource description of the resources that are to be excluded from the applying the policy rule
|
||||
type ExcludeResources struct {
|
||||
Roles []string `json:"roles"`
|
||||
ClusterRoles []string `json:"clusterRoles"`
|
||||
Subjects []rbacv1.Subject `json:"subjects"`
|
||||
ResourceDescription `json:"resources"`
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,8 @@ limitations under the License.
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
@ -166,6 +167,21 @@ func (in *ClusterPolicyViolationList) DeepCopyObject() runtime.Object {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExcludeResources) DeepCopyInto(out *ExcludeResources) {
|
||||
*out = *in
|
||||
if in.Roles != nil {
|
||||
in, out := &in.Roles, &out.Roles
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ClusterRoles != nil {
|
||||
in, out := &in.ClusterRoles, &out.ClusterRoles
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Subjects != nil {
|
||||
in, out := &in.Subjects, &out.Subjects
|
||||
*out = make([]v1.Subject, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
in.ResourceDescription.DeepCopyInto(&out.ResourceDescription)
|
||||
return
|
||||
}
|
||||
|
@ -209,6 +225,21 @@ func (in *ManagedResourceSpec) DeepCopy() *ManagedResourceSpec {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MatchResources) DeepCopyInto(out *MatchResources) {
|
||||
*out = *in
|
||||
if in.Roles != nil {
|
||||
in, out := &in.Roles, &out.Roles
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ClusterRoles != nil {
|
||||
in, out := &in.ClusterRoles, &out.ClusterRoles
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Subjects != nil {
|
||||
in, out := &in.Subjects, &out.Subjects
|
||||
*out = make([]v1.Subject, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
in.ResourceDescription.DeepCopyInto(&out.ResourceDescription)
|
||||
return
|
||||
}
|
||||
|
@ -348,7 +379,7 @@ func (in *ResourceDescription) DeepCopyInto(out *ResourceDescription) {
|
|||
}
|
||||
if in.Selector != nil {
|
||||
in, out := &in.Selector, &out.Selector
|
||||
*out = new(v1.LabelSelector)
|
||||
*out = new(metav1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
|
|
|
@ -4,13 +4,15 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
// Mutate performs mutation. Overlay first and then mutation patches
|
||||
func Mutate(policy kyverno.ClusterPolicy, resource unstructured.Unstructured) (response EngineResponse) {
|
||||
func Mutate(policyContext PolicyContext) (response EngineResponse) {
|
||||
startTime := time.Now()
|
||||
policy := policyContext.Policy
|
||||
resource := policyContext.Resource
|
||||
|
||||
// policy information
|
||||
func() {
|
||||
// set policy information
|
||||
|
@ -39,6 +41,15 @@ func Mutate(policy kyverno.ClusterPolicy, resource unstructured.Unstructured) (r
|
|||
if !rule.HasMutate() {
|
||||
continue
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
if !matchAdmissionInfo(rule, policyContext.AdmissionInfo) {
|
||||
glog.V(3).Infof("rule '%s' cannot be applied on %s/%s/%s, admission permission: %v",
|
||||
rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), policyContext.AdmissionInfo)
|
||||
continue
|
||||
}
|
||||
glog.V(4).Infof("Time: Mutate matchAdmissionInfo %v", time.Since(startTime))
|
||||
|
||||
// check if the resource satisfies the filter conditions defined in the rule
|
||||
//TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
|
||||
// dont statisfy a policy rule resource description
|
||||
|
@ -53,9 +64,10 @@ func Mutate(policy kyverno.ClusterPolicy, resource unstructured.Unstructured) (r
|
|||
ruleResponse, patchedResource = processOverlay(rule, resource)
|
||||
if ruleResponse.Success == true && ruleResponse.Patches == nil {
|
||||
// overlay pattern does not match the resource conditions
|
||||
glog.Infof(ruleResponse.Message)
|
||||
glog.V(4).Infof(ruleResponse.Message)
|
||||
continue
|
||||
}
|
||||
glog.Infof("Mutate overlay in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
|
||||
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
|
||||
incrementAppliedRuleCount()
|
||||
}
|
||||
|
@ -64,6 +76,7 @@ func Mutate(policy kyverno.ClusterPolicy, resource unstructured.Unstructured) (r
|
|||
if rule.Mutation.Patches != nil {
|
||||
var ruleResponse RuleResponse
|
||||
ruleResponse, patchedResource = processPatches(rule, resource)
|
||||
glog.Infof("Mutate patches in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
|
||||
response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, ruleResponse)
|
||||
incrementAppliedRuleCount()
|
||||
}
|
||||
|
|
|
@ -31,13 +31,6 @@ func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (resp
|
|||
// resource does not satisfy the overlay pattern, we don't apply this rule
|
||||
if !reflect.DeepEqual(overlayerr, overlayError{}) {
|
||||
switch overlayerr.statusCode {
|
||||
// condition key is not present in the resource, don't apply this rule
|
||||
// consider as success
|
||||
case conditionNotPresent:
|
||||
glog.Infof("Resource %s/%s/%s: %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg())
|
||||
response.Success = true
|
||||
response.Message = overlayerr.ErrorMsg()
|
||||
return response, resource
|
||||
// conditions are not met, don't apply this rule
|
||||
// consider as failure
|
||||
case conditionFailure:
|
||||
|
@ -95,13 +88,8 @@ func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (resp
|
|||
|
||||
func processOverlayPatches(resource, overlay interface{}) ([][]byte, overlayError) {
|
||||
if path, overlayerr := meetConditions(resource, overlay); !reflect.DeepEqual(overlayerr, overlayError{}) {
|
||||
switch overlayerr.statusCode {
|
||||
// anchor key does not exist in the resource, skip applying policy
|
||||
case conditionNotPresent:
|
||||
glog.V(4).Infof("Mutate rule: policy not applied: %v at %s", overlayerr, path)
|
||||
return nil, newOverlayError(overlayerr.statusCode, fmt.Sprintf("policy not applied: %v at %s", overlayerr.ErrorMsg(), path))
|
||||
// anchor key is not satisfied in the resource, skip applying policy
|
||||
case conditionFailure:
|
||||
if overlayerr.statusCode == conditionFailure {
|
||||
// anchor key is not satisfied in the resource, skip applying policy
|
||||
glog.V(4).Infof("Mutate rule: failed to validate condition at %s, err: %v", path, overlayerr)
|
||||
return nil, newOverlayError(overlayerr.statusCode, fmt.Sprintf("Conditions are not met at %s, %v", path, overlayerr))
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ func validateConditionAnchorMap(resourceMap, anchors map[string]interface{}, pat
|
|||
}
|
||||
} else {
|
||||
// noAnchorKey doesn't exist in resource
|
||||
return curPath, newOverlayError(conditionNotPresent, fmt.Sprintf("resource field is not present %s", noAnchorKey))
|
||||
continue
|
||||
}
|
||||
}
|
||||
return "", overlayError{}
|
||||
|
|
|
@ -6,7 +6,6 @@ type codeKey int
|
|||
|
||||
const (
|
||||
conditionFailure codeKey = iota
|
||||
conditionNotPresent
|
||||
overlayFailure
|
||||
)
|
||||
|
||||
|
|
26
pkg/engine/policyContext.go
Normal file
26
pkg/engine/policyContext.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
// PolicyContext contains the contexts for engine to process
|
||||
type PolicyContext struct {
|
||||
// policy to be processed
|
||||
Policy kyverno.ClusterPolicy
|
||||
// resource to be processed
|
||||
Resource unstructured.Unstructured
|
||||
AdmissionInfo RequestInfo
|
||||
}
|
||||
|
||||
// RequestInfo contains permission info carried in an admission request
|
||||
type RequestInfo struct {
|
||||
// Roles is a list of possible role send the request
|
||||
Roles []string
|
||||
// ClusterRoles is a list of possible clusterRoles send the request
|
||||
ClusterRoles []string
|
||||
// UserInfo is the userInfo carried in the admission request
|
||||
AdmissionUserInfo authenticationv1.UserInfo
|
||||
}
|
116
pkg/engine/rbacValidation.go
Normal file
116
pkg/engine/rbacValidation.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
||||
utils "github.com/nirmata/kyverno/pkg/utils"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
SaPrefix = "system:serviceaccount:"
|
||||
)
|
||||
|
||||
// matchAdmissionInfo return true if the rule can be applied to the request
|
||||
func matchAdmissionInfo(rule kyverno.Rule, requestInfo RequestInfo) bool {
|
||||
// when processing existing resource, it does not contain requestInfo
|
||||
// skip permission checking
|
||||
if reflect.DeepEqual(requestInfo, RequestInfo{}) {
|
||||
return true
|
||||
}
|
||||
|
||||
if !validateMatch(rule.MatchResources, requestInfo) {
|
||||
return false
|
||||
}
|
||||
|
||||
return validateExclude(rule.ExcludeResources, requestInfo)
|
||||
}
|
||||
|
||||
// match:
|
||||
// roles: role1, role2
|
||||
// clusterRoles: clusterRole1,clusterRole2
|
||||
// subjects: subject1, subject2
|
||||
// validateMatch return true if (role1 || role2) and (clusterRole1 || clusterRole2)
|
||||
// and (subject1 || subject2) are found in requestInfo, OR operation for each list
|
||||
func validateMatch(match kyverno.MatchResources, requestInfo RequestInfo) bool {
|
||||
if len(match.Roles) > 0 {
|
||||
if !matchRoleRefs(match.Roles, requestInfo.Roles) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(match.ClusterRoles) > 0 {
|
||||
if !matchRoleRefs(match.ClusterRoles, requestInfo.ClusterRoles) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(match.Subjects) > 0 {
|
||||
if !matchSubjects(match.Subjects, requestInfo.AdmissionUserInfo) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// exclude:
|
||||
// roles: role1, role2
|
||||
// clusterRoles: clusterRole1,clusterRole2
|
||||
// subjects: subject1, subject2
|
||||
// validateExclude return true if none of the above found in requestInfo
|
||||
// otherwise return false immediately means rule should not be applied
|
||||
func validateExclude(exclude kyverno.ExcludeResources, requestInfo RequestInfo) bool {
|
||||
if len(exclude.Roles) > 0 {
|
||||
if matchRoleRefs(exclude.Roles, requestInfo.Roles) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(exclude.ClusterRoles) > 0 {
|
||||
if matchRoleRefs(exclude.ClusterRoles, requestInfo.ClusterRoles) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(exclude.Subjects) > 0 {
|
||||
if matchSubjects(exclude.Subjects, requestInfo.AdmissionUserInfo) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// matchRoleRefs return true if one of ruleRoleRefs exist in resourceRoleRefs
|
||||
func matchRoleRefs(ruleRoleRefs, resourceRoleRefs []string) bool {
|
||||
for _, ruleRoleRef := range ruleRoleRefs {
|
||||
if utils.ContainsString(resourceRoleRefs, ruleRoleRef) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matchSubjects return true if one of ruleSubjects exist in userInfo
|
||||
func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.UserInfo) bool {
|
||||
userGroups := append(userInfo.Groups, userInfo.Username)
|
||||
for _, subject := range ruleSubjects {
|
||||
switch subject.Kind {
|
||||
case "ServiceAccount":
|
||||
if len(userInfo.Username) <= len(SaPrefix) {
|
||||
continue
|
||||
}
|
||||
subjectServiceAccount := subject.Namespace + ":" + subject.Name
|
||||
if userInfo.Username[len(SaPrefix):] == subjectServiceAccount {
|
||||
return true
|
||||
}
|
||||
case "User", "Group":
|
||||
if utils.ContainsString(userGroups, subject.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
287
pkg/engine/rbacValidation_test.go
Normal file
287
pkg/engine/rbacValidation_test.go
Normal file
|
@ -0,0 +1,287 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
||||
"gotest.tools/assert"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
)
|
||||
|
||||
func Test_matchAdmissionInfo(t *testing.T) {
|
||||
flag.Parse()
|
||||
flag.Set("logtostderr", "true")
|
||||
flag.Set("v", "3")
|
||||
tests := []struct {
|
||||
rule kyverno.Rule
|
||||
info RequestInfo
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
rule: kyverno.Rule{
|
||||
MatchResources: kyverno.MatchResources{},
|
||||
},
|
||||
info: RequestInfo{},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rule: kyverno.Rule{
|
||||
MatchResources: kyverno.MatchResources{
|
||||
Roles: []string{"ns-a:role-a"},
|
||||
},
|
||||
},
|
||||
info: RequestInfo{
|
||||
Roles: []string{"ns-a:role-a"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rule: kyverno.Rule{
|
||||
MatchResources: kyverno.MatchResources{
|
||||
Roles: []string{"ns-a:role-a"},
|
||||
},
|
||||
},
|
||||
info: RequestInfo{
|
||||
Roles: []string{"ns-a:role"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rule: kyverno.Rule{
|
||||
MatchResources: kyverno.MatchResources{
|
||||
Subjects: testSubjects(),
|
||||
},
|
||||
},
|
||||
info: RequestInfo{
|
||||
AdmissionUserInfo: authenticationv1.UserInfo{
|
||||
Username: "serviceaccount:mynamespace:mysa",
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rule: kyverno.Rule{
|
||||
ExcludeResources: kyverno.ExcludeResources{
|
||||
Subjects: testSubjects(),
|
||||
},
|
||||
},
|
||||
info: RequestInfo{
|
||||
AdmissionUserInfo: authenticationv1.UserInfo{
|
||||
UID: "1",
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rule: kyverno.Rule{
|
||||
ExcludeResources: kyverno.ExcludeResources{
|
||||
Subjects: testSubjects(),
|
||||
},
|
||||
},
|
||||
info: RequestInfo{
|
||||
AdmissionUserInfo: authenticationv1.UserInfo{
|
||||
Username: "kubernetes-admin",
|
||||
Groups: []string{"system:masters", "system:authenticated"},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
assert.Assert(t, test.expected == matchAdmissionInfo(test.rule, test.info))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateMatch(t *testing.T) {
|
||||
requestInfo := []struct {
|
||||
info RequestInfo
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
info: RequestInfo{
|
||||
Roles: []string{},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
info: RequestInfo{
|
||||
Roles: []string{"ns-b:role-b"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
info: RequestInfo{
|
||||
Roles: []string{"ns:role"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
matchRoles := kyverno.MatchResources{
|
||||
Roles: []string{"ns-a:role-a", "ns-b:role-b"},
|
||||
}
|
||||
|
||||
for _, info := range requestInfo {
|
||||
assert.Assert(t, info.expected == validateMatch(matchRoles, info.info))
|
||||
}
|
||||
|
||||
requestInfo = []struct {
|
||||
info RequestInfo
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
info: RequestInfo{
|
||||
ClusterRoles: []string{},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
info: RequestInfo{
|
||||
ClusterRoles: []string{"role-b"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
info: RequestInfo{
|
||||
ClusterRoles: []string{"clusterrole-b"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
info: RequestInfo{
|
||||
ClusterRoles: []string{"clusterrole-a", "clusterrole-b"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
info: RequestInfo{
|
||||
ClusterRoles: []string{"fake-a", "fake-b"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
matchClusterRoles := kyverno.MatchResources{
|
||||
ClusterRoles: []string{"clusterrole-a", "clusterrole-b"},
|
||||
}
|
||||
|
||||
for _, info := range requestInfo {
|
||||
assert.Assert(t, info.expected == validateMatch(matchClusterRoles, info.info))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateExclude(t *testing.T) {
|
||||
requestInfo := []struct {
|
||||
info RequestInfo
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
info: RequestInfo{
|
||||
Roles: []string{},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
info: RequestInfo{
|
||||
Roles: []string{"ns-b:role-b"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
info: RequestInfo{
|
||||
Roles: []string{"ns:role"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
excludeRoles := kyverno.ExcludeResources{
|
||||
Roles: []string{"ns-a:role-a", "ns-b:role-b"},
|
||||
}
|
||||
|
||||
for _, info := range requestInfo {
|
||||
assert.Assert(t, info.expected == validateExclude(excludeRoles, info.info))
|
||||
}
|
||||
|
||||
requestInfo = []struct {
|
||||
info RequestInfo
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
info: RequestInfo{
|
||||
ClusterRoles: []string{},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
info: RequestInfo{
|
||||
ClusterRoles: []string{"role-b"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
info: RequestInfo{
|
||||
ClusterRoles: []string{"clusterrole-b"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
info: RequestInfo{
|
||||
ClusterRoles: []string{"fake-a", "fake-b"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
excludeClusterRoles := kyverno.ExcludeResources{
|
||||
ClusterRoles: []string{"clusterrole-a", "clusterrole-b"},
|
||||
}
|
||||
|
||||
for _, info := range requestInfo {
|
||||
assert.Assert(t, info.expected == validateExclude(excludeClusterRoles, info.info))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_matchSubjects(t *testing.T) {
|
||||
group := authenticationv1.UserInfo{
|
||||
Username: "kubernetes-admin",
|
||||
Groups: []string{"system:masters", "system:authenticated"},
|
||||
}
|
||||
|
||||
sa := authenticationv1.UserInfo{
|
||||
Username: "system:serviceaccount:mynamespace:mysa",
|
||||
Groups: []string{"system:serviceaccounts", "system:serviceaccounts:mynamespace", "system:authenticated"},
|
||||
}
|
||||
|
||||
user := authenticationv1.UserInfo{
|
||||
Username: "system:kube-scheduler",
|
||||
Groups: []string{"system:authenticated"},
|
||||
}
|
||||
|
||||
subjects := testSubjects()
|
||||
|
||||
assert.Assert(t, matchSubjects(subjects, sa))
|
||||
assert.Assert(t, !matchSubjects(subjects, user))
|
||||
assert.Assert(t, matchSubjects(subjects, group))
|
||||
}
|
||||
|
||||
func testSubjects() []rbacv1.Subject {
|
||||
return []rbacv1.Subject{
|
||||
{
|
||||
Kind: "User",
|
||||
Name: "kube-scheduler",
|
||||
},
|
||||
{
|
||||
Kind: "Group",
|
||||
Name: "system:masters",
|
||||
},
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: "mysa",
|
||||
Namespace: "mynamespace",
|
||||
},
|
||||
}
|
||||
}
|
|
@ -16,8 +16,11 @@ import (
|
|||
)
|
||||
|
||||
//Validate applies validation rules from policy on the resource
|
||||
func Validate(policy kyverno.ClusterPolicy, resource unstructured.Unstructured) (response EngineResponse) {
|
||||
func Validate(policyContext PolicyContext) (response EngineResponse) {
|
||||
startTime := time.Now()
|
||||
policy := policyContext.Policy
|
||||
resource := policyContext.Resource
|
||||
|
||||
// policy information
|
||||
func() {
|
||||
// set policy information
|
||||
|
@ -45,6 +48,15 @@ func Validate(policy kyverno.ClusterPolicy, resource unstructured.Unstructured)
|
|||
if !rule.HasValidate() {
|
||||
continue
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
if !matchAdmissionInfo(rule, policyContext.AdmissionInfo) {
|
||||
glog.V(3).Infof("rule '%s' cannot be applied on %s/%s/%s, admission permission: %v",
|
||||
rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), policyContext.AdmissionInfo)
|
||||
continue
|
||||
}
|
||||
glog.V(4).Infof("Time: Validate matchAdmissionInfo %v", time.Since(startTime))
|
||||
|
||||
// check if the resource satisfies the filter conditions defined in the rule
|
||||
// TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
|
||||
// dont statisfy a policy rule resource description
|
||||
|
|
|
@ -1628,7 +1628,8 @@ func TestValidate_ServiceTest(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
assert.Assert(t, len(er.PolicyResponse.Rules) == 0)
|
||||
}
|
||||
|
||||
|
@ -1725,7 +1726,7 @@ func TestValidate_MapHasFloats(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
assert.Assert(t, len(er.PolicyResponse.Rules) == 0)
|
||||
}
|
||||
|
||||
|
@ -1820,7 +1821,7 @@ func TestValidate_image_tag_fail(t *testing.T) {
|
|||
"Validation rule 'validate-tag' succeeded.",
|
||||
"Validation error: imagePullPolicy 'Always' required with tag 'latest'\nValidation rule 'validate-latest' failed at path '/spec/containers/0/imagePullPolicy/'.",
|
||||
}
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
assert.Equal(t, r.Message, msgs[index])
|
||||
}
|
||||
|
@ -1918,7 +1919,7 @@ func TestValidate_image_tag_pass(t *testing.T) {
|
|||
"Validation rule 'validate-tag' succeeded.",
|
||||
"Validation rule 'validate-latest' succeeded.",
|
||||
}
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
assert.Equal(t, r.Message, msgs[index])
|
||||
}
|
||||
|
@ -1991,7 +1992,7 @@ func TestValidate_Fail_anyPattern(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
msgs := []string{"Validation error: A namespace is required\nValidation rule check-default-namespace anyPattern[0] failed at path /metadata/namespace/.\nValidation rule check-default-namespace anyPattern[1] failed at path /metadata/namespace/."}
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
assert.Equal(t, r.Message, msgs[index])
|
||||
|
@ -2072,7 +2073,7 @@ func TestValidate_host_network_port(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
msgs := []string{"Validation error: Host network and port are not allowed\nValidation rule 'validate-host-network-port' failed at path '/spec/containers/0/ports/0/hostPort/'."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
|
@ -2161,7 +2162,7 @@ func TestValidate_anchor_arraymap_pass(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
msgs := []string{"Validation rule 'validate-host-path' succeeded."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
|
@ -2249,7 +2250,7 @@ func TestValidate_anchor_arraymap_fail(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
msgs := []string{"Validation error: Host path '/var/lib/' is not allowed\nValidation rule 'validate-host-path' failed at path '/spec/volumes/0/hostPath/path/'."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
|
@ -2318,7 +2319,7 @@ func TestValidate_anchor_map_notfound(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
msgs := []string{"Validation rule 'pod rule 2' succeeded."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
|
@ -2390,7 +2391,7 @@ func TestValidate_anchor_map_found_valid(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
msgs := []string{"Validation rule 'pod rule 2' succeeded."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
|
@ -2462,7 +2463,7 @@ func TestValidate_anchor_map_found_invalid(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
msgs := []string{"Validation error: pod: validate run as non root user\nValidation rule 'pod rule 2' failed at path '/spec/securityContext/runAsNonRoot/'."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
|
@ -2536,7 +2537,7 @@ func TestValidate_AnchorList_pass(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
msgs := []string{"Validation rule 'pod image rule' succeeded."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
|
@ -2610,7 +2611,7 @@ func TestValidate_AnchorList_fail(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
// msgs := []string{"Validation rule 'pod image rule' failed at '/spec/containers/1/name/' for resource Pod//myapp-pod."}
|
||||
// for index, r := range er.PolicyResponse.Rules {
|
||||
// // t.Log(r.Message)
|
||||
|
@ -2684,7 +2685,7 @@ func TestValidate_existenceAnchor_fail(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
// msgs := []string{"Validation rule 'pod image rule' failed at '/spec/containers/' for resource Pod//myapp-pod."}
|
||||
|
||||
// for index, r := range er.PolicyResponse.Rules {
|
||||
|
@ -2759,7 +2760,7 @@ func TestValidate_existenceAnchor_pass(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
msgs := []string{"Validation rule 'pod image rule' succeeded."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
|
@ -2846,7 +2847,7 @@ func TestValidate_negationAnchor_deny(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
msgs := []string{"Validation error: Host path is not allowed\nValidation rule 'validate-host-path' failed at path '/spec/volumes/0/hostPath/'."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
|
@ -2932,7 +2933,7 @@ func TestValidate_negationAnchor_pass(t *testing.T) {
|
|||
|
||||
resourceUnstructured, err := ConvertToUnstructured(rawResource)
|
||||
assert.NilError(t, err)
|
||||
er := Validate(policy, *resourceUnstructured)
|
||||
er := Validate(PolicyContext{Policy: policy, Resource: *resourceUnstructured})
|
||||
msgs := []string{"Validation rule 'validate-host-path' succeeded."}
|
||||
|
||||
for index, r := range er.PolicyResponse.Rules {
|
||||
|
|
|
@ -105,7 +105,7 @@ func applyPolicyOnRaw(policy *kyverno.ClusterPolicy, rawResource []byte, gvk *me
|
|||
}
|
||||
//TODO check if the kind information is present resource
|
||||
// Process Mutation
|
||||
engineResponse := engine.Mutate(*policy, *resource)
|
||||
engineResponse := engine.Mutate(engine.PolicyContext{Policy: *policy, Resource: *resource})
|
||||
if !engineResponse.IsSuccesful() {
|
||||
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns)
|
||||
for _, r := range engineResponse.PolicyResponse.Rules {
|
||||
|
@ -115,7 +115,7 @@ func applyPolicyOnRaw(policy *kyverno.ClusterPolicy, rawResource []byte, gvk *me
|
|||
glog.Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, gvk.Kind, rname, rns)
|
||||
|
||||
// Process Validation
|
||||
engineResponse := engine.Validate(*policy, *resource)
|
||||
engineResponse := engine.Validate(engine.PolicyContext{Policy: *policy, Resource: *resource})
|
||||
|
||||
if !engineResponse.IsSuccesful() {
|
||||
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns)
|
||||
|
|
|
@ -41,6 +41,10 @@ func (nsl *NamespaceLister) ListResources(selector labels.Selector) (ret []*v1.N
|
|||
//GetResource is a wrapper to get the resource and inject the GVK
|
||||
func (nsl *NamespaceLister) GetResource(name string) (*v1.Namespace, error) {
|
||||
namespace, err := nsl.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
namespace.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("Namespace"))
|
||||
return namespace, err
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
|
|||
sendStat(false)
|
||||
|
||||
//VALIDATION
|
||||
engineResponse = engine.Validate(policy, resource)
|
||||
engineResponse = engine.Validate(engine.PolicyContext{Policy: policy, Resource: resource})
|
||||
engineResponses = append(engineResponses, engineResponse)
|
||||
// gather stats
|
||||
gatherStat(policy.Name, engineResponse.PolicyResponse)
|
||||
|
@ -80,7 +80,7 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
|
|||
return engineResponses
|
||||
}
|
||||
func mutation(policy kyverno.ClusterPolicy, resource unstructured.Unstructured, policyStatus PolicyStatusInterface) (engine.EngineResponse, error) {
|
||||
engineResponse := engine.Mutate(policy, resource)
|
||||
engineResponse := engine.Mutate(engine.PolicyContext{Policy: policy, Resource: resource})
|
||||
if !engineResponse.IsSuccesful() {
|
||||
glog.V(4).Infof("mutation had errors reporting them")
|
||||
return engineResponse, nil
|
||||
|
|
|
@ -148,7 +148,7 @@ func runTestCase(t *testing.T, tc scaseT) bool {
|
|||
|
||||
var er engine.EngineResponse
|
||||
|
||||
er = engine.Mutate(*policy, *resource)
|
||||
er = engine.Mutate(engine.PolicyContext{Policy: *policy, Resource: *resource})
|
||||
t.Log("---Mutation---")
|
||||
validateResource(t, er.PatchedResource, tc.Expected.Mutation.PatchedResource)
|
||||
validateResponse(t, er.PolicyResponse, tc.Expected.Mutation.PolicyResponse)
|
||||
|
@ -158,7 +158,7 @@ func runTestCase(t *testing.T, tc scaseT) bool {
|
|||
resource = &er.PatchedResource
|
||||
}
|
||||
|
||||
er = engine.Validate(*policy, *resource)
|
||||
er = engine.Validate(engine.PolicyContext{Policy: *policy, Resource: *resource})
|
||||
t.Log("---Validation---")
|
||||
validateResponse(t, er.PolicyResponse, tc.Expected.Validation.PolicyResponse)
|
||||
|
||||
|
|
124
pkg/userinfo/roleRef.go
Normal file
124
pkg/userinfo/roleRef.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package userinfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
engine "github.com/nirmata/kyverno/pkg/engine"
|
||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
rbaclister "k8s.io/client-go/listers/rbac/v1"
|
||||
)
|
||||
|
||||
func GetRoleRef(rbLister rbaclister.RoleBindingLister, crbLister rbaclister.ClusterRoleBindingLister, request *v1beta1.AdmissionRequest) (roles []string, clusterRoles []string, err error) {
|
||||
// rolebindings
|
||||
roleBindings, err := rbLister.List(labels.NewSelector())
|
||||
if err != nil {
|
||||
return roles, clusterRoles, fmt.Errorf("failed to list rolebindings: %v", err)
|
||||
}
|
||||
|
||||
rs, crs, err := getRoleRefByRoleBindings(roleBindings, request.UserInfo)
|
||||
if err != nil {
|
||||
return roles, clusterRoles, err
|
||||
}
|
||||
roles = append(roles, rs...)
|
||||
clusterRoles = append(clusterRoles, crs...)
|
||||
|
||||
// clusterrolebindings
|
||||
clusterroleBindings, err := crbLister.List(labels.NewSelector())
|
||||
if err != nil {
|
||||
return roles, clusterRoles, fmt.Errorf("failed to list clusterrolebindings: %v", err)
|
||||
}
|
||||
|
||||
crs, err = getRoleRefByClusterRoleBindings(clusterroleBindings, request.UserInfo)
|
||||
if err != nil {
|
||||
return roles, clusterRoles, err
|
||||
}
|
||||
clusterRoles = append(clusterRoles, crs...)
|
||||
|
||||
return roles, clusterRoles, nil
|
||||
}
|
||||
|
||||
func getRoleRefByRoleBindings(roleBindings []*rbacv1.RoleBinding, userInfo authenticationv1.UserInfo) (roles []string, clusterRoles []string, err error) {
|
||||
for _, rolebinding := range roleBindings {
|
||||
for _, subject := range rolebinding.Subjects {
|
||||
if !matchSubjectsMap(subject, userInfo) {
|
||||
continue
|
||||
}
|
||||
|
||||
// roleRefMap := roleRef.(map[string]interface{})
|
||||
switch rolebinding.RoleRef.Kind {
|
||||
case "role":
|
||||
roles = append(roles, rolebinding.Namespace+":"+rolebinding.RoleRef.Name)
|
||||
case "clusterRole":
|
||||
clusterRoles = append(clusterRoles, rolebinding.RoleRef.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return roles, clusterRoles, nil
|
||||
}
|
||||
|
||||
// RoleRef in ClusterRoleBindings can only reference a ClusterRole in the global namespace
|
||||
func getRoleRefByClusterRoleBindings(clusterroleBindings []*rbacv1.ClusterRoleBinding, userInfo authenticationv1.UserInfo) (clusterRoles []string, err error) {
|
||||
for _, clusterRoleBinding := range clusterroleBindings {
|
||||
for _, subject := range clusterRoleBinding.Subjects {
|
||||
if !matchSubjectsMap(subject, userInfo) {
|
||||
continue
|
||||
}
|
||||
|
||||
if clusterRoleBinding.RoleRef.Kind == "clusterRole" {
|
||||
clusterRoles = append(clusterRoles, clusterRoleBinding.RoleRef.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return clusterRoles, nil
|
||||
}
|
||||
|
||||
// matchSubjectsMap checks if userInfo found in subject
|
||||
// return true directly if found a match
|
||||
// subject["kind"] can only be ServiceAccount, User and Group
|
||||
func matchSubjectsMap(subject rbacv1.Subject, userInfo authenticationv1.UserInfo) bool {
|
||||
// ServiceAccount
|
||||
if isServiceaccountUserInfo(userInfo.Username) {
|
||||
return matchServiceAccount(subject, userInfo)
|
||||
}
|
||||
|
||||
// User or Group
|
||||
return matchUserOrGroup(subject, userInfo)
|
||||
}
|
||||
|
||||
func isServiceaccountUserInfo(username string) bool {
|
||||
if strings.Contains(username, engine.SaPrefix) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matchServiceAccount checks if userInfo sa matche the subject sa
|
||||
// serviceaccount represents as saPrefix:namespace:name in userInfo
|
||||
func matchServiceAccount(subject rbacv1.Subject, userInfo authenticationv1.UserInfo) bool {
|
||||
subjectServiceAccount := subject.Namespace + ":" + subject.Name
|
||||
if userInfo.Username[len(engine.SaPrefix):] != subjectServiceAccount {
|
||||
glog.V(3).Infof("service account not match, expect %s, got %s", subjectServiceAccount, userInfo.Username[len(engine.SaPrefix):])
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// matchUserOrGroup checks if userInfo contains user or group info in a subject
|
||||
func matchUserOrGroup(subject rbacv1.Subject, userInfo authenticationv1.UserInfo) bool {
|
||||
keys := append(userInfo.Groups, userInfo.Username)
|
||||
for _, key := range keys {
|
||||
if subject.Name == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(3).Infof("user/group '%v' info not found in request userInfo: %v", subject.Name, keys)
|
||||
return false
|
||||
}
|
275
pkg/userinfo/roleRef_test.go
Normal file
275
pkg/userinfo/roleRef_test.go
Normal file
|
@ -0,0 +1,275 @@
|
|||
package userinfo
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func Test_isServiceaccountUserInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
username string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
username: "system:serviceaccount:default:saconfig",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
username: "serviceaccount:default:saconfig",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
res := isServiceaccountUserInfo(test.username)
|
||||
assert.Assert(t, test.expected == res)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_matchServiceAccount_subject_variants(t *testing.T) {
|
||||
userInfo := authenticationv1.UserInfo{
|
||||
Username: "system:serviceaccount:default:saconfig",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
subject rbacv1.Subject
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
subject: rbacv1.Subject{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
subject: rbacv1.Subject{
|
||||
Kind: "serviceaccount",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
subject: rbacv1.Subject{
|
||||
Kind: "ServiceAccount",
|
||||
Namespace: "testnamespace",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
subject: rbacv1.Subject{
|
||||
Kind: "ServiceAccount",
|
||||
Namespace: "1",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
subject: rbacv1.Subject{
|
||||
Kind: "ServiceAccount",
|
||||
Namespace: "testnamespace",
|
||||
Name: "",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
subject: rbacv1.Subject{
|
||||
Kind: "ServiceAccount",
|
||||
Namespace: "testnamespace",
|
||||
Name: "testname",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
res := matchServiceAccount(test.subject, userInfo)
|
||||
assert.Assert(t, test.expected == res)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_matchUserOrGroup(t *testing.T) {
|
||||
group := authenticationv1.UserInfo{
|
||||
Username: "kubernetes-admin",
|
||||
Groups: []string{"system:masters", "system:authenticated"},
|
||||
}
|
||||
|
||||
sa := authenticationv1.UserInfo{
|
||||
Username: "system:serviceaccount:kube-system:deployment-controller",
|
||||
Groups: []string{"system:serviceaccounts", "system:serviceaccounts:kube-system", "system:authenticated"},
|
||||
}
|
||||
|
||||
user := authenticationv1.UserInfo{
|
||||
Username: "system:kube-scheduler",
|
||||
Groups: []string{"system:authenticated"},
|
||||
}
|
||||
|
||||
userContext := rbacv1.Subject{
|
||||
Kind: "User",
|
||||
Name: "system:kube-scheduler",
|
||||
}
|
||||
|
||||
groupContext := rbacv1.Subject{
|
||||
Kind: "Group",
|
||||
Name: "system:masters",
|
||||
}
|
||||
|
||||
fakegroupContext := rbacv1.Subject{
|
||||
Kind: "Group",
|
||||
Name: "fakeGroup",
|
||||
}
|
||||
|
||||
res := matchUserOrGroup(userContext, user)
|
||||
assert.Assert(t, res)
|
||||
|
||||
res = matchUserOrGroup(groupContext, group)
|
||||
assert.Assert(t, res)
|
||||
|
||||
res = matchUserOrGroup(groupContext, sa)
|
||||
assert.Assert(t, !res)
|
||||
|
||||
res = matchUserOrGroup(fakegroupContext, group)
|
||||
assert.Assert(t, !res)
|
||||
}
|
||||
|
||||
func Test_matchSubjectsMap(t *testing.T) {
|
||||
sa := authenticationv1.UserInfo{
|
||||
Username: "system:serviceaccount:default:saconfig",
|
||||
}
|
||||
|
||||
group := authenticationv1.UserInfo{
|
||||
Username: "kubernetes-admin",
|
||||
Groups: []string{"system:masters", "system:authenticated"},
|
||||
}
|
||||
|
||||
sasubject := rbacv1.Subject{
|
||||
Kind: "ServiceAccount",
|
||||
Namespace: "default",
|
||||
Name: "saconfig",
|
||||
}
|
||||
|
||||
groupsubject := rbacv1.Subject{
|
||||
Kind: "Group",
|
||||
Name: "fakeGroup",
|
||||
}
|
||||
|
||||
res := matchSubjectsMap(sasubject, sa)
|
||||
assert.Assert(t, res)
|
||||
|
||||
res = matchSubjectsMap(groupsubject, group)
|
||||
assert.Assert(t, !res)
|
||||
}
|
||||
|
||||
func Test_getRoleRefByRoleBindings(t *testing.T) {
|
||||
flag.Parse()
|
||||
flag.Set("logtostderr", "true")
|
||||
flag.Set("v", "3")
|
||||
|
||||
list := []*rbacv1.RoleBinding{
|
||||
&rbacv1.RoleBinding{
|
||||
metav1.TypeMeta{
|
||||
Kind: "RoleBinding",
|
||||
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||
},
|
||||
metav1.ObjectMeta{Name: "test1", Namespace: "mynamespace"},
|
||||
[]rbacv1.Subject{
|
||||
rbacv1.Subject{
|
||||
Kind: "ServiceAccount",
|
||||
Name: "saconfig",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
rbacv1.RoleRef{
|
||||
Kind: "role",
|
||||
Name: "myrole",
|
||||
},
|
||||
},
|
||||
&rbacv1.RoleBinding{
|
||||
metav1.TypeMeta{
|
||||
Kind: "RoleBinding",
|
||||
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||
},
|
||||
metav1.ObjectMeta{Name: "test2", Namespace: "mynamespace"},
|
||||
[]rbacv1.Subject{
|
||||
rbacv1.Subject{
|
||||
Kind: "ServiceAccount",
|
||||
Name: "saconfig",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
rbacv1.RoleRef{
|
||||
Kind: "clusterRole",
|
||||
Name: "myclusterrole",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sa := authenticationv1.UserInfo{
|
||||
Username: "system:serviceaccount:default:saconfig",
|
||||
}
|
||||
|
||||
expectedrole := []string{"mynamespace:myrole"}
|
||||
expectedClusterRole := []string{"myclusterrole"}
|
||||
roles, clusterroles, err := getRoleRefByRoleBindings(list, sa)
|
||||
assert.Assert(t, err == nil)
|
||||
assert.Assert(t, reflect.DeepEqual(roles, expectedrole))
|
||||
assert.Assert(t, reflect.DeepEqual(clusterroles, expectedClusterRole))
|
||||
}
|
||||
|
||||
func Test_getRoleRefByClusterRoleBindings(t *testing.T) {
|
||||
list := []*rbacv1.ClusterRoleBinding{
|
||||
&rbacv1.ClusterRoleBinding{
|
||||
metav1.TypeMeta{
|
||||
Kind: "ClusterRoleBinding",
|
||||
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||
},
|
||||
metav1.ObjectMeta{Name: "test1", Namespace: "mynamespace"},
|
||||
[]rbacv1.Subject{
|
||||
rbacv1.Subject{
|
||||
Kind: "User",
|
||||
Name: "kube-scheduler",
|
||||
},
|
||||
},
|
||||
rbacv1.RoleRef{
|
||||
Kind: "clusterRole",
|
||||
Name: "fakeclusterrole",
|
||||
},
|
||||
},
|
||||
&rbacv1.ClusterRoleBinding{
|
||||
metav1.TypeMeta{
|
||||
Kind: "ClusterRoleBinding",
|
||||
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||
},
|
||||
metav1.ObjectMeta{Name: "test2", Namespace: "mynamespace"},
|
||||
[]rbacv1.Subject{
|
||||
rbacv1.Subject{
|
||||
Kind: "Group",
|
||||
Name: "system:masters",
|
||||
},
|
||||
},
|
||||
rbacv1.RoleRef{
|
||||
Kind: "clusterRole",
|
||||
Name: "myclusterrole",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
group := authenticationv1.UserInfo{
|
||||
Username: "kubernetes-admin",
|
||||
Groups: []string{"system:masters", "system:authenticated"},
|
||||
}
|
||||
|
||||
user := authenticationv1.UserInfo{
|
||||
Username: "system:kube-scheduler",
|
||||
Groups: []string{"system:authenticated"},
|
||||
}
|
||||
|
||||
clusterroles, err := getRoleRefByClusterRoleBindings(list, group)
|
||||
assert.Assert(t, err == nil)
|
||||
assert.Assert(t, reflect.DeepEqual(clusterroles, []string{"myclusterrole"}))
|
||||
|
||||
clusterroles, err = getRoleRefByClusterRoleBindings(list, user)
|
||||
assert.Assert(t, err == nil)
|
||||
assert.Assert(t, len(clusterroles) == 0)
|
||||
}
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
policyAnnotation = "policies.kyverno.io/patches"
|
||||
policyAnnotation = "policies.kyverno.patches"
|
||||
)
|
||||
|
||||
type policyPatch struct {
|
||||
|
@ -29,7 +29,12 @@ type response struct {
|
|||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
func generateAnnotationPatches(annotations map[string]string, engineResponses []engine.EngineResponse) []byte {
|
||||
func generateAnnotationPatches(engineResponses []engine.EngineResponse) []byte {
|
||||
var annotations map[string]string
|
||||
if len(engineResponses) > 0 {
|
||||
annotations = engineResponses[0].PatchedResource.GetAnnotations()
|
||||
}
|
||||
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
|
@ -41,15 +46,30 @@ func generateAnnotationPatches(annotations map[string]string, engineResponses []
|
|||
return nil
|
||||
}
|
||||
|
||||
// Kyverno uses jsonpath to patch obejct
|
||||
// since policyAnnotation=policies.kyverno.io/patches contains "/"
|
||||
// the operation here should always be "add"
|
||||
// otherwise the key when patching will be "patches" which is missing
|
||||
annotations[policyAnnotation] = string(value)
|
||||
patchResponse = response{
|
||||
Op: "add",
|
||||
Path: "/metadata/annotations",
|
||||
Value: annotations,
|
||||
if _, ok := annotations[policyAnnotation]; ok {
|
||||
// create update patch string
|
||||
patchResponse = response{
|
||||
Op: "replace",
|
||||
Path: "/metadata/annotations/" + policyAnnotation,
|
||||
Value: string(value),
|
||||
}
|
||||
} else {
|
||||
// mutate rule has annotation patches
|
||||
if len(annotations) > 0 {
|
||||
patchResponse = response{
|
||||
Op: "add",
|
||||
Path: "/metadata/annotations/" + policyAnnotation,
|
||||
Value: string(value),
|
||||
}
|
||||
} else {
|
||||
// insert 'policies.kyverno.patches' entry in annotation map
|
||||
annotations[policyAnnotation] = string(value)
|
||||
patchResponse = response{
|
||||
Op: "add",
|
||||
Path: "/metadata/annotations",
|
||||
Value: annotations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
patchByte, _ := json.Marshal(patchResponse)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/nirmata/kyverno/pkg/engine"
|
||||
"gotest.tools/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func newPolicyResponse(policy, rule string, patchesStr []string, success bool) engine.PolicyResponse {
|
||||
|
@ -24,18 +25,25 @@ func newPolicyResponse(policy, rule string, patchesStr []string, success bool) e
|
|||
}
|
||||
}
|
||||
|
||||
func newEngineResponse(policy, rule string, patchesStr []string, success bool) engine.EngineResponse {
|
||||
func newEngineResponse(policy, rule string, patchesStr []string, success bool, annotation map[string]string) engine.EngineResponse {
|
||||
return engine.EngineResponse{
|
||||
PatchedResource: unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"annotation": annotation,
|
||||
},
|
||||
},
|
||||
},
|
||||
PolicyResponse: newPolicyResponse(policy, rule, patchesStr, success),
|
||||
}
|
||||
}
|
||||
|
||||
func Test_empty_annotation(t *testing.T) {
|
||||
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true)
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true, nil)
|
||||
|
||||
annPatches := generateAnnotationPatches(nil, []engine.EngineResponse{engineResponse})
|
||||
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.io/patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
|
||||
annPatches := generateAnnotationPatches([]engine.EngineResponse{engineResponse})
|
||||
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
|
||||
assert.Assert(t, string(annPatches) == expectedPatches)
|
||||
}
|
||||
|
||||
|
@ -45,48 +53,47 @@ func Test_exist_annotation(t *testing.T) {
|
|||
}
|
||||
|
||||
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true)
|
||||
annPatches := generateAnnotationPatches(annotation, []engine.EngineResponse{engineResponse})
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true, annotation)
|
||||
annPatches := generateAnnotationPatches([]engine.EngineResponse{engineResponse})
|
||||
|
||||
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.io/patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]","test":"annotation"}}`
|
||||
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
|
||||
assert.Assert(t, string(annPatches) == expectedPatches)
|
||||
}
|
||||
|
||||
func Test_exist_kyverno_annotation(t *testing.T) {
|
||||
annotation := map[string]string{
|
||||
"policies.kyverno.io/patches": "old-annotation",
|
||||
"policies.kyverno.patches": "old-annotation",
|
||||
}
|
||||
|
||||
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true)
|
||||
annPatches := generateAnnotationPatches(annotation, []engine.EngineResponse{engineResponse})
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true, annotation)
|
||||
annPatches := generateAnnotationPatches([]engine.EngineResponse{engineResponse})
|
||||
|
||||
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.io/patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
|
||||
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
|
||||
assert.Assert(t, string(annPatches) == expectedPatches)
|
||||
}
|
||||
|
||||
func Test_annotation_nil_patch(t *testing.T) {
|
||||
annotation := map[string]string{
|
||||
"policies.kyverno.io/patches": "old-annotation",
|
||||
"policies.kyverno.patches": "old-annotation",
|
||||
}
|
||||
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", nil, true)
|
||||
annPatches := generateAnnotationPatches(annotation, []engine.EngineResponse{engineResponse})
|
||||
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", nil, true, annotation)
|
||||
annPatches := generateAnnotationPatches([]engine.EngineResponse{engineResponse})
|
||||
assert.Assert(t, annPatches == nil)
|
||||
|
||||
engineResponseNew := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{""}, true)
|
||||
annPatchesNew := generateAnnotationPatches(annotation, []engine.EngineResponse{engineResponseNew})
|
||||
engineResponseNew := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{""}, true, annotation)
|
||||
annPatchesNew := generateAnnotationPatches([]engine.EngineResponse{engineResponseNew})
|
||||
assert.Assert(t, annPatchesNew == nil)
|
||||
}
|
||||
|
||||
func Test_annotation_failed_Patch(t *testing.T) {
|
||||
annotation := map[string]string{
|
||||
"policies.kyverno.io/patches": "old-annotation",
|
||||
"policies.kyverno.patches": "old-annotation",
|
||||
}
|
||||
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", nil, false)
|
||||
annPatches := generateAnnotationPatches(annotation, []engine.EngineResponse{engineResponse})
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", nil, false, annotation)
|
||||
annPatches := generateAnnotationPatches([]engine.EngineResponse{engineResponse})
|
||||
|
||||
assert.Assert(t, annPatches == nil)
|
||||
}
|
||||
|
|
|
@ -2,16 +2,17 @@ package webhooks
|
|||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
||||
engine "github.com/nirmata/kyverno/pkg/engine"
|
||||
policyctr "github.com/nirmata/kyverno/pkg/policy"
|
||||
"github.com/nirmata/kyverno/pkg/utils"
|
||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// HandleMutation handles mutating webhook admission request
|
||||
func (ws *WebhookServer) handleMutation(request *v1beta1.AdmissionRequest) (bool, []byte, string) {
|
||||
func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest,
|
||||
policies []*v1alpha1.ClusterPolicy, roles, clusterRoles []string) (bool, []byte, string) {
|
||||
glog.V(4).Infof("Receive request in mutating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
|
||||
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
|
||||
|
||||
|
@ -61,18 +62,18 @@ func (ws *WebhookServer) handleMutation(request *v1beta1.AdmissionRequest) (bool
|
|||
//TODO: check if the name and namespace is also passed right in the resource?
|
||||
// if not then set it from the api request
|
||||
resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind})
|
||||
policies, err := ws.pLister.List(labels.NewSelector())
|
||||
if err != nil {
|
||||
//TODO check if the CRD is created ?
|
||||
// Unable to connect to policy Lister to access policies
|
||||
glog.Errorln("Unable to connect to policy controller to access policies. Mutation Rules are NOT being applied")
|
||||
glog.Warning(err)
|
||||
return true, nil, ""
|
||||
}
|
||||
|
||||
var engineResponses []engine.EngineResponse
|
||||
for _, policy := range policies {
|
||||
policyContext := engine.PolicyContext{
|
||||
Resource: *resource,
|
||||
AdmissionInfo: engine.RequestInfo{
|
||||
Roles: roles,
|
||||
ClusterRoles: clusterRoles,
|
||||
AdmissionUserInfo: request.UserInfo},
|
||||
}
|
||||
|
||||
for _, policy := range policies {
|
||||
policyContext.Policy = *policy
|
||||
// check if policy has a rule for the admission request kind
|
||||
if !utils.ContainsString(getApplicableKindsForPolicy(policy), request.Kind.Kind) {
|
||||
continue
|
||||
|
@ -81,7 +82,7 @@ func (ws *WebhookServer) handleMutation(request *v1beta1.AdmissionRequest) (bool
|
|||
glog.V(2).Infof("Handling mutation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
|
||||
resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation)
|
||||
// TODO: this can be
|
||||
engineResponse := engine.Mutate(*policy, *resource)
|
||||
engineResponse := engine.Mutate(policyContext)
|
||||
engineResponses = append(engineResponses, engineResponse)
|
||||
// Gather policy application statistics
|
||||
gatherStat(policy.Name, engineResponse.PolicyResponse)
|
||||
|
@ -98,7 +99,7 @@ func (ws *WebhookServer) handleMutation(request *v1beta1.AdmissionRequest) (bool
|
|||
}
|
||||
|
||||
// generate annotations
|
||||
if annPatches := generateAnnotationPatches(resource.GetAnnotations(), engineResponses); annPatches != nil {
|
||||
if annPatches := generateAnnotationPatches(engineResponses); annPatches != nil {
|
||||
patches = append(patches, annPatches)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,9 +20,13 @@ import (
|
|||
"github.com/nirmata/kyverno/pkg/event"
|
||||
"github.com/nirmata/kyverno/pkg/policy"
|
||||
tlsutils "github.com/nirmata/kyverno/pkg/tls"
|
||||
userinfo "github.com/nirmata/kyverno/pkg/userinfo"
|
||||
"github.com/nirmata/kyverno/pkg/webhookconfig"
|
||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
rbacinformer "k8s.io/client-go/informers/rbac/v1"
|
||||
rbaclister "k8s.io/client-go/listers/rbac/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
|
@ -36,6 +40,8 @@ type WebhookServer struct {
|
|||
pvLister kyvernolister.ClusterPolicyViolationLister
|
||||
pListerSynced cache.InformerSynced
|
||||
pvListerSynced cache.InformerSynced
|
||||
rbLister rbaclister.RoleBindingLister
|
||||
crbLister rbaclister.ClusterRoleBindingLister
|
||||
eventGen event.Interface
|
||||
webhookRegistrationClient *webhookconfig.WebhookRegistrationClient
|
||||
// API to send policy stats for aggregation
|
||||
|
@ -56,6 +62,8 @@ func NewWebhookServer(
|
|||
tlsPair *tlsutils.TlsPemPair,
|
||||
pInformer kyvernoinformer.ClusterPolicyInformer,
|
||||
pvInformer kyvernoinformer.ClusterPolicyViolationInformer,
|
||||
rbInformer rbacinformer.RoleBindingInformer,
|
||||
crbInformer rbacinformer.ClusterRoleBindingInformer,
|
||||
eventGen event.Interface,
|
||||
webhookRegistrationClient *webhookconfig.WebhookRegistrationClient,
|
||||
policyStatus policy.PolicyStatusInterface,
|
||||
|
@ -85,6 +93,8 @@ func NewWebhookServer(
|
|||
webhookRegistrationClient: webhookRegistrationClient,
|
||||
policyStatus: policyStatus,
|
||||
configHandler: configHandler,
|
||||
rbLister: rbInformer.Lister(),
|
||||
crbLister: crbInformer.Lister(),
|
||||
cleanUp: cleanUp,
|
||||
lastReqTime: checker.NewLastReqTime(),
|
||||
}
|
||||
|
@ -154,8 +164,32 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
|
||||
// TODO: this will be replaced by policy store lookup
|
||||
policies, err := ws.pLister.List(labels.NewSelector())
|
||||
if err != nil {
|
||||
//TODO check if the CRD is created ?
|
||||
// Unable to connect to policy Lister to access policies
|
||||
glog.Errorf("Unable to connect to policy controller to access policies. Policies are NOT being applied: %v", err)
|
||||
return &v1beta1.AdmissionResponse{Allowed: true}
|
||||
}
|
||||
|
||||
var roles, clusterRoles []string
|
||||
|
||||
// TODO(shuting): replace containRBACinfo after policy cache lookup is introduced
|
||||
// getRoleRef only if policy has roles/clusterroles defined
|
||||
startTime := time.Now()
|
||||
if containRBACinfo(policies) {
|
||||
roles, clusterRoles, err = userinfo.GetRoleRef(ws.rbLister, ws.crbLister, request)
|
||||
if err != nil {
|
||||
// TODO(shuting): continue apply policy if error getting roleRef?
|
||||
glog.Errorf("Unable to get rbac information for request Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s: %v",
|
||||
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation, err)
|
||||
}
|
||||
}
|
||||
glog.V(4).Infof("Time: webhook GetRoleRef %v", time.Since(startTime))
|
||||
|
||||
// MUTATION
|
||||
ok, patches, msg := ws.handleMutation(request)
|
||||
ok, patches, msg := ws.HandleMutation(request, policies, roles, clusterRoles)
|
||||
if !ok {
|
||||
glog.V(4).Infof("Deny admission request: %v/%s/%s", request.Kind, request.Namespace, request.Name)
|
||||
return &v1beta1.AdmissionResponse{
|
||||
|
@ -171,7 +205,7 @@ func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionReques
|
|||
patchedResource := processResourceWithPatches(patches, request.Object.Raw)
|
||||
|
||||
// VALIDATION
|
||||
ok, msg = ws.handleValidation(request, patchedResource)
|
||||
ok, msg = ws.HandleValidation(request, policies, patchedResource, roles, clusterRoles)
|
||||
if !ok {
|
||||
glog.V(4).Infof("Deny admission request: %v/%s/%s", request.Kind, request.Namespace, request.Name)
|
||||
return &v1beta1.AdmissionResponse{
|
||||
|
|
|
@ -98,3 +98,14 @@ func processResourceWithPatches(patch []byte, resource []byte) []byte {
|
|||
}
|
||||
return resource
|
||||
}
|
||||
|
||||
func containRBACinfo(policies []*kyverno.ClusterPolicy) bool {
|
||||
for _, policy := range policies {
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if len(rule.MatchResources.Roles) > 0 || len(rule.MatchResources.ClusterRoles) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -2,19 +2,20 @@ package webhooks
|
|||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
|
||||
engine "github.com/nirmata/kyverno/pkg/engine"
|
||||
policyctr "github.com/nirmata/kyverno/pkg/policy"
|
||||
"github.com/nirmata/kyverno/pkg/policyviolation"
|
||||
"github.com/nirmata/kyverno/pkg/utils"
|
||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// handleValidation handles validating webhook admission request
|
||||
// If there are no errors in validating rule we apply generation rules
|
||||
// patchedResource is the (resource + patches) after applying mutation rules
|
||||
func (ws *WebhookServer) handleValidation(request *v1beta1.AdmissionRequest, patchedResource []byte) (bool, string) {
|
||||
func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest,
|
||||
policies []*v1alpha1.ClusterPolicy, patchedResource []byte, roles, clusterRoles []string) (bool, string) {
|
||||
glog.V(4).Infof("Receive request in validating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
|
||||
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
|
||||
|
||||
|
@ -71,18 +72,17 @@ func (ws *WebhookServer) handleValidation(request *v1beta1.AdmissionRequest, pat
|
|||
// resource namespace is empty for the first CREATE operation
|
||||
resource.SetNamespace(request.Namespace)
|
||||
|
||||
policies, err := ws.pLister.List(labels.NewSelector())
|
||||
if err != nil {
|
||||
//TODO check if the CRD is created ?
|
||||
// Unable to connect to policy Lister to access policies
|
||||
glog.Error("Unable to connect to policy controller to access policies. Validation Rules are NOT being applied")
|
||||
glog.Warning(err)
|
||||
return true, ""
|
||||
policyContext := engine.PolicyContext{
|
||||
Resource: *resource,
|
||||
AdmissionInfo: engine.RequestInfo{
|
||||
Roles: roles,
|
||||
ClusterRoles: clusterRoles,
|
||||
AdmissionUserInfo: request.UserInfo},
|
||||
}
|
||||
|
||||
var engineResponses []engine.EngineResponse
|
||||
for _, policy := range policies {
|
||||
|
||||
policyContext.Policy = *policy
|
||||
if !utils.ContainsString(getApplicableKindsForPolicy(policy), request.Kind.Kind) {
|
||||
continue
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ func (ws *WebhookServer) handleValidation(request *v1beta1.AdmissionRequest, pat
|
|||
glog.V(2).Infof("Handling validation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
|
||||
resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation)
|
||||
|
||||
engineResponse := engine.Validate(*policy, *resource)
|
||||
engineResponse := engine.Validate(policyContext)
|
||||
engineResponses = append(engineResponses, engineResponse)
|
||||
// Gather policy application statistics
|
||||
gatherStat(policy.Name, engineResponse.PolicyResponse)
|
||||
|
|
|
@ -18,7 +18,7 @@ spec:
|
|||
overlay:
|
||||
metadata:
|
||||
annotations:
|
||||
+(cluster-autoscaler.kubernetes.io/safe-to-evict): true
|
||||
+(cluster-autoscaler.kubernetes.io/safe-to-evict): "true"
|
||||
spec:
|
||||
volumes:
|
||||
- (emptyDir): {}
|
||||
|
@ -31,7 +31,7 @@ spec:
|
|||
overlay:
|
||||
metadata:
|
||||
annotations:
|
||||
+(cluster-autoscaler.kubernetes.io/safe-to-evict): true
|
||||
+(cluster-autoscaler.kubernetes.io/safe-to-evict): "true"
|
||||
spec:
|
||||
volumes:
|
||||
- (hostPath):
|
||||
|
|
|
@ -2,9 +2,8 @@ apiVersion: v1
|
|||
kind: Pod
|
||||
metadata:
|
||||
name: pod-with-emptydir
|
||||
metadata:
|
||||
annotations:
|
||||
cluster-autoscaler.kubernetes.io/safe-to-evict: true
|
||||
annotations:
|
||||
cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
|
||||
spec:
|
||||
containers:
|
||||
- image: k8s.gcr.io/test-webserver
|
||||
|
|
|
@ -3,7 +3,7 @@ kind: Pod
|
|||
metadata:
|
||||
name: pod-with-hostpath
|
||||
annotations:
|
||||
cluster-autoscaler.kubernetes.io/safe-to-evict: true
|
||||
cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
|
||||
spec:
|
||||
containers:
|
||||
- image: k8s.gcr.io/test-webserver
|
||||
|
|
|
@ -14,6 +14,10 @@ expected:
|
|||
name: pod-with-emptydir
|
||||
rules:
|
||||
- name: annotate-empty-dir
|
||||
type: Mutation
|
||||
success: true
|
||||
message: "successfully processed overlay"
|
||||
- name: annotate-host-path
|
||||
type: Mutation
|
||||
success: true
|
||||
message: "successfully processed overlay"
|
|
@ -13,7 +13,11 @@ expected:
|
|||
namespace: ''
|
||||
name: pod-with-hostpath
|
||||
rules:
|
||||
- name: annotate-host-path
|
||||
- name: annotate-empty-dir
|
||||
type: Mutation
|
||||
success: true
|
||||
message: "successfully processed overlay"
|
||||
- name: annotate-host-path
|
||||
type: Mutation
|
||||
success: true
|
||||
message: "successfully processed overlay"
|
Loading…
Add table
Reference in a new issue