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:
commit
520e675155
15 changed files with 232 additions and 61 deletions
1
Makefile
1
Makefile
|
@ -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)"
|
||||
|
||||
##################################
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}}"
|
Loading…
Add table
Reference in a new issue