1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-30 19:35:06 +00:00

593 feature (#594)

* initial commit

* background policy validation

* correct message

* skip non-background policy process for add/update

* add Generate Request CR

* generate Request Generator Initial

* test generate request CR generation

* initial commit gr generator

* generate controller initial framework

* add crd for generate request

* gr cleanup controller initial commit

* cleanup controller initial

* generate mid-commit

* generate rule processing

* create PV on generate error

* embed resource type

* testing phase 1- generate resources with variable substitution

* fix tests

* comment broken test #586

* add printer column for state

* return if existing resource for clone

* set resync time to 2 mins & remove resource version check in update handler for gr

* generate events for reporting

* fix logs

* initial commit

* fix trailing quote in patch

* remove comments

* initial condition (equal & notequal)

* initial support for conditions

* initial support fo conditions in generate

* support precondition checks

* cleanup

* re-evaluate GR on namespace update using dynamic informers

* add status for generated resources

* display loaded variable SA

* support delete cleanup of generate request main resources

* fix log

* remove namespace from SA username

* support multiple variables per statement for scalar values

* fix fail variables

* add check for userInfo

* validation checks for conditions

* update policy

* refactor logs

* code review

* add openapispec for clusterpolicy preconditions

* Update documentation

* CR fixes

* documentation

* CR fixes

* update variable

* fix logs

* update policy

* pre-defined variables (serviceAccountName & serviceAccountNamespace)

* update test
This commit is contained in:
Shivkumar Dudhani 2020-01-07 15:13:57 -08:00 committed by GitHub
parent 8d2866a29f
commit 3cf9141f4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1467 additions and 118 deletions

View file

@ -87,6 +87,9 @@ func main() {
kubeInformer := kubeinformers.NewSharedInformerFactoryWithOptions(
kubeClient,
10*time.Second)
// KUBERNETES Dynamic informer
// - cahce resync time: 10 seconds
kubedynamicInformer := client.NewDynamicSharedInformerFactory(10 * time.Second)
// WERBHOOK REGISTRATION CLIENT
webhookRegistrationClient := webhookconfig.NewWebhookRegistrationClient(
@ -168,6 +171,7 @@ func main() {
pInformer.Kyverno().V1().GenerateRequests(),
egen,
pvgen,
kubedynamicInformer,
)
// GENERATE REQUEST CLEANUP
// -- cleans up the generate requests that have not been processed(i.e. state = [Pending, Failed]) for more than defined timeout
@ -176,6 +180,7 @@ func main() {
client,
pInformer.Kyverno().V1().ClusterPolicies(),
pInformer.Kyverno().V1().GenerateRequests(),
kubedynamicInformer,
)
// CONFIGURE CERTIFICATES
@ -221,6 +226,7 @@ func main() {
// Start the components
pInformer.Start(stopCh)
kubeInformer.Start(stopCh)
kubedynamicInformer.Start(stopCh)
go grgen.Run(1)
go rWebhookWatcher.Run(stopCh)
go configData.Run(stopCh)

View file

@ -170,6 +170,14 @@ spec:
type: array
items:
type: string
preconditions:
type: array
items:
type: object
required:
- key # can be of any type
- operator # typed
- value # can be of any type
mutate:
type: object
properties:

View file

@ -170,6 +170,14 @@ spec:
type: array
items:
type: string
preconditions:
type: array
items:
type: object
required:
- key # can be of any type
- operator # typed
- value # can be of any type
mutate:
type: object
properties:

View file

@ -2,10 +2,11 @@
# Generate Configurations
```generate``` is used to create default resources for a namespace. This feature is useful for managing resources that are required in each namespace.
```generate``` is used to create additional resources when a resource is created. This is useful to create supporting resources, such as role bindings for a new namespace.
## Example 1
- rule
Creates a ConfigMap with name `default-config` for all
````yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
@ -13,7 +14,7 @@ metadata:
name: basic-policy
spec:
rules:
- name: "Basic config generator for all namespaces"
- name: "Generate ConfigMap"
match:
resources:
kinds:
@ -22,12 +23,13 @@ spec:
matchLabels:
LabelForSelector : "namespace2"
generate:
kind: ConfigMap
name: default-config
kind: ConfigMap # Kind of resource
name: default-config # Name of the new Resource
namespace: "{{request.object.metadata.name}}" # Create in the namespace that triggers this rule
clone:
namespace: default
name: config-template
- name: "Basic config generator for all namespaces"
- name: "Generate Secret"
match:
resources:
kinds:
@ -38,6 +40,7 @@ spec:
generate:
kind: Secret
name: mongo-creds
namespace: "{{request.object.metadata.name}}" # Create in the namespace that triggers this rule
data:
data:
DB_USER: YWJyYWthZGFicmE=
@ -69,6 +72,7 @@ spec:
generate:
kind: NetworkPolicy
name: deny-all-traffic
namespace: "{{request.object.metadata.name}}" # Create in the namespace that triggers this rule
data:
spec:
podSelector:

View file

@ -70,14 +70,48 @@ spec :
clusterroles:
- cluster-admin
- admin
# rule is evaluated if the preconditions are satisfied
# all preconditions are AND/&& operation
preconditions:
- key: name # compares (key operator value)
operator: Equal
value: name # constant "name" == "name"
- key: "{{serviceAccount}}" # refer to a pre-defined variable serviceAccount
operator: NotEqual
value: "user1" # if service
# Each rule can contain a single validate, mutate, or generate directive
...
````
Each rule can validate, mutate, or generate configurations of matching resources. A rule definition can contain only a single **mutate**, **validate**, or **generate** child node. These actions are applied to the resource in described order: mutation, validation and then generation.
# Variables:
Variables can be used to reference attributes that are loaded in the context using a [JMESPATH](http://jmespath.org/) search path.
Format: `{{<JMESPATH>}}`
Resources available in context:
- Resource: `{{request.object}}`
- UserInfo: `{{request.userInfo}}`
## Pre-defined Variables
- `serviceAccountName` : the variable removes the suffix system:serviceaccount:<namespace>: and stores the userName.
Example userName=`system:serviceaccount:nirmata:user1` will store variable value as `user1`.
- `serviceAccountNamespace` : extracts the `namespace` of the serviceAccount.
Example userName=`system:serviceaccount:nirmata:user1` will store variable value as `nirmata`.
Examples:
1. Refer to resource name(type string)
`{{request.object.metadata.name}}`
2. Build name from multiple variables(type string)
`"ns-owner-{{request.object.metadata.namespace}}-{{request.userInfo.username}}-binding"`
3. Refer to metadata struct/object(type object)
`{{request.object.metadata}}`
---
<small>*Read Next >> [Validate](/documentation/writing-policies-validate.md)*</small>

View file

@ -43,6 +43,9 @@ type RequestInfo struct {
type GenerateRequestStatus struct {
State GenerateRequestState `json:"state"`
Message string `json:"message,omitempty"`
// This will track the resoruces that are generated by the generate Policy
// Will be used during clean up resources
GeneratedResources []ResourceSpec `json:"generatedResources,omitempty"`
}
//GenerateRequestState defines the state of
@ -125,7 +128,7 @@ type Policy struct {
type Spec struct {
Rules []Rule `json:"rules"`
ValidationFailureAction string `json:"validationFailureAction"`
Background bool `json:"background,omitempty"`
Background *bool `json:"background"`
}
// Rule is set of mutation, validation and generation actions
@ -134,11 +137,27 @@ type Rule struct {
Name string `json:"name"`
MatchResources MatchResources `json:"match"`
ExcludeResources ExcludeResources `json:"exclude,omitempty"`
Conditions []Condition `json:"preconditions,omitempty"`
Mutation Mutation `json:"mutate,omitempty"`
Validation Validation `json:"validate,omitempty"`
Generation Generation `json:"generate,omitempty"`
}
type Condition struct {
Key interface{} `json:"key"`
Operator ConditionOperator `json:"operator"`
Value interface{} `json:"value"`
}
type ConditionOperator string
const (
Equal ConditionOperator = "Equal"
NotEqual ConditionOperator = "NotEqual"
In ConditionOperator = "In"
NotIn ConditionOperator = "NotIn"
)
//MatchResources contains resource description of the resources that the rule is to apply on
type MatchResources struct {
UserInfo

View file

@ -55,6 +55,14 @@ func (gen *Generation) DeepCopyInto(out *Generation) {
}
}
// DeepCopyInto is declared because k8s:deepcopy-gen is
// not able to generate this method for interface{} member
func (cond *Condition) DeepCopyInto(out *Condition) {
if out != nil {
*out = *cond
}
}
//ToKey generates the key string used for adding label to polivy violation
func (rs ResourceSpec) ToKey() string {
return rs.Kind + "." + rs.Name

View file

@ -164,6 +164,16 @@ func (in *ClusterPolicyViolationList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
func (in *Condition) DeepCopy() *Condition {
if in == nil {
return nil
}
out := new(Condition)
in.DeepCopyInto(out)
return out
}
// 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
@ -188,7 +198,7 @@ func (in *GenerateRequest) DeepCopyInto(out *GenerateRequest) {
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
in.Status.DeepCopyInto(&out.Status)
return
}
@ -281,6 +291,11 @@ func (in *GenerateRequestSpec) DeepCopy() *GenerateRequestSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GenerateRequestStatus) DeepCopyInto(out *GenerateRequestStatus) {
*out = *in
if in.GeneratedResources != nil {
in, out := &in.GeneratedResources, &out.GeneratedResources
*out = make([]ResourceSpec, len(*in))
copy(*out, *in)
}
return
}
@ -588,6 +603,13 @@ func (in *Rule) DeepCopyInto(out *Rule) {
*out = *in
in.MatchResources.DeepCopyInto(&out.MatchResources)
in.ExcludeResources.DeepCopyInto(&out.ExcludeResources)
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.Mutation.DeepCopyInto(&out.Mutation)
in.Validation.DeepCopyInto(&out.Validation)
in.Generation.DeepCopyInto(&out.Generation)
@ -630,6 +652,11 @@ func (in *Spec) DeepCopyInto(out *Spec) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Background != nil {
in, out := &in.Background, &out.Background
*out = new(bool)
**out = **in
}
return
}

View file

@ -154,6 +154,24 @@ type GenerateRequestListerExpansion interface {
// GenerateRequestNamespaceLister.
type GenerateRequestNamespaceListerExpansion interface {
GetGenerateRequestsForClusterPolicy(policy string) ([]*kyvernov1.GenerateRequest, error)
GetGenerateRequestsForResource(kind, namespace, name string) ([]*kyvernov1.GenerateRequest, error)
}
func (s generateRequestNamespaceLister) GetGenerateRequestsForResource(kind, namespace, name string) ([]*kyvernov1.GenerateRequest, error) {
var list []*kyvernov1.GenerateRequest
grs, err := s.List(labels.NewSelector())
if err != nil {
return nil, err
}
for idx, gr := range grs {
if gr.Spec.Resource.Kind == kind &&
gr.Spec.Resource.Namespace == namespace &&
gr.Spec.Resource.Name == name {
list = append(list, grs[idx])
}
}
return list, err
}
func (s generateRequestNamespaceLister) GetGenerateRequestsForClusterPolicy(policy string) ([]*kyvernov1.GenerateRequest, error) {

View file

@ -19,6 +19,7 @@ import (
"k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/kubernetes"
csrtype "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
event "k8s.io/client-go/kubernetes/typed/core/v1"
@ -62,6 +63,10 @@ func NewClient(config *rest.Config, resync time.Duration, stopCh <-chan struct{}
return &client, nil
}
func (c *Client) NewDynamicSharedInformerFactory(defaultResync time.Duration) dynamicinformer.DynamicSharedInformerFactory {
return dynamicinformer.NewDynamicSharedInformerFactory(c.client, defaultResync)
}
//GetKubePolicyDeployment returns kube policy depoyment value
func (c *Client) GetKubePolicyDeployment() (*apps.Deployment, error) {
kubePolicyDeployment, err := c.GetResource("Deployment", config.KubePolicyNamespace, config.KubePolicyDeploymentName)

View file

@ -2,6 +2,7 @@ package context
import (
"encoding/json"
"strings"
"sync"
jsonpatch "github.com/evanphx/json-patch"
@ -17,6 +18,8 @@ type Interface interface {
AddResource(dataRaw []byte) error
// merges userInfo json under kyverno.userInfo
AddUserInfo(userInfo kyverno.UserInfo) error
// merges serrviceaccount
AddSA(userName string) error
EvalInterface
}
@ -97,3 +100,56 @@ func (ctx *Context) AddUserInfo(userRequestInfo kyverno.RequestInfo) error {
}
return ctx.AddJSON(objRaw)
}
// removes prefix 'system:serviceaccount:' and namespace, then loads only username
func (ctx *Context) AddSA(userName string) error {
saPrefix := "system:serviceaccount:"
var sa string
saName := ""
saNamespace := ""
if len(userName) <= len(saPrefix) {
sa = ""
} else {
sa = userName[len(saPrefix):]
}
// filter namespace
groups := strings.Split(sa, ":")
if len(groups) >= 2 {
glog.V(4).Infof("serviceAccount namespace: %s", groups[0])
glog.V(4).Infof("serviceAccount name: %s", groups[1])
saName = groups[1]
saNamespace = groups[0]
}
glog.Infof("Loading variable serviceAccountName with value: %s", saName)
saNameObj := struct {
SA string `json:"serviceAccountName"`
}{
SA: saName,
}
saNameRaw, err := json.Marshal(saNameObj)
if err != nil {
glog.V(4).Infof("failed to marshall the updated context data")
return err
}
if err := ctx.AddJSON(saNameRaw); err != nil {
return err
}
glog.Infof("Loading variable serviceAccountNamespace with value: %s", saNamespace)
saNsObj := struct {
SA string `json:"serviceAccountNamespace"`
}{
SA: saNamespace,
}
saNsRaw, err := json.Marshal(saNsObj)
if err != nil {
glog.V(4).Infof("failed to marshall the updated context data")
return err
}
if err := ctx.AddJSON(saNsRaw); err != nil {
return err
}
return nil
}

View file

@ -44,7 +44,7 @@ func Test_addResourceAndUserContext(t *testing.T) {
`)
userInfo := authenticationv1.UserInfo{
Username: "admin",
Username: "system:serviceaccount:nirmata:user1",
UID: "014fbff9a07c",
}
userRequestInfo := kyverno.RequestInfo{
@ -80,7 +80,29 @@ func Test_addResourceAndUserContext(t *testing.T) {
if err != nil {
t.Error(err)
}
expectedResult = "admin"
expectedResult = "system:serviceaccount:nirmata:user1"
t.Log(result)
if !reflect.DeepEqual(expectedResult, result) {
t.Error("exected result does not match")
}
// Add service account Name
ctx.AddSA(userRequestInfo.AdmissionUserInfo.Username)
result, err = ctx.Query("serviceAccountName")
if err != nil {
t.Error(err)
}
expectedResult = "user1"
t.Log(result)
if !reflect.DeepEqual(expectedResult, result) {
t.Error("exected result does not match")
}
// Add service account Namespace
result, err = ctx.Query("serviceAccountNamespace")
if err != nil {
t.Error(err)
}
expectedResult = "nirmata"
t.Log(result)
if !reflect.DeepEqual(expectedResult, result) {
t.Error("exected result does not match")

View file

@ -1,9 +1,12 @@
package engine
import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/rbac"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/variables"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -12,10 +15,11 @@ func GenerateNew(policyContext PolicyContext) (resp response.EngineResponse) {
policy := policyContext.Policy
resource := policyContext.NewResource
admissionInfo := policyContext.AdmissionInfo
return filterRules(policy, resource, admissionInfo)
ctx := policyContext.Context
return filterRules(policy, resource, admissionInfo, ctx)
}
func filterRule(rule kyverno.Rule, resource unstructured.Unstructured, admissionInfo kyverno.RequestInfo) *response.RuleResponse {
func filterRule(rule kyverno.Rule, resource unstructured.Unstructured, admissionInfo kyverno.RequestInfo, ctx context.EvalInterface) *response.RuleResponse {
if !rule.HasGenerate() {
return nil
}
@ -25,6 +29,12 @@ func filterRule(rule kyverno.Rule, resource unstructured.Unstructured, admission
if !MatchesResourceDescription(resource, rule) {
return nil
}
// evaluate pre-conditions
if !variables.EvaluateConditions(ctx, rule.Conditions) {
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
return nil
}
// build rule Response
return &response.RuleResponse{
Name: rule.Name,
@ -32,7 +42,7 @@ func filterRule(rule kyverno.Rule, resource unstructured.Unstructured, admission
}
}
func filterRules(policy kyverno.ClusterPolicy, resource unstructured.Unstructured, admissionInfo kyverno.RequestInfo) response.EngineResponse {
func filterRules(policy kyverno.ClusterPolicy, resource unstructured.Unstructured, admissionInfo kyverno.RequestInfo, ctx context.EvalInterface) response.EngineResponse {
resp := response.EngineResponse{
PolicyResponse: response.PolicyResponse{
Policy: policy.Name,
@ -45,7 +55,7 @@ func filterRules(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
}
for _, rule := range policy.Spec.Rules {
if ruleResp := filterRule(rule, resource, admissionInfo); ruleResp != nil {
if ruleResp := filterRule(rule, resource, admissionInfo, ctx); ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
}
}

View file

@ -8,6 +8,7 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/variables"
"github.com/nirmata/kyverno/pkg/engine/rbac"
)
@ -69,6 +70,13 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
continue
}
// evaluate pre-conditions
if !variables.EvaluateConditions(ctx, rule.Conditions) {
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
continue
}
// Process Overlay
if rule.Mutation.Overlay != nil {
var ruleResponse response.RuleResponse

View file

@ -20,12 +20,23 @@ func ContainsUserInfo(policy kyverno.ClusterPolicy) error {
}
// variable defined with user information
// - condition.key
// - condition.value
// - mutate.overlay
// - validate.pattern
// - validate.anyPattern[*]
// variables to filter
// - request.userInfo
filterVars := []string{"request.userInfo*"}
for condIdx, condition := range rule.Conditions {
if err := variables.CheckVariables(condition.Key, filterVars, "/"); err != nil {
return fmt.Errorf("path: spec/rules[%d]/condition[%d]/key%s", idx, condIdx, err)
}
if err := variables.CheckVariables(condition.Value, filterVars, "/"); err != nil {
return fmt.Errorf("path: spec/rules[%d]/condition[%d]/value%s", idx, condIdx, err)
}
}
if err := variables.CheckVariables(rule.Mutation.Overlay, filterVars, "/"); err != nil {
return fmt.Errorf("path: spec/rules[%d]/mutate/overlay%s", idx, err)
}

View file

@ -21,7 +21,11 @@ func Validate(p kyverno.ClusterPolicy) error {
if path, err := validateUniqueRuleName(p); err != nil {
return fmt.Errorf("path: spec.%s: %v", path, err)
}
if p.Spec.Background {
if p.Spec.Background == nil {
//skipped policy mutation default -> skip validation -> will not be processed for background processing
return nil
}
if *p.Spec.Background {
if err := ContainsUserInfo(p); err != nil {
// policy.spec.background -> "true"
// - cannot use variables with request.userInfo

View file

@ -12,6 +12,7 @@ import (
"github.com/nirmata/kyverno/pkg/engine/rbac"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/validate"
"github.com/nirmata/kyverno/pkg/engine/variables"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@ -107,6 +108,13 @@ func validateResource(ctx context.EvalInterface, policy kyverno.ClusterPolicy, r
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
continue
}
// evaluate pre-conditions
if !variables.EvaluateConditions(ctx, rule.Conditions) {
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
continue
}
if rule.Validation.Pattern != nil || rule.Validation.AnyPattern != nil {
ruleResponse := validatePatterns(ctx, resource, rule)
incrementAppliedCount(resp)

View file

@ -0,0 +1,28 @@
package variables
import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/variables/operator"
)
func Evaluate(ctx context.EvalInterface, condition kyverno.Condition) bool {
// get handler for the operator
handle := operator.CreateOperatorHandler(ctx, condition.Operator, SubstituteVariables)
if handle == nil {
return false
}
return handle.Evaluate(condition.Key, condition.Value)
}
func EvaluateConditions(ctx context.EvalInterface, conditions []kyverno.Condition) bool {
// AND the conditions
for _, condition := range conditions {
if !Evaluate(ctx, condition) {
glog.V(4).Infof("condition %v failed", condition)
return false
}
}
return true
}

View file

@ -0,0 +1,518 @@
package variables
import (
"encoding/json"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/context"
)
// STRINGS
func Test_Eval_Equal_Const_String_Pass(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: "name",
Operator: kyverno.Equal,
Value: "name",
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_Equal_Const_String_Fail(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: "name",
Operator: kyverno.Equal,
Value: "name1",
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
func Test_Eval_NoEqual_Const_String_Pass(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: "name",
Operator: kyverno.NotEqual,
Value: "name1",
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_NoEqual_Const_String_Fail(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: "name",
Operator: kyverno.NotEqual,
Value: "name",
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
//Bool
func Test_Eval_Equal_Const_Bool_Pass(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: true,
Operator: kyverno.Equal,
Value: true,
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_Equal_Const_Bool_Fail(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: true,
Operator: kyverno.Equal,
Value: false,
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
func Test_Eval_NoEqual_Const_Bool_Pass(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: true,
Operator: kyverno.NotEqual,
Value: false,
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_NoEqual_Const_Bool_Fail(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: true,
Operator: kyverno.NotEqual,
Value: true,
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
// int
func Test_Eval_Equal_Const_int_Pass(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: 1,
Operator: kyverno.Equal,
Value: 1,
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_Equal_Const_int_Fail(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: 1,
Operator: kyverno.Equal,
Value: 2,
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
func Test_Eval_NoEqual_Const_int_Pass(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: 1,
Operator: kyverno.NotEqual,
Value: 2,
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_NoEqual_Const_int_Fail(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: 1,
Operator: kyverno.NotEqual,
Value: 1,
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
// int64
func Test_Eval_Equal_Const_int64_Pass(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: int64(1),
Operator: kyverno.Equal,
Value: int64(1),
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_Equal_Const_int64_Fail(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: int64(1),
Operator: kyverno.Equal,
Value: int64(2),
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
func Test_Eval_NoEqual_Const_int64_Pass(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: int64(1),
Operator: kyverno.NotEqual,
Value: int64(2),
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_NoEqual_Const_int64_Fail(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: int64(1),
Operator: kyverno.NotEqual,
Value: int64(1),
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
//float64
func Test_Eval_Equal_Const_float64_Pass(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: 1.5,
Operator: kyverno.Equal,
Value: 1.5,
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_Equal_Const_float64_Fail(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: 1.5,
Operator: kyverno.Equal,
Value: 1.6,
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
func Test_Eval_NoEqual_Const_float64_Pass(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: 1.5,
Operator: kyverno.NotEqual,
Value: 1.6,
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_NoEqual_Const_float64_Fail(t *testing.T) {
ctx := context.NewContext()
// no variables
condition := kyverno.Condition{
Key: 1.5,
Operator: kyverno.NotEqual,
Value: 1.5,
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
//object/map[string]interface
func Test_Eval_Equal_Const_object_Pass(t *testing.T) {
ctx := context.NewContext()
obj1Raw := []byte(`{ "dir": { "file1": "a" } }`)
obj2Raw := []byte(`{ "dir": { "file1": "a" } }`)
var obj1, obj2 interface{}
json.Unmarshal(obj1Raw, &obj1)
json.Unmarshal(obj2Raw, &obj2)
// no variables
condition := kyverno.Condition{
Key: obj1,
Operator: kyverno.Equal,
Value: obj2,
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_Equal_Const_object_Fail(t *testing.T) {
ctx := context.NewContext()
obj1Raw := []byte(`{ "dir": { "file1": "a" } }`)
obj2Raw := []byte(`{ "dir": { "file1": "b" } }`)
var obj1, obj2 interface{}
json.Unmarshal(obj1Raw, &obj1)
json.Unmarshal(obj2Raw, &obj2)
// no variables
condition := kyverno.Condition{
Key: obj1,
Operator: kyverno.Equal,
Value: obj2,
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
func Test_Eval_NotEqual_Const_object_Pass(t *testing.T) {
ctx := context.NewContext()
obj1Raw := []byte(`{ "dir": { "file1": "a" } }`)
obj2Raw := []byte(`{ "dir": { "file1": "b" } }`)
var obj1, obj2 interface{}
json.Unmarshal(obj1Raw, &obj1)
json.Unmarshal(obj2Raw, &obj2)
// no variables
condition := kyverno.Condition{
Key: obj1,
Operator: kyverno.NotEqual,
Value: obj2,
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_NotEqual_Const_object_Fail(t *testing.T) {
ctx := context.NewContext()
obj1Raw := []byte(`{ "dir": { "file1": "a" } }`)
obj2Raw := []byte(`{ "dir": { "file1": "a" } }`)
var obj1, obj2 interface{}
json.Unmarshal(obj1Raw, &obj1)
json.Unmarshal(obj2Raw, &obj2)
// no variables
condition := kyverno.Condition{
Key: obj1,
Operator: kyverno.NotEqual,
Value: obj2,
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
// list/ []interface{}
func Test_Eval_Equal_Const_list_Pass(t *testing.T) {
ctx := context.NewContext()
obj1Raw := []byte(`[ { "name": "a", "file": "a" }, { "name": "b", "file": "b" } ]`)
obj2Raw := []byte(`[ { "name": "a", "file": "a" }, { "name": "b", "file": "b" } ]`)
var obj1, obj2 interface{}
json.Unmarshal(obj1Raw, &obj1)
json.Unmarshal(obj2Raw, &obj2)
// no variables
condition := kyverno.Condition{
Key: obj1,
Operator: kyverno.Equal,
Value: obj2,
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_Equal_Const_list_Fail(t *testing.T) {
ctx := context.NewContext()
obj1Raw := []byte(`[ { "name": "a", "file": "a" }, { "name": "b", "file": "b" } ]`)
obj2Raw := []byte(`[ { "name": "b", "file": "a" }, { "name": "b", "file": "b" } ]`)
var obj1, obj2 interface{}
json.Unmarshal(obj1Raw, &obj1)
json.Unmarshal(obj2Raw, &obj2)
// no variables
condition := kyverno.Condition{
Key: obj1,
Operator: kyverno.Equal,
Value: obj2,
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
func Test_Eval_NotEqual_Const_list_Pass(t *testing.T) {
ctx := context.NewContext()
obj1Raw := []byte(`[ { "name": "a", "file": "a" }, { "name": "b", "file": "b" } ]`)
obj2Raw := []byte(`[ { "name": "b", "file": "a" }, { "name": "b", "file": "b" } ]`)
var obj1, obj2 interface{}
json.Unmarshal(obj1Raw, &obj1)
json.Unmarshal(obj2Raw, &obj2)
// no variables
condition := kyverno.Condition{
Key: obj1,
Operator: kyverno.NotEqual,
Value: obj2,
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_NotEqual_Const_list_Fail(t *testing.T) {
ctx := context.NewContext()
obj1Raw := []byte(`[ { "name": "a", "file": "a" }, { "name": "b", "file": "b" } ]`)
obj2Raw := []byte(`[ { "name": "a", "file": "a" }, { "name": "b", "file": "b" } ]`)
var obj1, obj2 interface{}
json.Unmarshal(obj1Raw, &obj1)
json.Unmarshal(obj2Raw, &obj2)
// no variables
condition := kyverno.Condition{
Key: obj1,
Operator: kyverno.NotEqual,
Value: obj2,
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}
// Variables
func Test_Eval_Equal_Var_Pass(t *testing.T) {
resourceRaw := []byte(`
{
"metadata": {
"name": "temp",
"namespace": "n1"
},
"spec": {
"namespace": "n1",
"name": "temp1"
}
}
`)
// context
ctx := context.NewContext()
ctx.AddResource(resourceRaw)
condition := kyverno.Condition{
Key: "{{request.object.metadata.name}}",
Operator: kyverno.Equal,
Value: "temp",
}
if !Evaluate(ctx, condition) {
t.Error("expected to pass")
}
}
func Test_Eval_Equal_Var_Fail(t *testing.T) {
resourceRaw := []byte(`
{
"metadata": {
"name": "temp",
"namespace": "n1"
},
"spec": {
"namespace": "n1",
"name": "temp1"
}
}
`)
// context
ctx := context.NewContext()
ctx.AddResource(resourceRaw)
condition := kyverno.Condition{
Key: "{{request.object.metadata.name}}",
Operator: kyverno.Equal,
Value: "temp1",
}
if Evaluate(ctx, condition) {
t.Error("expected to fail")
}
}

View file

@ -0,0 +1,139 @@
package operator
import (
"math"
"reflect"
"strconv"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/context"
)
func NewEqualHandler(ctx context.EvalInterface, subHandler VariableSubstitutionHandler) OperatorHandler {
return EqualHandler{
ctx: ctx,
subHandler: subHandler,
}
}
type EqualHandler struct {
ctx context.EvalInterface
subHandler VariableSubstitutionHandler
}
func (eh EqualHandler) Evaluate(key, value interface{}) bool {
// substitute the variables
nKey := eh.subHandler(eh.ctx, key)
nValue := eh.subHandler(eh.ctx, value)
// key and value need to be of same type
switch typedKey := nKey.(type) {
case bool:
return eh.validateValuewithBoolPattern(typedKey, nValue)
case int:
return eh.validateValuewithIntPattern(int64(typedKey), nValue)
case int64:
return eh.validateValuewithIntPattern(typedKey, nValue)
case float64:
return eh.validateValuewithFloatPattern(typedKey, nValue)
case string:
return eh.validateValuewithStringPattern(typedKey, nValue)
case map[string]interface{}:
return eh.validateValueWithMapPattern(typedKey, nValue)
case []interface{}:
return eh.validateValueWithSlicePattern(typedKey, nValue)
default:
glog.Errorf("Unsupported type %v", typedKey)
return false
}
}
func (eh EqualHandler) validateValueWithSlicePattern(key []interface{}, value interface{}) bool {
if val, ok := value.([]interface{}); ok {
return reflect.DeepEqual(key, val)
}
glog.Warningf("Expected []interface{}, %v is of type %T", value, value)
return false
}
func (eh EqualHandler) validateValueWithMapPattern(key map[string]interface{}, value interface{}) bool {
if val, ok := value.(map[string]interface{}); ok {
return reflect.DeepEqual(key, val)
}
glog.Warningf("Expected map[string]interface{}, %v is of type %T", value, value)
return false
}
func (eh EqualHandler) validateValuewithStringPattern(key string, value interface{}) bool {
if val, ok := value.(string); ok {
return key == val
}
glog.Warningf("Expected string, %v is of type %T", value, value)
return false
}
func (eh EqualHandler) validateValuewithFloatPattern(key float64, value interface{}) bool {
switch typedValue := value.(type) {
case int:
// check that float has not fraction
if key == math.Trunc(key) {
return int(key) == typedValue
}
glog.Warningf("Expected float, found int: %d\n", typedValue)
case int64:
// check that float has not fraction
if key == math.Trunc(key) {
return int64(key) == typedValue
}
glog.Warningf("Expected float, found int: %d\n", typedValue)
case float64:
return typedValue == key
case string:
// extract float from string
float64Num, err := strconv.ParseFloat(typedValue, 64)
if err != nil {
glog.Warningf("Failed to parse float64 from string: %v", err)
return false
}
return float64Num == key
default:
glog.Warningf("Expected float, found: %T\n", value)
return false
}
return false
}
func (eh EqualHandler) validateValuewithBoolPattern(key bool, value interface{}) bool {
typedValue, ok := value.(bool)
if !ok {
glog.Error("Expected bool, found %V", value)
return false
}
return key == typedValue
}
func (eh EqualHandler) validateValuewithIntPattern(key int64, value interface{}) bool {
switch typedValue := value.(type) {
case int:
return int64(typedValue) == key
case int64:
return typedValue == key
case float64:
// check that float has no fraction
if typedValue == math.Trunc(typedValue) {
return int64(typedValue) == key
}
glog.Warningf("Expected int, found float: %f", typedValue)
return false
case string:
// extract in64 from string
int64Num, err := strconv.ParseInt(typedValue, 10, 64)
if err != nil {
glog.Warningf("Failed to parse int64 from string: %v", err)
return false
}
return int64Num == key
default:
glog.Warningf("Expected int, %v is of type %T", value, value)
return false
}
}

View file

@ -0,0 +1,139 @@
package operator
import (
"math"
"reflect"
"strconv"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/context"
)
func NewNotEqualHandler(ctx context.EvalInterface, subHandler VariableSubstitutionHandler) OperatorHandler {
return NotEqualHandler{
ctx: ctx,
subHandler: subHandler,
}
}
type NotEqualHandler struct {
ctx context.EvalInterface
subHandler VariableSubstitutionHandler
}
func (neh NotEqualHandler) Evaluate(key, value interface{}) bool {
// substitute the variables
nKey := neh.subHandler(neh.ctx, key)
nValue := neh.subHandler(neh.ctx, value)
// key and value need to be of same type
switch typedKey := nKey.(type) {
case bool:
return neh.validateValuewithBoolPattern(typedKey, nValue)
case int:
return neh.validateValuewithIntPattern(int64(typedKey), nValue)
case int64:
return neh.validateValuewithIntPattern(typedKey, nValue)
case float64:
return neh.validateValuewithFloatPattern(typedKey, nValue)
case string:
return neh.validateValuewithStringPattern(typedKey, nValue)
case map[string]interface{}:
return neh.validateValueWithMapPattern(typedKey, nValue)
case []interface{}:
return neh.validateValueWithSlicePattern(typedKey, nValue)
default:
glog.Error("Unsupported type %V", typedKey)
return false
}
}
func (neh NotEqualHandler) validateValueWithSlicePattern(key []interface{}, value interface{}) bool {
if val, ok := value.([]interface{}); ok {
return !reflect.DeepEqual(key, val)
}
glog.Warningf("Expected []interface{}, %v is of type %T", value, value)
return false
}
func (neh NotEqualHandler) validateValueWithMapPattern(key map[string]interface{}, value interface{}) bool {
if val, ok := value.(map[string]interface{}); ok {
return !reflect.DeepEqual(key, val)
}
glog.Warningf("Expected map[string]interface{}, %v is of type %T", value, value)
return false
}
func (neh NotEqualHandler) validateValuewithStringPattern(key string, value interface{}) bool {
if val, ok := value.(string); ok {
return key != val
}
glog.Warningf("Expected string, %v is of type %T", value, value)
return false
}
func (neh NotEqualHandler) validateValuewithFloatPattern(key float64, value interface{}) bool {
switch typedValue := value.(type) {
case int:
// check that float has not fraction
if key == math.Trunc(key) {
return int(key) != typedValue
}
glog.Warningf("Expected float, found int: %d\n", typedValue)
case int64:
// check that float has not fraction
if key == math.Trunc(key) {
return int64(key) != typedValue
}
glog.Warningf("Expected float, found int: %d\n", typedValue)
case float64:
return typedValue != key
case string:
// extract float from string
float64Num, err := strconv.ParseFloat(typedValue, 64)
if err != nil {
glog.Warningf("Failed to parse float64 from string: %v", err)
return false
}
return float64Num != key
default:
glog.Warningf("Expected float, found: %T\n", value)
return false
}
return false
}
func (neh NotEqualHandler) validateValuewithBoolPattern(key bool, value interface{}) bool {
typedValue, ok := value.(bool)
if !ok {
glog.Error("Expected bool, found %V", value)
return false
}
return key != typedValue
}
func (neh NotEqualHandler) validateValuewithIntPattern(key int64, value interface{}) bool {
switch typedValue := value.(type) {
case int:
return int64(typedValue) != key
case int64:
return typedValue != key
case float64:
// check that float has no fraction
if typedValue == math.Trunc(typedValue) {
return int64(typedValue) != key
}
glog.Warningf("Expected int, found float: %f\n", typedValue)
return false
case string:
// extract in64 from string
int64Num, err := strconv.ParseInt(typedValue, 10, 64)
if err != nil {
glog.Warningf("Failed to parse int64 from string: %v", err)
return false
}
return int64Num != key
default:
glog.Warningf("Expected int, %v is of type %T", value, value)
return false
}
}

View file

@ -0,0 +1,30 @@
package operator
import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/context"
)
type OperatorHandler interface {
Evaluate(key, value interface{}) bool
validateValuewithBoolPattern(key bool, value interface{}) bool
validateValuewithIntPattern(key int64, value interface{}) bool
validateValuewithFloatPattern(key float64, value interface{}) bool
validateValueWithMapPattern(key map[string]interface{}, value interface{}) bool
validateValueWithSlicePattern(key []interface{}, value interface{}) bool
}
type VariableSubstitutionHandler = func(ctx context.EvalInterface, pattern interface{}) interface{}
func CreateOperatorHandler(ctx context.EvalInterface, op kyverno.ConditionOperator, subHandler VariableSubstitutionHandler) OperatorHandler {
switch op {
case kyverno.Equal:
return NewEqualHandler(ctx, subHandler)
case kyverno.NotEqual:
return NewNotEqualHandler(ctx, subHandler)
default:
glog.Errorf("unsupported operator: %s", string(op))
}
return nil
}

View file

@ -73,24 +73,65 @@ func substituteValue(ctx context.EvalInterface, valuePattern string) interface{}
func getValueQuery(ctx context.EvalInterface, valuePattern string) interface{} {
var emptyInterface interface{}
// extract variable {{<variable>}}
variableRegex := regexp.MustCompile("{{(.*)}}")
groups := variableRegex.FindStringSubmatch(valuePattern)
if len(groups) < 2 {
validRegex := regexp.MustCompile(`\{\{([^{}]*)\}\}`)
groups := validRegex.FindAllStringSubmatch(valuePattern, -1)
// can have multiple variables in a single value pattern
// var Map <variable,value>
varMap := getValues(ctx, groups)
if len(varMap) == 0 {
// there are no varaiables
// return the original value
return valuePattern
}
searchPath := groups[1]
// search for the path in ctx
variable, err := ctx.Query(searchPath)
if err != nil {
glog.V(4).Infof("variable substitution failed for query %s: %v", searchPath, err)
return emptyInterface
}
// only replace the value if returned value is scalar
if val, ok := variable.(string); ok {
newVal := strings.Replace(valuePattern, groups[0], val, -1)
// only substitute values if all the variable values are of type string
if isAllVarStrings(varMap) {
newVal := valuePattern
for key, value := range varMap {
if val, ok := value.(string); ok {
newVal = strings.Replace(newVal, key, val, -1)
}
}
return newVal
}
return variable
// we do not support mutliple substitution per statement for non-string types
for _, value := range varMap {
return value
}
return emptyInterface
}
// returns map of variables as keys and variable values as values
func getValues(ctx context.EvalInterface, groups [][]string) map[string]interface{} {
var emptyInterface interface{}
subs := map[string]interface{}{}
for _, group := range groups {
if len(group) == 2 {
// 0th is string
// 1st is the capture group
variable, err := ctx.Query(group[1])
if err != nil {
glog.V(4).Infof("variable substitution failed for query %s: %v", group[0], err)
subs[group[0]] = emptyInterface
continue
}
if variable == nil {
subs[group[0]] = emptyInterface
} else {
subs[group[0]] = variable
}
}
}
return subs
}
func isAllVarStrings(subVar map[string]interface{}) bool {
for _, value := range subVar {
if _, ok := value.(string); !ok {
return false
}
}
return true
}
func getOperator(pattern string) string {

View file

@ -76,6 +76,72 @@ func Test_variablesub1(t *testing.T) {
t.Error("result does not match")
}
}
func Test_variablesub_multiple(t *testing.T) {
patternMap := []byte(`
{
"kind": "ClusterRole",
"name": "ns-owner-{{request.object.metadata.namespace}}-{{request.userInfo.username}}-bindings",
"data": {
"rules": [
{
"apiGroups": [
""
],
"resources": [
"namespaces"
],
"verbs": [
"*"
],
"resourceNames": [
"{{request.object.metadata.name}}"
]
}
]
}
}
`)
resourceRaw := []byte(`
{
"metadata": {
"name": "temp",
"namespace": "n1"
},
"spec": {
"namespace": "n1",
"name": "temp1"
}
}
`)
// userInfo
userReqInfo := kyverno.RequestInfo{
AdmissionUserInfo: authenticationv1.UserInfo{
Username: "user1",
},
}
resultMap := []byte(`{"data":{"rules":[{"apiGroups":[""],"resourceNames":["temp"],"resources":["namespaces"],"verbs":["*"]}]},"kind":"ClusterRole","name":"ns-owner-n1-user1-bindings"}`)
var pattern, resource interface{}
json.Unmarshal(patternMap, &pattern)
json.Unmarshal(resourceRaw, &resource)
// context
ctx := context.NewContext()
ctx.AddResource(resourceRaw)
ctx.AddUserInfo(userReqInfo)
value := SubstituteVariables(ctx, pattern)
resultRaw, err := json.Marshal(value)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(resultMap, resultRaw) {
t.Log(string(resultMap))
t.Log(string(resultRaw))
t.Error("result does not match")
}
}
func Test_variablesubstitution(t *testing.T) {
patternMap := []byte(`
{

View file

@ -13,7 +13,6 @@ const timoutMins = 2
const timeout = time.Minute * timoutMins // 2 minutes
func (c *Controller) processGR(gr kyverno.GenerateRequest) error {
glog.V(4).Info("processGR cleanup")
// 1-Corresponding policy has been deleted
_, err := c.pLister.Get(gr.Spec.Policy)
if errors.IsNotFound(err) {
@ -25,13 +24,15 @@ func (c *Controller) processGR(gr kyverno.GenerateRequest) error {
if gr.Status.State == kyverno.Completed {
glog.V(4).Infof("checking if owner exists for gr %s", gr.Name)
if !ownerResourceExists(c.client, gr) {
if err := deleteGeneratedResources(c.client, gr); err != nil {
return err
}
glog.V(4).Infof("delete GR %s", gr.Name)
return c.control.Delete(gr.Name)
}
return nil
}
createTime := gr.GetCreationTimestamp()
glog.V(4).Infof("state %s", string(gr.Status.State))
if time.Since(createTime.UTC()) > timeout {
// the GR was in state ["",Failed] for more than timeout
glog.V(4).Infof("GR %s was not processed succesfully in %d minutes", gr.Name, timoutMins)
@ -44,9 +45,22 @@ func (c *Controller) processGR(gr kyverno.GenerateRequest) error {
func ownerResourceExists(client *dclient.Client, gr kyverno.GenerateRequest) bool {
_, err := client.GetResource(gr.Spec.Resource.Kind, gr.Spec.Resource.Namespace, gr.Spec.Resource.Name)
if err != nil {
glog.V(4).Info("cleanup Resource does not exits")
return false
}
glog.V(4).Info("cleanup Resource does exits")
return true
}
func deleteGeneratedResources(client *dclient.Client, gr kyverno.GenerateRequest) error {
for _, genResource := range gr.Status.GeneratedResources {
err := client.DeleteResource(genResource.Kind, genResource.Namespace, genResource.Name, false)
if errors.IsNotFound(err) {
glog.V(4).Infof("resource %s/%s/%s not found, will no delete", genResource.Kind, genResource.Namespace, genResource.Name)
continue
}
if err != nil {
return err
}
}
return nil
}

View file

@ -12,8 +12,11 @@ import (
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
dclient "github.com/nirmata/kyverno/pkg/dclient"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/informers"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
@ -44,6 +47,11 @@ type Controller struct {
pSynced cache.InformerSynced
// grSynced returns true if the generate request store has been synced at least once
grSynced cache.InformerSynced
// dyanmic sharedinformer factory
dynamicInformer dynamicinformer.DynamicSharedInformerFactory
//TODO: list of generic informers
// only support Namespaces for deletion of resource
nsInformer informers.GenericInformer
}
func NewController(
@ -51,13 +59,15 @@ func NewController(
client *dclient.Client,
pInformer kyvernoinformer.ClusterPolicyInformer,
grInformer kyvernoinformer.GenerateRequestInformer,
dynamicInformer dynamicinformer.DynamicSharedInformerFactory,
) *Controller {
c := Controller{
kyvernoClient: kyvernoclient,
client: client,
//TODO: do the math for worst case back off and make sure cleanup runs after that
// as we dont want a deleted GR to be re-queue
queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(1, 30), "generate-request-cleanup"),
queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(1, 30), "generate-request-cleanup"),
dynamicInformer: dynamicInformer,
}
c.control = Control{client: kyvernoclient}
c.enqueueGR = c.enqueue
@ -78,10 +88,30 @@ func NewController(
UpdateFunc: c.updateGR,
DeleteFunc: c.deleteGR,
}, 2*time.Minute)
//TODO: dynamic registration
// Only supported for namespaces
nsInformer := dynamicInformer.ForResource(client.DiscoveryClient.GetGVRFromKind("Namespace"))
c.nsInformer = nsInformer
c.nsInformer.Informer().AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{
DeleteFunc: c.deleteGenericResource,
}, 2*time.Minute)
return &c
}
func (c *Controller) deleteGenericResource(obj interface{}) {
r := obj.(*unstructured.Unstructured)
grs, err := c.grLister.GetGenerateRequestsForResource(r.GetKind(), r.GetNamespace(), r.GetName())
if err != nil {
glog.Errorf("failed to Generate Requests for resource %s/%s/%s: %v", r.GetKind(), r.GetNamespace(), r.GetName(), err)
return
}
// re-evaluate the GR as the resource was deleted
for _, gr := range grs {
c.enqueueGR(gr)
}
}
func (c *Controller) deletePolicy(obj interface{}) {
p, ok := obj.(*kyverno.ClusterPolicy)
if !ok {

View file

@ -12,8 +12,11 @@ import (
dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/policyviolation"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/informers"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
@ -48,6 +51,11 @@ type Controller struct {
grSynced cache.InformerSynced
// policy violation generator
pvGenerator policyviolation.GeneratorInterface
// dyanmic sharedinformer factory
dynamicInformer dynamicinformer.DynamicSharedInformerFactory
//TODO: list of generic informers
// only support Namespaces for re-evalutation on resource updates
nsInformer informers.GenericInformer
}
func NewController(
@ -57,6 +65,7 @@ func NewController(
grInformer kyvernoinformer.GenerateRequestInformer,
eventGen event.Interface,
pvGenerator policyviolation.GeneratorInterface,
dynamicInformer dynamicinformer.DynamicSharedInformerFactory,
) *Controller {
c := Controller{
client: client,
@ -65,7 +74,8 @@ func NewController(
pvGenerator: pvGenerator,
//TODO: do the math for worst case back off and make sure cleanup runs after that
// as we dont want a deleted GR to be re-queue
queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(1, 30), "generate-request"),
queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(1, 30), "generate-request"),
dynamicInformer: dynamicInformer,
}
c.statusControl = StatusControl{client: kyvernoclient}
@ -89,9 +99,31 @@ func NewController(
c.pSynced = pInformer.Informer().HasSynced
c.grSynced = pInformer.Informer().HasSynced
//TODO: dynamic registration
// Only supported for namespaces
nsInformer := dynamicInformer.ForResource(client.DiscoveryClient.GetGVRFromKind("Namespace"))
c.nsInformer = nsInformer
c.nsInformer.Informer().AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{
UpdateFunc: c.updateGenericResource,
}, 2*time.Minute)
return &c
}
func (c *Controller) updateGenericResource(old, cur interface{}) {
curR := cur.(*unstructured.Unstructured)
grs, err := c.grLister.GetGenerateRequestsForResource(curR.GetKind(), curR.GetNamespace(), curR.GetName())
if err != nil {
glog.Errorf("failed to Generate Requests for resource %s/%s/%s: %v", curR.GetKind(), curR.GetNamespace(), curR.GetName(), err)
return
}
// re-evaluate the GR as the resource was updated
for _, gr := range grs {
c.enqueueGR(gr)
}
}
func (c *Controller) enqueue(gr *kyverno.GenerateRequest) {
key, err := cache.MetaNamespaceKeyFunc(gr)
if err != nil {
@ -124,7 +156,6 @@ func (c *Controller) updatePolicy(old, cur interface{}) {
func (c *Controller) addGR(obj interface{}) {
gr := obj.(*kyverno.GenerateRequest)
// glog.V(4).Infof("Adding GR %s; Policy %s; Resource %v", gr.Name, gr.Spec.Policy, gr.Spec.Resource)
c.enqueueGR(gr)
}
@ -163,6 +194,7 @@ func (c *Controller) deleteGR(obj interface{}) {
c.enqueueGR(gr)
}
//Run ...
func (c *Controller) Run(workers int, stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
defer c.queue.ShutDown()

View file

@ -12,23 +12,24 @@ import (
"github.com/nirmata/kyverno/pkg/engine/variables"
"github.com/nirmata/kyverno/pkg/policyviolation"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
func (c *Controller) processGR(gr *kyverno.GenerateRequest) error {
var err error
var resource *unstructured.Unstructured
var genResources []kyverno.ResourceSpec
// 1 - Check if the resource exists
resource, err := getResource(c.client, gr.Spec.Resource)
resource, err = getResource(c.client, gr.Spec.Resource)
if err != nil {
// Dont update status
glog.V(4).Infof("resource does not exist or is yet to be created, requeuing: %v", err)
return err
}
glog.V(4).Infof("processGR %v", gr.Status.State)
// 2 - Apply the generate policy on the resource
err = c.applyGenerate(*resource, *gr)
genResources, err = c.applyGenerate(*resource, *gr)
switch e := err.(type) {
case *Violation:
// Generate event
@ -45,28 +46,28 @@ func (c *Controller) processGR(gr *kyverno.GenerateRequest) error {
reportEvents(err, c.eventGen, *gr, *resource)
// 4 - Update Status
return updateStatus(c.statusControl, *gr, err)
return updateStatus(c.statusControl, *gr, err, genResources)
}
func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyverno.GenerateRequest) error {
func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyverno.GenerateRequest) ([]kyverno.ResourceSpec, error) {
// Get the list of rules to be applied
// get policy
glog.V(4).Info("applyGenerate")
policy, err := c.pLister.Get(gr.Spec.Policy)
if err != nil {
glog.V(4).Infof("policy %s not found: %v", gr.Spec.Policy, err)
return nil
return nil, nil
}
// build context
ctx := context.NewContext()
resourceRaw, err := resource.MarshalJSON()
if err != nil {
glog.V(4).Infof("failed to marshal resource: %v", err)
return err
return nil, err
}
ctx.AddResource(resourceRaw)
ctx.AddUserInfo(gr.Spec.Context.UserRequestInfo)
ctx.AddSA(gr.Spec.Context.UserRequestInfo.AdmissionUserInfo.Username)
policyContext := engine.PolicyContext{
NewResource: resource,
@ -75,35 +76,34 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
AdmissionInfo: gr.Spec.Context.UserRequestInfo,
}
glog.V(4).Info("GenerateNew")
// check if the policy still applies to the resource
engineResponse := engine.GenerateNew(policyContext)
if len(engineResponse.PolicyResponse.Rules) == 0 {
glog.V(4).Infof("policy %s, dont not apply to resource %v", gr.Spec.Policy, gr.Spec.Resource)
return fmt.Errorf("policy %s, dont not apply to resource %v", gr.Spec.Policy, gr.Spec.Resource)
return nil, fmt.Errorf("policy %s, dont not apply to resource %v", gr.Spec.Policy, gr.Spec.Resource)
}
glog.V(4).Infof("%v", gr)
// Apply the generate rule on resource
return applyGeneratePolicy(c.client, policyContext, gr.Status.State)
}
func updateStatus(statusControl StatusControlInterface, gr kyverno.GenerateRequest, err error) error {
func updateStatus(statusControl StatusControlInterface, gr kyverno.GenerateRequest, err error, genResources []kyverno.ResourceSpec) error {
if err != nil {
return statusControl.Failed(gr, err.Error())
return statusControl.Failed(gr, err.Error(), genResources)
}
// Generate request successfully processed
return statusControl.Success(gr)
return statusControl.Success(gr, genResources)
}
func applyGeneratePolicy(client *dclient.Client, policyContext engine.PolicyContext, state kyverno.GenerateRequestState) error {
func applyGeneratePolicy(client *dclient.Client, policyContext engine.PolicyContext, state kyverno.GenerateRequestState) ([]kyverno.ResourceSpec, error) {
// List of generatedResources
var genResources []kyverno.ResourceSpec
// Get the response as the actions to be performed on the resource
// - DATA (rule.Generation.Data)
// - - substitute values
policy := policyContext.Policy
resource := policyContext.NewResource
ctx := policyContext.Context
glog.V(4).Info("applyGeneratePolicy")
// To manage existing resources, we compare the creation time for the default resiruce to be generated and policy creation time
processExisting := func() bool {
rcreationTime := resource.GetCreationTimestamp()
@ -115,17 +115,20 @@ func applyGeneratePolicy(client *dclient.Client, policyContext engine.PolicyCont
if !rule.HasGenerate() {
continue
}
if err := applyRule(client, rule, resource, ctx, state, processExisting); err != nil {
return err
genResource, err := applyRule(client, rule, resource, ctx, state, processExisting)
if err != nil {
return nil, err
}
genResources = append(genResources, genResource)
}
return nil
return genResources, nil
}
func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.Unstructured, ctx context.EvalInterface, state kyverno.GenerateRequestState, processExisting bool) error {
func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.Unstructured, ctx context.EvalInterface, state kyverno.GenerateRequestState, processExisting bool) (kyverno.ResourceSpec, error) {
var rdata map[string]interface{}
var err error
var noGenResource kyverno.ResourceSpec
// variable substitution
// - name
@ -133,8 +136,14 @@ func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.
// - clone.name
// - clone.namespace
gen := variableSubsitutionForAttributes(rule.Generation, ctx)
// Resource to be generated
newGenResource := kyverno.ResourceSpec{
Kind: gen.Kind,
Namespace: gen.Namespace,
Name: gen.Name,
}
// DATA
glog.V(4).Info("applyRule")
if gen.Data != nil {
if rdata, err = handleData(rule.Name, gen, client, resource, ctx, state); err != nil {
glog.V(4).Info(err)
@ -143,62 +152,57 @@ func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.
// handled errors
case *Violation:
// create policy violation
return e
return noGenResource, e
default:
// errors that cant be handled
return e
return noGenResource, e
}
}
if rdata == nil {
// existing resource contains the configuration
return nil
return newGenResource, nil
}
}
// CLONE
if gen.Clone != (kyverno.CloneFrom{}) {
if rdata, err = handleClone(gen, client, resource, ctx, state); err != nil {
glog.V(4).Info(err)
switch e := err.(type) {
case *NotFound:
// handled errors
return e
return noGenResource, e
default:
// errors that cant be handled
return e
return noGenResource, e
}
}
if rdata == nil {
// resource already exists
return nil
return newGenResource, nil
}
}
if processExisting {
// handle existing resources
// policy was generated after the resource
// we do not create new resource
return err
return noGenResource, err
}
// Create the generate resource
newResource := &unstructured.Unstructured{}
glog.V(4).Info(rdata)
newResource.SetUnstructuredContent(rdata)
newResource.SetName(gen.Name)
newResource.SetNamespace(gen.Namespace)
// Reset resource version
newResource.SetResourceVersion("")
// set the ownerReferences
ownerRefs := newResource.GetOwnerReferences()
// add ownerRefs
newResource.SetOwnerReferences(ownerRefs)
glog.V(4).Infof("creating resource %v", newResource)
_, err = client.CreateResource(gen.Kind, gen.Namespace, newResource, false)
if err != nil {
glog.Info(err)
return err
return noGenResource, err
}
glog.V(4).Infof("created new resource %s %s %s ", gen.Kind, gen.Namespace, gen.Name)
// New Resource created succesfully
return nil
return newGenResource, nil
}
func variableSubsitutionForAttributes(gen kyverno.Generation, ctx context.EvalInterface) kyverno.Generation {
@ -227,24 +231,9 @@ func variableSubsitutionForAttributes(gen kyverno.Generation, ctx context.EvalIn
if newcloneNamespace, ok := newcloneNamespaceVar.(string); ok {
gen.Clone.Namespace = newcloneNamespace
}
glog.V(4).Infof("var updated %v", gen.Name)
return gen
}
func createOwnerReference(ownerRefs []metav1.OwnerReference, resource unstructured.Unstructured) {
controllerFlag := true
blockOwnerDeletionFlag := true
ownerRef := metav1.OwnerReference{
APIVersion: resource.GetAPIVersion(),
Kind: resource.GetKind(),
Name: resource.GetName(),
UID: resource.GetUID(),
Controller: &controllerFlag,
BlockOwnerDeletion: &blockOwnerDeletionFlag,
}
ownerRefs = append(ownerRefs, ownerRef)
}
func handleData(ruleName string, generateRule kyverno.Generation, client *dclient.Client, resource unstructured.Unstructured, ctx context.EvalInterface, state kyverno.GenerateRequestState) (map[string]interface{}, error) {
newData := variables.SubstituteVariables(ctx, generateRule.Data)
@ -252,7 +241,6 @@ func handleData(ruleName string, generateRule kyverno.Generation, client *dclien
obj, err := client.GetResource(generateRule.Kind, generateRule.Namespace, generateRule.Name)
glog.V(4).Info(err)
if errors.IsNotFound(err) {
glog.V(4).Info("handleData NotFound")
glog.V(4).Info(string(state))
// Resource does not exist
if state == "" {
@ -270,7 +258,6 @@ func handleData(ruleName string, generateRule kyverno.Generation, client *dclien
// report Violation to notify the error
return nil, NewViolation(ruleName, NewNotFound(generateRule.Kind, generateRule.Namespace, generateRule.Name))
}
glog.V(4).Info(err)
if err != nil {
//something wrong while fetching resource
return nil, err
@ -293,12 +280,10 @@ func handleClone(generateRule kyverno.Generation, client *dclient.Client, resour
// check if resource exists
_, err := client.GetResource(generateRule.Kind, generateRule.Namespace, generateRule.Name)
if err == nil {
glog.V(4).Info("handleClone Exists")
// resource exists
return nil, nil
}
if !errors.IsNotFound(err) {
glog.V(4).Info("handleClone NotFound")
//something wrong while fetching resource
return nil, err
}
@ -306,15 +291,12 @@ func handleClone(generateRule kyverno.Generation, client *dclient.Client, resour
// get reference clone resource
obj, err := client.GetResource(generateRule.Kind, generateRule.Clone.Namespace, generateRule.Clone.Name)
if errors.IsNotFound(err) {
glog.V(4).Info("handleClone reference not Found")
return nil, NewNotFound(generateRule.Kind, generateRule.Clone.Namespace, generateRule.Clone.Name)
}
if err != nil {
glog.V(4).Info("handleClone reference Error")
//something wrong while fetching resource
return nil, err
}
glog.V(4).Info("handleClone refrerence sending")
return obj.UnstructuredContent(), nil
}

View file

@ -7,8 +7,8 @@ import (
)
type StatusControlInterface interface {
Failed(gr kyverno.GenerateRequest, message string) error
Success(gr kyverno.GenerateRequest) error
Failed(gr kyverno.GenerateRequest, message string, genResources []kyverno.ResourceSpec) error
Success(gr kyverno.GenerateRequest, genResources []kyverno.ResourceSpec) error
}
// StatusControl is default implementaation of GRStatusControlInterface
@ -17,10 +17,11 @@ type StatusControl struct {
}
//FailedGR sets gr status.state to failed with message
func (sc StatusControl) Failed(gr kyverno.GenerateRequest, message string) error {
func (sc StatusControl) Failed(gr kyverno.GenerateRequest, message string, genResources []kyverno.ResourceSpec) error {
gr.Status.State = kyverno.Failed
gr.Status.Message = message
// Update Generated Resources
gr.Status.GeneratedResources = genResources
_, err := sc.client.KyvernoV1().GenerateRequests("kyverno").UpdateStatus(&gr)
if err != nil {
glog.V(4).Infof("FAILED: updated gr %s status to %s", gr.Name, string(kyverno.Failed))
@ -31,9 +32,11 @@ func (sc StatusControl) Failed(gr kyverno.GenerateRequest, message string) error
}
// SuccessGR sets the gr status.state to completed and clears message
func (sc StatusControl) Success(gr kyverno.GenerateRequest) error {
func (sc StatusControl) Success(gr kyverno.GenerateRequest, genResources []kyverno.ResourceSpec) error {
gr.Status.State = kyverno.Completed
gr.Status.Message = ""
// Update Generated Resources
gr.Status.GeneratedResources = genResources
_, err := sc.client.KyvernoV1().GenerateRequests("kyverno").UpdateStatus(&gr)
if err != nil {

View file

@ -13,6 +13,7 @@ import (
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine/policy"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/policystore"
"github.com/nirmata/kyverno/pkg/policyviolation"
@ -159,9 +160,25 @@ func (pc *PolicyController) addPolicy(obj interface{}) {
// policy.spec.background -> "True"
// register with policy meta-store
pc.pMetaStore.Register(*p)
if !p.Spec.Background {
return
// TODO: code might seem vague, awaiting resolution of issue https://github.com/nirmata/kyverno/issues/598
if p.Spec.Background == nil {
// if userInfo is not defined in policy we process the policy
if err := policy.ContainsUserInfo(*p); err != nil {
return
}
} else {
if !*p.Spec.Background {
return
}
// If userInfo is used then skip the policy
// ideally this should be handled by background flag only
if err := policy.ContainsUserInfo(*p); err != nil {
// contains userInfo used in policy
return
}
}
glog.V(4).Infof("Adding Policy %s", p.Name)
pc.enqueuePolicy(p)
}
@ -176,8 +193,22 @@ func (pc *PolicyController) updatePolicy(old, cur interface{}) {
// Only process policies that are enabled for "background" execution
// policy.spec.background -> "True"
if !curP.Spec.Background {
return
// TODO: code might seem vague, awaiting resolution of issue https://github.com/nirmata/kyverno/issues/598
if curP.Spec.Background == nil {
// if userInfo is not defined in policy we process the policy
if err := policy.ContainsUserInfo(*curP); err != nil {
return
}
} else {
if !*curP.Spec.Background {
return
}
// If userInfo is used then skip the policy
// ideally this should be handled by background flag only
if err := policy.ContainsUserInfo(*curP); err != nil {
// contains userInfo used in policy
return
}
}
glog.V(4).Infof("Updating Policy %s", oldP.Name)
pc.enqueuePolicy(curP)

View file

@ -34,6 +34,8 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic
// load incoming resource into the context
// ctx.AddResource(request.Object.Raw)
ctx.AddUserInfo(userRequestInfo)
// load service account in context
ctx.AddSA(userRequestInfo.AdmissionUserInfo.Username)
policyContext := engine.PolicyContext{
NewResource: *resource,

View file

@ -64,6 +64,8 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest, resou
// load incoming resource into the context
ctx.AddResource(request.Object.Raw)
ctx.AddUserInfo(userRequestInfo)
ctx.AddSA(userRequestInfo.AdmissionUserInfo.Username)
policyContext := engine.PolicyContext{
NewResource: resource,
AdmissionInfo: userRequestInfo,

View file

@ -60,6 +60,12 @@ func generateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy, operation v1b
updateMsgs = append(updateMsgs, updateMsg)
}
// default 'Background'
if patch, updateMsg := defaultBackgroundFlag(policy); patch != nil {
patches = append(patches, patch)
updateMsgs = append(updateMsgs, updateMsg)
}
// TODO(shuting): enable this feature on policy UPDATE
if operation == v1beta1.Create {
patch, errs := generatePodControllerRule(*policy)
@ -77,6 +83,31 @@ func generateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy, operation v1b
return utils.JoinPatches(patches), updateMsgs
}
func defaultBackgroundFlag(policy *kyverno.ClusterPolicy) ([]byte, string) {
// default 'Background' flag to 'true' if not specified
defaultVal := true
if policy.Spec.Background == nil {
glog.V(4).Infof("default policy %s 'Background' to '%s'", policy.Name, strconv.FormatBool(true))
jsonPatch := struct {
Path string `json:"path"`
Op string `json:"op"`
Value *bool `json:"value"`
}{
"/spec/background",
"add",
&defaultVal,
}
patchByte, err := json.Marshal(jsonPatch)
if err != nil {
glog.Errorf("failed to set default 'Background' to '%s' for policy %s", strconv.FormatBool(true), policy.Name)
return nil, ""
}
glog.V(4).Infof("generate JSON Patch to set default 'Background' to '%s' for policy %s", strconv.FormatBool(true), policy.Name)
return patchByte, fmt.Sprintf("default 'Background' to '%s'", strconv.FormatBool(true))
}
return nil, ""
}
func defaultvalidationFailureAction(policy *kyverno.ClusterPolicy) ([]byte, string) {
// default ValidationFailureAction to "audit" if not specified
if policy.Spec.ValidationFailureAction == "" {

View file

@ -68,6 +68,7 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pol
// load incoming resource into the context
ctx.AddResource(request.Object.Raw)
ctx.AddUserInfo(userRequestInfo)
ctx.AddSA(userRequestInfo.AdmissionUserInfo.Username)
policyContext := engine.PolicyContext{
NewResource: newR,

View file

@ -24,6 +24,7 @@ spec:
generate:
kind: NetworkPolicy
name: default-deny-ingress
namespace: "{{request.object.metadata.name}}"
data:
spec:
# select all pods in the namespace

View file

@ -25,6 +25,7 @@ spec:
generate:
kind: ResourceQuota
name: default-resourcequota
namespace: "{{request.object.metadata.name}}"
data:
spec:
hard:
@ -40,6 +41,7 @@ spec:
generate:
kind: LimitRange
name: default-limitrange
namespace: "{{request.object.metadata.name}}"
data:
spec:
limits:

View file

@ -21,6 +21,7 @@ spec:
generate:
kind: NetworkPolicy
name: default-deny-ingress
namespace: "{{request.object.metadata.name}}"
data:
spec:
# select all pods in the namespace

View file

@ -17,6 +17,7 @@ spec:
generate:
kind: ResourceQuota
name: default-resourcequota
namespace: "{{request.object.metadata.name}}"
data:
spec:
hard:
@ -32,6 +33,7 @@ spec:
generate:
kind: LimitRange
name: default-limitrange
namespace: "{{request.object.metadata.name}}"
data:
spec:
limits:

View file

@ -12,15 +12,14 @@ spec:
resources:
kinds:
- Namespace
name: devtest
generate:
kind: ClusterRole
name: "ns-owner-{{request.userInfo.username}}"
name: "ns-owner-{{request.object.metadata.name}}-{{request.userInfo.username}}"
data:
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["*"]
verbs: ["delete"]
resourceNames:
- "{{request.object.metadata.name}}"
- name: generate-owner-role-binding
@ -28,28 +27,27 @@ spec:
resources:
kinds:
- Namespace
name: devtest
generate:
kind: ClusterRoleBinding
name: "ns-owner-{{request.userInfo.username}}-binding"
name: "ns-owner-{{request.object.metadata.name}}-{{request.userInfo.username}}-binding"
data:
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: "nsowner-{{request.userInfo.username}}"
name: "ns-owner-{{request.object.metadata.name}}-{{request.userInfo.username}}"
subjects:
- kind: ServiceAccount
name: "{{request.userInfo.username}}"
namespace: "{{request.object.metadata.name}}"
# pre-defined context value (removes the suffix system:serviceaccount:<namespace>:<name> from userName)
name: "{{serviceAccountName}}" # <name>
namespace: "{{serviceAccountNamespace}}" # <namespace>
- name: generate-admin-role-binding
match:
resources:
kinds:
- Namespace
name: devtest
generate:
kind: RoleBinding
name: "ns-admin-{{request.userInfo.username}}-binding"
name: "ns-admin-{{request.object.metadata.name}}-{{request.userInfo.username}}-binding"
namespace: "{{request.object.metadata.name}}"
data:
roleRef:
@ -58,5 +56,5 @@ spec:
name: admin
subjects:
- kind: ServiceAccount
name: "{{request.userInfo.username}}"
namespace: "{{request.object.metadata.name}}"
name: "{{serviceAccountName}}"
namespace: "{{serviceAccountNamespace}}"