1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-29 10:55:05 +00:00

Merge branch 'master' into 253_ValidationInMutationFlag_v2

This commit is contained in:
shravan 2020-01-15 19:45:16 +05:30
commit 520e675155
15 changed files with 232 additions and 61 deletions

View file

@ -12,6 +12,7 @@ REGISTRY=index.docker.io
REPO=$(REGISTRY)/nirmata/kyverno
IMAGE_TAG=$(GIT_VERSION)
GOOS ?= $(shell go env GOOS)
PACKAGE ?=github.com/nirmata/kyverno
LD_FLAGS="-s -w -X $(PACKAGE)/pkg/version.BuildVersion=$(GIT_VERSION) -X $(PACKAGE)/pkg/version.BuildHash=$(GIT_HASH) -X $(PACKAGE)/pkg/version.BuildTime=$(TIMESTAMP)"
##################################

View file

@ -520,10 +520,10 @@ spec:
serviceAccountName: kyverno-service-account
initContainers:
- name: kyverno-pre
image: nirmata/kyvernopre:v1.1.0
image: nirmata/kyvernopre:v1.1.1
containers:
- name: kyverno
image: nirmata/kyverno:v1.1.0
image: nirmata/kyverno:v1.1.1
args:
- "--filterK8Resources=[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*]"
# customize webhook timout

View file

@ -1,21 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: policyviolation
# change namespace below to create rolebinding for the namespace admin
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: policyviolation
subjects:
# configure below to access policy violation for the namespace admin
- kind: ServiceAccount
name: default
namespace: default
# - apiGroup: rbac.authorization.k8s.io
# kind: User
# name:
# - apiGroup: rbac.authorization.k8s.io
# kind: Group
# name:

View file

@ -116,12 +116,34 @@ Here is a script that generates a self-signed CA, a TLS certificate-key pair, an
# Configure a namespace admin to access policy violations
During Kyverno installation, it creates a ClusterRole `policyviolation` which has the `list,get,watch` operation on resource `policyviolations`. To grant access to a namespace admin, configure [definitions/rolebinding.yaml](../definitions/rolebinding.yaml) then apply to the cluster.
During Kyverno installation, it creates a ClusterRole `policyviolation` which has the `list,get,watch` operation on resource `policyviolations`. To grant access to a namespace admin, configure the following YAML file then apply to the cluster.
- Replace `metadata.namespace` with namespace of the admin
- Configure `subjects` field to bind admin's role to the ClusterRole `policyviolation`
````yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: policyviolation
# change namespace below to create rolebinding for the namespace admin
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: policyviolation
subjects:
# configure below to access policy violation for the namespace admin
- kind: ServiceAccount
name: default
namespace: default
# - apiGroup: rbac.authorization.k8s.io
# kind: User
# name:
# - apiGroup: rbac.authorization.k8s.io
# kind: Group
# name:
````
# Installing outside of the cluster (debug mode)
To build Kyverno in a development environment see: https://github.com/nirmata/kyverno/wiki/Building

View file

