Skip to content

Commit

Permalink
Merge pull request #1143 from realshuting/handle_array_in_notin
Browse files Browse the repository at this point in the history
Parse string value to array from configMap
  • Loading branch information
JimBugwadia committed Sep 23, 2020
2 parents bd406f5 + 82143a2 commit 06b3197
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 27 deletions.
69 changes: 56 additions & 13 deletions documentation/writing-policies-configmap-reference.md
Expand Up @@ -8,43 +8,86 @@ The Configmap Reference allows the reference of configmap values inside kyverno

# Defining Rule Context

To refer Configmap inside any Rule provide the context inside each rule defining the list of configmaps which will be referenced in that Rule.
To reference values from a ConfigMap inside any Rule, define a context inside the rule with one or more ConfigMap declarations.

```
````yaml
rules:
- name: add-sidecar-pod
- name: example-configmap-lookup
# added context to define the configmap information which will be referred
context:
# unique name to identify configmap
- name: mycmapRef
- name: dictionary
configMap:
# configmap name - name of the configmap which will be referred
name: mycmap
# configmap namepsace - namespace of the configmap which will be referred
```
namespace: test
````

Referenced Configmap Definition

```
````yaml
apiVersion: v1
data:
env: production, sandbox, staging
env: production
kind: ConfigMap
metadata:
name: mycmap
```
````

# Referring Value

The configmaps that are defined inside rule context can be referred using the unique name that is used to identify configmap inside context.
A ConfigMap that is defined inside the rule context can be referred to using its unique name within the context.

We can refer it's value using a JMESPATH
ConfigMap values can be references using a JMESPATH expression `{{<name>.<data>.<key>}}`.

`{{<name>.<data>.<key>}}`
For the example above, we can refer to a ConfigMap value using {{dictionary.data.env}}. The variable will be substituted with production during policy execution.

So for the above context we can refer it's value using
# Handling Array Values

`{{mycmapRef.data.env}}`
The ConfigMap value can be an array of string values in JSON format. Kyverno will parse the JSON string to a list of strings, so set operations like In and NotIn can then be applied.

For example, a list of allowed roles can be stored in a ConfigMap, and the Kyverno policy can refer to this list to deny the requests where the role does not match the one of the values in the list.

Here are the allowed roles in the ConfigMap
````yaml
apiVersion: v1
data:
allowed-roles: "[\"cluster-admin\", \"cluster-operator\", \"tenant-admin\"]"
kind: ConfigMap
metadata:
name: roles-dictionary
namespace: test
````


This is a rule to deny the Deployment operation, if the value of annotation `role` is not in the allowed list:
````yaml
spec:
validationFailureAction: enforce
rules:
- name: validate-role-annotation
context:
- name: roles-dictionary
configMap:
name: roles-dictionary
namespace: test
match:
resources:
kinds:
- Deployment
preconditions:
- key: "{{ request.object.metadata.annotations.role }}"
operator: NotEquals
value: ""
validate:
message: "role {{ request.object.metadata.annotations.role }} is not in the allowed list {{ \"roles-dictionary\".data.\"allowed-roles\" }}"
deny:
conditions:
- key: "{{ request.object.metadata.annotations.role }}"
operator: NotIn
value: "{{ \"roles-dictionary\".data.\"allowed-roles\" }}"
````



Expand Down
1 change: 0 additions & 1 deletion pkg/engine/mutate/patchesUtils.go
Expand Up @@ -29,7 +29,6 @@ func generatePatches(src, dst []byte) ([][]byte, error) {
}

patchesBytes = append(patchesBytes, pbytes)
// fmt.Printf("generated patch %s\n", p)
}

return patchesBytes, err
Expand Down
7 changes: 4 additions & 3 deletions pkg/engine/utils.go
Expand Up @@ -4,14 +4,15 @@ import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"time"

"github.com/go-logr/logr"
"github.com/nirmata/kyverno/pkg/utils"
authenticationv1 "k8s.io/api/authentication/v1"
rbacv1 "k8s.io/api/rbac/v1"
"reflect"
"sigs.k8s.io/controller-runtime/pkg/log"
"strings"
"time"

"github.com/minio/minio/pkg/wildcard"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
Expand Down
21 changes: 15 additions & 6 deletions pkg/engine/variables/operator/in.go
@@ -1,9 +1,9 @@
package operator

import (
"encoding/json"
"fmt"
"reflect"
"strings"

"github.com/go-logr/logr"
"github.com/nirmata/kyverno/pkg/engine/context"
Expand Down Expand Up @@ -48,7 +48,7 @@ func (in InHandler) Evaluate(key, value interface{}) bool {
}

func (in InHandler) validateValuewithStringPattern(key string, value interface{}) (keyExists bool) {
invalidType, keyExists := ValidateStringPattern(key, value)
invalidType, keyExists := ValidateStringPattern(key, value, in.log)
if invalidType {
in.log.Info("expected type []string", "value", value, "type", fmt.Sprintf("%T", value))
return false
Expand All @@ -57,7 +57,7 @@ func (in InHandler) validateValuewithStringPattern(key string, value interface{}
return keyExists
}

func ValidateStringPattern(key string, value interface{}) (invalidType bool, keyExists bool) {
func ValidateStringPattern(key string, value interface{}, log logr.Logger) (invalidType bool, keyExists bool) {
stringType := reflect.TypeOf("")
switch valuesAvaliable := value.(type) {
case []interface{}:
Expand All @@ -69,10 +69,19 @@ func ValidateStringPattern(key string, value interface{}) (invalidType bool, key
keyExists = true
}
}
// add to handle the configMap lookup, as configmap.data
// takes string-string map, when looking for a value of array
// data:
// key: "[\"value1\", \"value2\"]"
// it will first unmarshal it to string slice, then compare
case string:
valuesAvaliable = strings.TrimSpace(valuesAvaliable)
vars := strings.Split(valuesAvaliable, ",")
for _, val := range vars {
var arr []string
if err := json.Unmarshal([]byte(valuesAvaliable), &arr); err != nil {
log.Error(err, "failed to unmarshal to string slice", "value", value)
return invalidType, keyExists
}

for _, val := range arr {
if key == val {
keyExists = true
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/engine/variables/operator/notin.go
Expand Up @@ -47,7 +47,7 @@ func (nin NotInHandler) Evaluate(key, value interface{}) bool {
}

func (nin NotInHandler) validateValuewithStringPattern(key string, value interface{}) bool {
invalidType, keyExists := ValidateStringPattern(key, value)
invalidType, keyExists := ValidateStringPattern(key, value, nin.log)
if invalidType {
nin.log.Info("expected type []string", "value", value, "type", fmt.Sprintf("%T", value))
return false
Expand Down
6 changes: 3 additions & 3 deletions test/e2e/generate/utils.go
@@ -1,14 +1,15 @@
package generate

import (
"os"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
"os"
"sigs.k8s.io/yaml"
"time"
)

type E2EClient struct {
Expand Down Expand Up @@ -103,7 +104,6 @@ func (e2e *E2EClient) ListNamespacedResources(gvr schema.GroupVersionResource, n
func (e2e *E2EClient) CreateNamespacedResourceYaml(gvr schema.GroupVersionResource, namespace string, resourceData []byte) (*unstructured.Unstructured, error) {
resource := unstructured.Unstructured{}
err := yaml.Unmarshal(resourceData, &resource)
// fmt.Println(resource)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit 06b3197

Please sign in to comment.