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:
parent
8d2866a29f
commit
3cf9141f4d
39 changed files with 1467 additions and 118 deletions
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
28
pkg/engine/variables/evaluate.go
Normal file
28
pkg/engine/variables/evaluate.go
Normal 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
|
||||
}
|
518
pkg/engine/variables/evaluate_test.go
Normal file
518
pkg/engine/variables/evaluate_test.go
Normal 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")
|
||||
}
|
||||
}
|
139
pkg/engine/variables/operator/equal.go
Normal file
139
pkg/engine/variables/operator/equal.go
Normal 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
|
||||
}
|
||||
}
|
139
pkg/engine/variables/operator/notequal.go
Normal file
139
pkg/engine/variables/operator/notequal.go
Normal 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
|
||||
}
|
||||
}
|
30
pkg/engine/variables/operator/operator.go
Normal file
30
pkg/engine/variables/operator/operator.go
Normal 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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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(`
|
||||
{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 == "" {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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}}"
|
||||
|
|
Loading…
Add table
Reference in a new issue