@ -98,7 +98,6 @@ Example userName=`system:serviceaccount:nirmata:user1` will store variable valu
- `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)
@ -113,5 +112,24 @@ Examples:
`{{request.object.metadata}}`
# PreConditions:
Apart from using `match` & `exclude` conditions on resource to filter which resources to apply the rule on, `preconditions` can be used to define custom filters.
```yaml
- name: generate-owner-role
match:
resources:
kinds:
- Namespace
preconditions:
- key: "{{request.userInfo.username}}"
operator: NotEqual
value: ""
```
In the above example, if the variable `{{request.userInfo.username}}` is blank then we dont apply the rule on resource.
Operators supported:
- Equal
- NotEqual
---
<small>*Read Next >> [Validate](/documentation/writing-policies-validate.md)*</small>

View file

@ -387,11 +387,11 @@ func preparePath(path string) string {
}
annPath := "/metadata/annotations/"
idx := strings.Index(path, annPath)
// escape slash in annotation patch
if strings.Contains(path, annPath) {
idx := strings.Index(path, annPath)
p := path[idx+len(annPath):]
path = annPath + strings.ReplaceAll(p, "/", "~1")
path = path[:idx+len(annPath)] + strings.ReplaceAll(p, "/", "~1")
}
return path
}

View file

@ -26,8 +26,10 @@ func ContainsUserInfo(policy kyverno.ClusterPolicy) error {
// - validate.pattern
// - validate.anyPattern[*]
// variables to filter
// - request.userInfo
filterVars := []string{"request.userInfo*"}
// - request.userInfo*
// - serviceAccountName
// - serviceAccountNamespace
filterVars := []string{"request.userInfo*", "serviceAccountName", "serviceAccountNamespace"}
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)

View file

@ -1434,3 +1434,81 @@ func Test_BackGroundUserInfo_validate_anyPattern(t *testing.T) {
t.Error("Incorrect Path")
}
}
func Test_BackGroundUserInfo_validate_anyPattern_multiple_var(t *testing.T) {
var err error
rawPolicy := []byte(`
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "disallow-root-user"
},
"spec": {
"rules": [
{
"name": "validate.anyPattern",
"validate": {
"anyPattern": [
{
"var1": "temp"
},
{
"var1": "{{request.userInfo}}-{{temp}}"
}
]
}
}
]
}
} `)
var policy *kyverno.ClusterPolicy
err = json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
if err.Error() != "path: spec/rules[0]/validate/anyPattern[1]/var1/{{request.userInfo}}-{{temp}}" {
t.Log(err)
t.Error("Incorrect Path")
}
}
func Test_BackGroundUserInfo_validate_anyPattern_serviceAccount(t *testing.T) {
var err error
rawPolicy := []byte(`
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "disallow-root-user"
},
"spec": {
"rules": [
{
"name": "validate.anyPattern",
"validate": {
"anyPattern": [
{
"var1": "temp"
},
{
"var1": "{{serviceAccountName}}"
}
]
}
}
]
}
} `)
var policy *kyverno.ClusterPolicy
err = json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
err = ContainsUserInfo(*policy)
if err.Error() != "path: spec/rules[0]/validate/anyPattern[1]/var1/{{serviceAccountName}}" {
t.Log(err)
t.Error("Incorrect Path")
}
}

View file

@ -13,25 +13,27 @@ import (
func ValidateVariables(ctx context.EvalInterface, pattern interface{}) string {
var pathsNotPresent []string
variableList := extractVariables(pattern)
for i := 0; i < len(variableList)-1; i = i + 2 {
p := variableList[i+1]
glog.V(3).Infof("validating variables %s", p)
val, err := ctx.Query(p)
// reference path is not present
if err == nil && val == nil {
pathsNotPresent = append(pathsNotPresent, p)
for _, variable := range variableList {
if len(variable) == 2 {
varName := variable[0]
varValue := variable[1]
glog.V(3).Infof("validating variable %s", varName)
val, err := ctx.Query(varValue)
if err == nil && val == nil {
// path is not present, returns nil interface
pathsNotPresent = append(pathsNotPresent, varValue)
}
}
}
if len(variableList) != 0 && len(pathsNotPresent) != 0 {
if len(pathsNotPresent) != 0 {
return strings.Join(pathsNotPresent, ";")
}
return ""
}
// extractVariables extracts variables in the pattern
func extractVariables(pattern interface{}) []string {
func extractVariables(pattern interface{}) [][]string {
switch typedPattern := pattern.(type) {
case map[string]interface{}:
return extractMap(typedPattern)
@ -44,8 +46,8 @@ func extractVariables(pattern interface{}) []string {
}
}
func extractMap(patternMap map[string]interface{}) []string {
var variableList []string
func extractMap(patternMap map[string]interface{}) [][]string {
var variableList [][]string
for _, patternElement := range patternMap {
if vars := extractVariables(patternElement); vars != nil {
@ -55,8 +57,8 @@ func extractMap(patternMap map[string]interface{}) []string {
return variableList
}
func extractArray(patternList []interface{}) []string {
var variableList []string
func extractArray(patternList []interface{}) [][]string {
var variableList [][]string
for _, patternElement := range patternList {
if vars := extractVariables(patternElement); vars != nil {
@ -66,17 +68,22 @@ func extractArray(patternList []interface{}) []string {
return variableList
}
func extractValue(valuePattern string) []string {
func extractValue(valuePattern string) [][]string {
operatorVariable := getOperator(valuePattern)
variable := valuePattern[len(operatorVariable):]
return extractValueVariable(variable)
}
func extractValueVariable(valuePattern string) []string {
// returns multiple variable match groups
func extractValueVariable(valuePattern string) [][]string {
variableRegex := regexp.MustCompile(variableRegex)
groups := variableRegex.FindStringSubmatch(valuePattern)
if len(groups)%2 != 0 {
groups := variableRegex.FindAllStringSubmatch(valuePattern, -1)
if len(groups) == 0 {
// no variables
return nil
}
// group[*] <- all the matches
// group[*][0] <- group match
// group[*][1] <- group capture value
return groups
}

View file

@ -40,7 +40,9 @@ func Test_ExtractVariables(t *testing.T) {
json.Unmarshal(patternRaw, &pattern)
vars := extractVariables(pattern)
result := []string{"{{request.userInfo.username}}", "request.userInfo.username", "{{request.object.metadata.name}}", "request.object.metadata.name"}
result := [][]string{[]string{"{{request.userInfo.username}}", "request.userInfo.username"},
[]string{"{{request.object.metadata.name}}", "request.object.metadata.name"}}
assert.Assert(t, len(vars) == len(result), fmt.Sprintf("result does not match, var: %s", vars))
}

View file

@ -110,17 +110,18 @@ func getValues(ctx context.EvalInterface, groups [][]string) map[string]interfac
for _, group := range groups {
if len(group) == 2 {
// 0th is string
// 1st is the capture group
variable, err := ctx.Query(group[1])
varName := group[0]
varValue := group[1]
variable, err := ctx.Query(varValue)
if err != nil {
glog.V(4).Infof("variable substitution failed for query %s: %v", group[0], err)
subs[group[0]] = emptyInterface
glog.V(4).Infof("variable substitution failed for query %s: %v", varName, err)
subs[varName] = emptyInterface
continue
}
if variable == nil {
subs[group[0]] = emptyInterface
subs[varName] = emptyInterface
} else {
subs[group[0]] = variable
subs[varName] = variable
}
}
}

View file

@ -50,11 +50,22 @@ func checkValue(valuePattern string, variables []string, path string) error {
func checkValueVariable(valuePattern string, variables []string) bool {
variableRegex := regexp.MustCompile(variableRegex)
groups := variableRegex.FindStringSubmatch(valuePattern)
if len(groups) < 2 {
groups := variableRegex.FindAllStringSubmatch(valuePattern, -1)
if len(groups) == 0 {
// no variables
return false
}
return variablePatternSearch(groups[1], variables)
// if variables are defined, check against the list of variables to be filtered
for _, group := range groups {
if len(group) == 2 {
// group[0] -> {{variable}}
// group[1] -> variable
if variablePatternSearch(group[1], variables) {
return true
}
}
}
return false
}
func variablePatternSearch(pattern string, regexs []string) bool {

View file

@ -92,7 +92,7 @@ const (
func processResourceWithPatches(patch []byte, resource []byte) []byte {
if patch == nil {
return nil
return resource
}
resource, err := engineutils.ApplyPatchNew(resource, patch)

View file

@ -33,7 +33,7 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic
// build context
ctx := context.NewContext()
// load incoming resource into the context
// ctx.AddResource(request.Object.Raw)
ctx.AddResource(request.Object.Raw)
ctx.AddUserInfo(userRequestInfo)
// load service account in context
ctx.AddSA(userRequestInfo.AdmissionUserInfo.Username)

View file

@ -6,16 +6,40 @@ metadata:
policies.kyverno.io/category: Workload Isolation
policies.kyverno.io/description: Create roles and role bindings for a new namespace
spec:
background: false
rules:
- name: add-sa-annotation
match:
resources:
kinds:
- Namespace
mutate:
overlay:
metadata:
annotations:
nirmata.io/ns-creator: "{{serviceAccountName}}"
- name: generate-owner-role
match:
resources:
kinds:
- Namespace
preconditions:
- key: "{{request.userInfo.username}}"
operator: NotEqual
value: ""
- key: "{{serviceAccountName}}"
operator: NotEqual
value: ""
- key: "{{serviceAccountNamespace}}"
operator: NotEqual
value: ""
generate:
kind: ClusterRole
name: "ns-owner-{{request.object.metadata.name}}-{{request.userInfo.username}}"
data:
metadata:
annotations:
nirmata.io/ns-creator: "{{serviceAccountName}}"
rules:
- apiGroups: [""]
resources: ["namespaces"]
@ -27,10 +51,23 @@ spec:
resources:
kinds:
- Namespace
preconditions:
- key: "{{request.userInfo.username}}"
operator: NotEqual
value: ""
- key: "{{serviceAccountName}}"
operator: NotEqual
value: ""
- key: "{{serviceAccountNamespace}}"
operator: NotEqual
value: ""
generate:
kind: ClusterRoleBinding
name: "ns-owner-{{request.object.metadata.name}}-{{request.userInfo.username}}-binding"
data:
metadata:
annotations:
nirmata.io/ns-creator: "{{serviceAccountName}}"
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
@ -45,11 +82,24 @@ spec:
resources:
kinds:
- Namespace
preconditions:
- key: "{{request.userInfo.username}}"
operator: NotEqual
value: ""
- key: "{{serviceAccountName}}"
operator: NotEqual
value: ""
- key: "{{serviceAccountNamespace}}"
operator: NotEqual
value: ""
generate:
kind: RoleBinding
name: "ns-admin-{{request.object.metadata.name}}-{{request.userInfo.username}}-binding"
namespace: "{{request.object.metadata.name}}"
data:
metadata:
annotations:
nirmata.io/ns-creator: "{{serviceAccountName}}"
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
@ -57,4 +107,4 @@ spec:
subjects:
- kind: ServiceAccount
name: "{{serviceAccountName}}"
namespace: "{{serviceAccountNamespace}}"
namespace: "{{serviceAccountNamespace}}"