mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
527 resolved merge conflicts
This commit is contained in:
commit
36e775edb0
69 changed files with 99030 additions and 3080 deletions
4
Makefile
4
Makefile
|
@ -73,9 +73,9 @@ docker-push-kyverno:
|
||||||
##################################
|
##################################
|
||||||
# CLI
|
# CLI
|
||||||
##################################
|
##################################
|
||||||
CLI_PATH := cmd/cli
|
CLI_PATH := cmd/cli/kubectl-kyverno
|
||||||
cli:
|
cli:
|
||||||
GOOS=$(GOOS) go build -o $(PWD)/$(CLI_PATH)/kyvernocli -ldflags=$(LD_FLAGS) $(PWD)/$(CLI_PATH)/main.go
|
GOOS=$(GOOS) go build -o $(PWD)/$(CLI_PATH)/kyverno -ldflags=$(LD_FLAGS) $(PWD)/$(CLI_PATH)/main.go
|
||||||
|
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
|
@ -129,6 +129,7 @@ Refer to a list of curated of ***[sample policies](/samples/README.md)*** that c
|
||||||
* [Background Processing](documentation/writing-policies-background.md)
|
* [Background Processing](documentation/writing-policies-background.md)
|
||||||
* [Testing Policies](documentation/testing-policies.md)
|
* [Testing Policies](documentation/testing-policies.md)
|
||||||
* [Policy Violations](documentation/policy-violations.md)
|
* [Policy Violations](documentation/policy-violations.md)
|
||||||
|
* [Kyverno CLI](documentation/kyverno-cli.md)
|
||||||
* [Sample Policies](/samples/README.md)
|
* [Sample Policies](/samples/README.md)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
goflag "flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/nirmata/kyverno/pkg/config"
|
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/kyverno"
|
|
||||||
flag "github.com/spf13/pflag"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cmd := kyverno.NewDefaultKyvernoCommand()
|
|
||||||
if err := cmd.Execute(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
|
|
||||||
config.LogDefaultFlags()
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
|
@ -6,6 +6,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -44,6 +46,11 @@ func main() {
|
||||||
glog.Fatalf("Error creating client: %v\n", err)
|
glog.Fatalf("Error creating client: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exit for unsupported version of kubernetes cluster
|
||||||
|
// https://github.com/nirmata/kyverno/issues/700
|
||||||
|
// - supported from v1.12.7+
|
||||||
|
isVersionSupported(client)
|
||||||
|
|
||||||
requests := []request{
|
requests := []request{
|
||||||
// Resource
|
// Resource
|
||||||
{validatingWebhookConfigKind, config.ValidatingWebhookConfigurationName},
|
{validatingWebhookConfigKind, config.ValidatingWebhookConfigurationName},
|
||||||
|
@ -206,3 +213,32 @@ func merge(done <-chan struct{}, stopCh <-chan struct{}, processes ...<-chan err
|
||||||
}()
|
}()
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isVersionSupported(client *client.Client) {
|
||||||
|
serverVersion, err := client.DiscoveryClient.GetServerVersion()
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to get kubernetes server version: %v\n", err)
|
||||||
|
}
|
||||||
|
exp := regexp.MustCompile(`v(\d*).(\d*).(\d*)`)
|
||||||
|
groups := exp.FindAllStringSubmatch(serverVersion.String(), -1)
|
||||||
|
if len(groups) != 1 || len(groups[0]) != 4 {
|
||||||
|
glog.Fatalf("Failed to extract kubernetes server version: %v.err %v\n", serverVersion, err)
|
||||||
|
}
|
||||||
|
// convert string to int
|
||||||
|
// assuming the version are always intergers
|
||||||
|
major, err := strconv.Atoi(groups[0][1])
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to extract kubernetes major server version: %v.err %v\n", serverVersion, err)
|
||||||
|
}
|
||||||
|
minor, err := strconv.Atoi(groups[0][2])
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to extract kubernetes minor server version: %v.err %v\n", serverVersion, err)
|
||||||
|
}
|
||||||
|
sub, err := strconv.Atoi(groups[0][3])
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to extract kubernetes sub minor server version:%v. err %v\n", serverVersion, err)
|
||||||
|
}
|
||||||
|
if major <= 1 && minor <= 12 && sub < 7 {
|
||||||
|
glog.Fatalf("Unsupported kubernetes server version %s. Kyverno is supported from version v1.12.7+", serverVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
97413
data/swaggerDoc.go
Normal file
97413
data/swaggerDoc.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -30,6 +30,8 @@ spec:
|
||||||
enum:
|
enum:
|
||||||
- enforce # blocks the resorce api-reques if a rule fails.
|
- enforce # blocks the resorce api-reques if a rule fails.
|
||||||
- audit # allows resource creation and reports the failed validation rules as violations. Default
|
- audit # allows resource creation and reports the failed validation rules as violations. Default
|
||||||
|
background:
|
||||||
|
type: boolean
|
||||||
rules:
|
rules:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
@ -468,27 +470,179 @@ metadata:
|
||||||
namespace: kyverno
|
namespace: kyverno
|
||||||
---
|
---
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
metadata:
|
metadata:
|
||||||
name: kyverno-admin
|
name: kyverno:webhook
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
name: cluster-admin
|
name: kyverno:webhook
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: kyverno-service-account
|
name: kyverno-service-account
|
||||||
namespace: kyverno
|
namespace: kyverno
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: kyverno:userinfo
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: kyverno:userinfo
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: kyverno-service-account
|
||||||
|
namespace: kyverno
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: kyverno:customresources
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: kyverno:customresources
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: kyverno-service-account
|
||||||
|
namespace: kyverno
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: kyverno:policycontroller
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: kyverno:policycontroller
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: kyverno-service-account
|
||||||
|
namespace: kyverno
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: kyverno:generatecontroller
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: kyverno:generatecontroller
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: kyverno-service-account
|
||||||
|
namespace: kyverno
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
metadata:
|
metadata:
|
||||||
name: policyviolation
|
name: kyverno:webhook
|
||||||
rules:
|
rules:
|
||||||
- apiGroups: ["kyverno.io"]
|
# Dynamic creation of webhooks, events & certs
|
||||||
|
- apiGroups:
|
||||||
|
- '*'
|
||||||
resources:
|
resources:
|
||||||
|
- events
|
||||||
|
- mutatingwebhookconfigurations
|
||||||
|
- validatingwebhookconfigurations
|
||||||
|
- certificatesigningrequests
|
||||||
|
- certificatesigningrequests/approval
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: kyverno:userinfo
|
||||||
|
rules:
|
||||||
|
# get the roleRef for incoming api-request user
|
||||||
|
- apiGroups:
|
||||||
|
- "*"
|
||||||
|
resources:
|
||||||
|
- rolebindings
|
||||||
|
- clusterrolebindings
|
||||||
|
- configmaps
|
||||||
|
verbs:
|
||||||
|
- watch
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: kyverno:customresources
|
||||||
|
rules:
|
||||||
|
# Kyverno CRs
|
||||||
|
- apiGroups:
|
||||||
|
- '*'
|
||||||
|
resources:
|
||||||
|
- clusterpolicies
|
||||||
|
- clusterpolicies/status
|
||||||
|
- clusterpolicyviolations
|
||||||
|
- clusterpolicyviolations/status
|
||||||
- policyviolations
|
- policyviolations
|
||||||
verbs: ["get", "list", "watch"]
|
- policyviolations/status
|
||||||
|
- generaterequests
|
||||||
|
- generaterequests/status
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: kyverno:policycontroller
|
||||||
|
rules:
|
||||||
|
# background processing, identify all existing resources
|
||||||
|
- apiGroups:
|
||||||
|
- '*'
|
||||||
|
resources:
|
||||||
|
- '*'
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: kyverno:generatecontroller
|
||||||
|
rules:
|
||||||
|
# process generate rules to generate resources
|
||||||
|
- apiGroups:
|
||||||
|
- "*"
|
||||||
|
resources:
|
||||||
|
- namespaces
|
||||||
|
- networkpolicies
|
||||||
|
- secrets
|
||||||
|
- configmaps
|
||||||
|
- resourcequotas
|
||||||
|
- limitranges
|
||||||
|
- clusterroles
|
||||||
|
- rolebindings
|
||||||
|
- clusterrolebindings
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
# dynamic watches on trigger resources for generate rules
|
||||||
|
# re-evaluate the policy if the resource is updated
|
||||||
|
- apiGroups:
|
||||||
|
- '*'
|
||||||
|
resources:
|
||||||
|
- namespaces
|
||||||
|
verbs:
|
||||||
|
- watch
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
|
@ -519,10 +673,10 @@ spec:
|
||||||
serviceAccountName: kyverno-service-account
|
serviceAccountName: kyverno-service-account
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: kyverno-pre
|
- name: kyverno-pre
|
||||||
image: nirmata/kyvernopre:v1.1.2
|
image: nirmata/kyvernopre:v1.1.3
|
||||||
containers:
|
containers:
|
||||||
- name: kyverno
|
- name: kyverno
|
||||||
image: nirmata/kyverno:v1.1.2
|
image: nirmata/kyverno:v1.1.3
|
||||||
args:
|
args:
|
||||||
- "--filterK8Resources=[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*]"
|
- "--filterK8Resources=[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*]"
|
||||||
# customize webhook timout
|
# customize webhook timout
|
||||||
|
|
|
@ -30,6 +30,8 @@ spec:
|
||||||
enum:
|
enum:
|
||||||
- enforce # blocks the resorce api-reques if a rule fails.
|
- enforce # blocks the resorce api-reques if a rule fails.
|
||||||
- audit # allows resource creation and reports the failed validation rules as violations. Default
|
- audit # allows resource creation and reports the failed validation rules as violations. Default
|
||||||
|
background:
|
||||||
|
type: boolean
|
||||||
rules:
|
rules:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
|
|
@ -81,7 +81,57 @@ Secret | Data | Content
|
||||||
|
|
||||||
Kyverno uses secrets created above to setup TLS communication with the kube-apiserver and specify the CA bundle to be used to validate the webhook server's certificate in the admission webhook configurations.
|
Kyverno uses secrets created above to setup TLS communication with the kube-apiserver and specify the CA bundle to be used to validate the webhook server's certificate in the admission webhook configurations.
|
||||||
|
|
||||||
### 3. Install Kyverno
|
### 3. Configure Kyverno Role
|
||||||
|
Kyverno, in `foreground` mode, leverages admission webhooks to manage incoming api-requests, and `background` mode applies the policies on existing resources. It uses ServiceAccount `kyverno-service-account`, which is bound to multiple ClusterRole, which defines the default resources and operations that are permitted.
|
||||||
|
|
||||||
|
ClusterRoles used by kyverno:
|
||||||
|
- kyverno:webhook
|
||||||
|
- kyverno:userinfo
|
||||||
|
- kyverno:customresources
|
||||||
|
- kyverno:policycontroller
|
||||||
|
- kyverno:generatecontroller
|
||||||
|
|
||||||
|
The `generate` rule creates a new resource, and to allow kyverno to create resource kyverno ClusterRole needs permissions to create/update/delete. This can be done by adding the resource to the ClusterRole `kyverno:generatecontroller` used by kyverno or by creating a new ClusterRole and a ClusterRoleBinding to kyverno's default ServiceAccount.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: kyverno:generatecontroller
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- "*"
|
||||||
|
resources:
|
||||||
|
- namespaces
|
||||||
|
- networkpolicies
|
||||||
|
- secrets
|
||||||
|
- configmaps
|
||||||
|
- resourcequotas
|
||||||
|
- limitranges
|
||||||
|
- ResourceA # new Resource to be generated
|
||||||
|
- ResourceB
|
||||||
|
verbs:
|
||||||
|
- create # generate new resources
|
||||||
|
- get # check the contents of exiting resources
|
||||||
|
- update # update existing resource, if required configuration defined in policy is not present
|
||||||
|
- delete # clean-up, if the generate trigger resource is deleted
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: kyverno-admin-generate
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: kyverno:generatecontroller # clusterRole defined above, to manage generated resources
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: kyverno-service-account # default kyverno serviceAccount
|
||||||
|
namespace: kyverno
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Install Kyverno
|
||||||
|
|
||||||
To install a specific version, change the image tag with git tag in `install.yaml`.
|
To install a specific version, change the image tag with git tag in `install.yaml`.
|
||||||
|
|
||||||
|
|
60
documentation/kyverno-cli.md
Normal file
60
documentation/kyverno-cli.md
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<small>*[documentation](/README.md#documentation) / kyverno-cli*</small>
|
||||||
|
|
||||||
|
|
||||||
|
# Kyverno CLI
|
||||||
|
|
||||||
|
The Kyverno Command Line Interface (CLI) is designed to validate policies and test the behavior of applying policies to resources before adding the policy to a cluster. It can be used as a kubectl plugin and as a standalone CLI.
|
||||||
|
|
||||||
|
## Build the CLI
|
||||||
|
|
||||||
|
You can build the CLI binary locally, then move the binary into a directory in your PATH.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/nirmata/kyverno.git
|
||||||
|
cd github.com/nirmata/kyverno
|
||||||
|
make cli
|
||||||
|
mv ./cmd/cli/kubectl-kyverno/kyverno /usr/local/bin/kyverno
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
#### Version
|
||||||
|
|
||||||
|
Prints the version of kyverno used by the CLI.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
kyverno version
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Validate
|
||||||
|
Validates a policy, can validate multiple policy resource description files or even an entire folder containing policy resource description
|
||||||
|
files. Currently supports files with resource description in yaml.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
kyverno validate /path/to/policy1.yaml /path/to/policy2.yaml /path/to/folderFullOfPolicies
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Apply
|
||||||
|
Applies policies on resources, and supports applying multiple policies on multiple resources in a single command.
|
||||||
|
Also supports applying the given policies to an entire cluster. The current kubectl context will be used to access the cluster.
|
||||||
|
Will return results to stdout.
|
||||||
|
|
||||||
|
Apply to a resource:
|
||||||
|
```
|
||||||
|
kyverno apply /path/to/policy.yaml --resource /path/to/resource.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply to all matching resources in a cluster:
|
||||||
|
```
|
||||||
|
kyverno apply /path/to/policy.yaml --cluster > policy-results.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply multiple policies to multiple resources:
|
||||||
|
```
|
||||||
|
kyverno apply /path/to/policy1.yaml /path/to/folderFullOfPolicies --resource /path/to/resource1.yaml --resource /path/to/resource2.yaml --cluster
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
<small>*Read Next >> [Sample Policies](/samples/README.md)*</small>
|
|
@ -26,3 +26,6 @@ docker validation-example2-gzfdf validation-example2 Deployment com
|
||||||
# Cluster Policy Violations
|
# Cluster Policy Violations
|
||||||
|
|
||||||
Cluster Policy Violations are like Policy Violations but created for cluster-wide resources.
|
Cluster Policy Violations are like Policy Violations but created for cluster-wide resources.
|
||||||
|
|
||||||
|
|
||||||
|
<small>*Read Next >> [Kyverno CLI](/documentation/kyverno-cli.md)*</small>
|
||||||
|
|
|
@ -22,4 +22,9 @@ Then compare the original resource definition in CM.yaml with the actual one:
|
||||||
kubectl get -f CM.yaml -o yaml
|
kubectl get -f CM.yaml -o yaml
|
||||||
````
|
````
|
||||||
|
|
||||||
|
## Test using Kyverno CLI
|
||||||
|
|
||||||
|
The Kyverno CLI allows testing policies before they are applied to a cluster. It is documented at [Kyverno CLI](kyverno-cli.md)
|
||||||
|
|
||||||
|
|
||||||
<small>*Read Next >> [Policy Violations](/documentation/policy-violations.md)*</small>
|
<small>*Read Next >> [Policy Violations](/documentation/policy-violations.md)*</small>
|
||||||
|
|
10
go.mod
10
go.mod
|
@ -8,15 +8,17 @@ require (
|
||||||
github.com/gogo/protobuf v1.3.1 // indirect
|
github.com/gogo/protobuf v1.3.1 // indirect
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 // indirect
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 // indirect
|
||||||
github.com/googleapis/gnostic v0.3.1 // indirect
|
github.com/googleapis/gnostic v0.3.1
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.3 // indirect
|
github.com/hashicorp/golang-lru v0.5.3 // indirect
|
||||||
github.com/imdario/mergo v0.3.8 // indirect
|
github.com/imdario/mergo v0.3.8 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
|
||||||
github.com/json-iterator/go v1.1.9 // indirect
|
github.com/json-iterator/go v1.1.9 // indirect
|
||||||
github.com/minio/minio v0.0.0-20200114012931-30922148fbb5
|
github.com/minio/minio v0.0.0-20200114012931-30922148fbb5
|
||||||
github.com/ory/go-acc v0.1.0 // indirect
|
github.com/ory/go-acc v0.1.0 // indirect
|
||||||
|
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||||
github.com/spf13/cobra v0.0.5
|
github.com/spf13/cobra v0.0.5
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5
|
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5
|
||||||
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 // indirect
|
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 // indirect
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
|
||||||
|
@ -29,10 +31,12 @@ require (
|
||||||
gotest.tools v2.2.0+incompatible
|
gotest.tools v2.2.0+incompatible
|
||||||
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
|
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
|
||||||
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
|
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
|
||||||
|
k8s.io/cli-runtime v0.0.0-20191004110135-b9eb767d2e1a
|
||||||
k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible
|
k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible
|
||||||
k8s.io/klog v1.0.0 // indirect
|
k8s.io/klog v1.0.0 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect
|
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
|
||||||
k8s.io/utils v0.0.0-20200109141947-94aeca20bf09 // indirect
|
k8s.io/utils v0.0.0-20200109141947-94aeca20bf09 // indirect
|
||||||
|
sigs.k8s.io/kustomize v2.0.3+incompatible // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
// Added for go1.13 migration https://github.com/golang/go/issues/32805
|
// Added for go1.13 migration https://github.com/golang/go/issues/32805
|
||||||
|
|
19
go.sum
19
go.sum
|
@ -24,7 +24,9 @@ github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcy
|
||||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/PuerkitoBio/purell v1.0.0 h1:0GoNN3taZV6QI81IXgCbxMyEaJDXMSIjArYBCYzVVvs=
|
||||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2 h1:JCHLVE3B+kJde7bIEo5N4J+ZbLhp0J1Fs+ulyRws4gE=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/Shopify/sarama v1.24.1/go.mod h1:fGP8eQ6PugKEI0iUETYYtnP6d1pH/bdDMTel1X5ajsU=
|
github.com/Shopify/sarama v1.24.1/go.mod h1:fGP8eQ6PugKEI0iUETYYtnP6d1pH/bdDMTel1X5ajsU=
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
|
@ -95,6 +97,7 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
|
||||||
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
||||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||||
github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw=
|
||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
|
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
|
||||||
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
|
@ -108,6 +111,7 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
@ -116,9 +120,13 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
|
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1 h1:wSt/4CYxs70xbATrGXhokKF1i0tZjENLOo1ioIO13zk=
|
||||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||||
|
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9 h1:tF+augKRWlWx0J0B7ZyyKSiTyV6E1zZe+7b3qQlcEf8=
|
||||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||||
|
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501 h1:C1JKChikHGpXwT5UQDFaryIpDtyyGL/CR6C2kB7F1oc=
|
||||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||||
|
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87 h1:zP3nY8Tk2E6RTkqGYrarZXuzh+ffyLDljLxCy1iJw80=
|
||||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
@ -316,7 +324,9 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
@ -351,6 +361,8 @@ github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
|
@ -443,6 +455,7 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
|
||||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5 h1:0x4qcEHDpruK6ML/m/YSlFUUu0UpRD3I2PHsNCuGnyA=
|
||||||
github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM=
|
github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM=
|
||||||
github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM=
|
github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM=
|
||||||
|
@ -566,6 +579,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
|
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
|
||||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||||
|
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||||
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
@ -950,6 +965,8 @@ k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbu
|
||||||
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
|
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
|
||||||
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA=
|
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA=
|
||||||
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
|
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
|
||||||
|
k8s.io/cli-runtime v0.0.0-20191004110135-b9eb767d2e1a h1:REMzGxu+NpG9dPRsE9my/fw9iYIecz1S8UFFl6hbe18=
|
||||||
|
k8s.io/cli-runtime v0.0.0-20191004110135-b9eb767d2e1a/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM=
|
||||||
k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible h1:bK03DJulJi9j05gwnXUufcs2j7h4M85YFvJ0dIlQ9k4=
|
k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible h1:bK03DJulJi9j05gwnXUufcs2j7h4M85YFvJ0dIlQ9k4=
|
||||||
k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
|
k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
|
||||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||||
|
@ -962,6 +979,8 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKf
|
||||||
k8s.io/utils v0.0.0-20200109141947-94aeca20bf09 h1:sz6xjn8QP74104YNmJpzLbJ+a3ZtHt0tkD0g8vpdWNw=
|
k8s.io/utils v0.0.0-20200109141947-94aeca20bf09 h1:sz6xjn8QP74104YNmJpzLbJ+a3ZtHt0tkD0g8vpdWNw=
|
||||||
k8s.io/utils v0.0.0-20200109141947-94aeca20bf09/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
k8s.io/utils v0.0.0-20200109141947-94aeca20bf09/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
|
||||||
|
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
|
||||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
|
|
|
@ -216,8 +216,8 @@ type Validation struct {
|
||||||
// Generation describes which resources will be created when other resource is created
|
// Generation describes which resources will be created when other resource is created
|
||||||
type Generation struct {
|
type Generation struct {
|
||||||
ResourceSpec
|
ResourceSpec
|
||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data,omitempty"`
|
||||||
Clone CloneFrom `json:"clone"`
|
Clone CloneFrom `json:"clone,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloneFrom - location of the resource
|
// CloneFrom - location of the resource
|
||||||
|
|
|
@ -97,6 +97,6 @@ func CreateClientConfig(kubeconfig string) (*rest.Config, error) {
|
||||||
glog.Info("Using in-cluster configuration")
|
glog.Info("Using in-cluster configuration")
|
||||||
return rest.InClusterConfig()
|
return rest.InClusterConfig()
|
||||||
}
|
}
|
||||||
glog.Infof("Using configuration from '%s'", kubeconfig)
|
glog.V(4).Infof("Using configuration from '%s'", kubeconfig)
|
||||||
return clientcmd.BuildConfigFromFlags("", kubeconfig)
|
return clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
patchTypes "k8s.io/apimachinery/pkg/types"
|
patchTypes "k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/version"
|
||||||
"k8s.io/client-go/discovery"
|
"k8s.io/client-go/discovery"
|
||||||
"k8s.io/client-go/discovery/cached/memory"
|
"k8s.io/client-go/discovery/cached/memory"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
|
@ -213,6 +214,7 @@ func convertToCSR(obj *unstructured.Unstructured) (*certificates.CertificateSign
|
||||||
//IDiscovery provides interface to mange Kind and GVR mapping
|
//IDiscovery provides interface to mange Kind and GVR mapping
|
||||||
type IDiscovery interface {
|
type IDiscovery interface {
|
||||||
GetGVRFromKind(kind string) schema.GroupVersionResource
|
GetGVRFromKind(kind string) schema.GroupVersionResource
|
||||||
|
GetServerVersion() (*version.Info, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDiscovery sets the discovery client implementation
|
// SetDiscovery sets the discovery client implementation
|
||||||
|
@ -265,6 +267,11 @@ func (c ServerPreferredResources) GetGVRFromKind(kind string) schema.GroupVersio
|
||||||
return gvr
|
return gvr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//GetServerVersion returns the server version of the cluster
|
||||||
|
func (c ServerPreferredResources) GetServerVersion() (*version.Info, error) {
|
||||||
|
return c.cachedClient.ServerVersion()
|
||||||
|
}
|
||||||
|
|
||||||
func loadServerResources(k string, cdi discovery.CachedDiscoveryInterface) (schema.GroupVersionResource, error) {
|
func loadServerResources(k string, cdi discovery.CachedDiscoveryInterface) (schema.GroupVersionResource, error) {
|
||||||
serverresources, err := cdi.ServerPreferredResources()
|
serverresources, err := cdi.ServerPreferredResources()
|
||||||
emptyGVR := schema.GroupVersionResource{}
|
emptyGVR := schema.GroupVersionResource{}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/version"
|
||||||
"k8s.io/client-go/dynamic/fake"
|
"k8s.io/client-go/dynamic/fake"
|
||||||
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
||||||
)
|
)
|
||||||
|
@ -64,6 +65,10 @@ func (c *fakeDiscoveryClient) getGVR(resource string) schema.GroupVersionResourc
|
||||||
return schema.GroupVersionResource{}
|
return schema.GroupVersionResource{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *fakeDiscoveryClient) GetServerVersion() (*version.Info, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *fakeDiscoveryClient) GetGVRFromKind(kind string) schema.GroupVersionResource {
|
func (c *fakeDiscoveryClient) GetGVRFromKind(kind string) schema.GroupVersionResource {
|
||||||
resource := strings.ToLower(kind) + "s"
|
resource := strings.ToLower(kind) + "s"
|
||||||
return c.getGVR(resource)
|
return c.getGVR(resource)
|
||||||
|
|
|
@ -30,16 +30,18 @@ type EvalInterface interface {
|
||||||
|
|
||||||
//Context stores the data resources as JSON
|
//Context stores the data resources as JSON
|
||||||
type Context struct {
|
type Context struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
// data map[string]interface{}
|
jsonRaw []byte
|
||||||
jsonRaw []byte
|
whiteListVars []string
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewContext returns a new context
|
//NewContext returns a new context
|
||||||
func NewContext() *Context {
|
// pass the list of variables to be white-listed
|
||||||
|
func NewContext(whiteListVars ...string) *Context {
|
||||||
ctx := Context{
|
ctx := Context{
|
||||||
// data: map[string]interface{}{},
|
// data: map[string]interface{}{},
|
||||||
jsonRaw: []byte(`{}`), // empty json struct
|
jsonRaw: []byte(`{}`), // empty json struct
|
||||||
|
whiteListVars: whiteListVars,
|
||||||
}
|
}
|
||||||
return &ctx
|
return &ctx
|
||||||
}
|
}
|
||||||
|
@ -122,7 +124,6 @@ func (ctx *Context) AddSA(userName string) error {
|
||||||
saNamespace = groups[0]
|
saNamespace = groups[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(4).Infof("Loading variable serviceAccountName with value: %s", saName)
|
|
||||||
saNameObj := struct {
|
saNameObj := struct {
|
||||||
SA string `json:"serviceAccountName"`
|
SA string `json:"serviceAccountName"`
|
||||||
}{
|
}{
|
||||||
|
@ -137,7 +138,6 @@ func (ctx *Context) AddSA(userName string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(4).Infof("Loading variable serviceAccountNamespace with value: %s", saNamespace)
|
|
||||||
saNsObj := struct {
|
saNsObj := struct {
|
||||||
SA string `json:"serviceAccountNamespace"`
|
SA string `json:"serviceAccountNamespace"`
|
||||||
}{
|
}{
|
||||||
|
|
|
@ -11,6 +11,11 @@ import (
|
||||||
//Query the JSON context with JMESPATH search path
|
//Query the JSON context with JMESPATH search path
|
||||||
func (ctx *Context) Query(query string) (interface{}, error) {
|
func (ctx *Context) Query(query string) (interface{}, error) {
|
||||||
var emptyResult interface{}
|
var emptyResult interface{}
|
||||||
|
// check for white-listed variables
|
||||||
|
if ctx.isWhiteListed(query) {
|
||||||
|
return emptyResult, fmt.Errorf("variable %s cannot be used", query)
|
||||||
|
}
|
||||||
|
|
||||||
// compile the query
|
// compile the query
|
||||||
queryPath, err := jmespath.Compile(query)
|
queryPath, err := jmespath.Compile(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -34,3 +39,12 @@ func (ctx *Context) Query(query string) (interface{}, error) {
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) isWhiteListed(variable string) bool {
|
||||||
|
for _, wVar := range ctx.whiteListVars {
|
||||||
|
if wVar == variable {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
"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/response"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/utils"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/variables"
|
"github.com/nirmata/kyverno/pkg/engine/variables"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
|
@ -33,15 +30,15 @@ func filterRule(rule kyverno.Rule, resource unstructured.Unstructured, admission
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
if !rbac.MatchAdmissionInfo(rule, admissionInfo) {
|
if err := MatchesResourceDescription(resource, rule, admissionInfo); err != nil {
|
||||||
return nil
|
glog.V(4).Infof(err.Error())
|
||||||
}
|
|
||||||
if !MatchesResourceDescription(resource, rule) {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// operate on the copy of the conditions, as we perform variable substitution
|
||||||
|
copyConditions := copyConditions(rule.Conditions)
|
||||||
|
|
||||||
// evaluate pre-conditions
|
// evaluate pre-conditions
|
||||||
if !variables.EvaluateConditions(ctx, rule.Conditions) {
|
if !variables.EvaluateConditions(ctx, copyConditions) {
|
||||||
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
|
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -69,13 +66,6 @@ func filterRules(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range policy.Spec.Rules {
|
for _, rule := range policy.Spec.Rules {
|
||||||
if paths := validateGeneralRuleInfoVariables(ctx, rule); len(paths) != 0 {
|
|
||||||
glog.Infof("referenced path not present in generate rule %s, resource %s/%s/%s, path: %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), paths)
|
|
||||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules,
|
|
||||||
newPathNotPresentRuleResponse(rule.Name, utils.Mutation.String(), fmt.Sprintf("path not present: %s", paths)))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ruleResp := filterRule(rule, resource, admissionInfo, ctx); ruleResp != nil {
|
if ruleResp := filterRule(rule, resource, admissionInfo, ctx); ruleResp != nil {
|
||||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
|
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,40 +14,22 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/anchor"
|
"github.com/nirmata/kyverno/pkg/engine/anchor"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/response"
|
"github.com/nirmata/kyverno/pkg/engine/response"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/utils"
|
"github.com/nirmata/kyverno/pkg/engine/utils"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/variables"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessOverlay processes mutation overlay on the resource
|
// ProcessOverlay processes mutation overlay on the resource
|
||||||
func ProcessOverlay(ctx context.EvalInterface, rule kyverno.Rule, resource unstructured.Unstructured) (resp response.RuleResponse, patchedResource unstructured.Unstructured) {
|
func ProcessOverlay(ruleName string, overlay interface{}, resource unstructured.Unstructured) (resp response.RuleResponse, patchedResource unstructured.Unstructured) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
glog.V(4).Infof("started applying overlay rule %q (%v)", rule.Name, startTime)
|
glog.V(4).Infof("started applying overlay rule %q (%v)", ruleName, startTime)
|
||||||
resp.Name = rule.Name
|
resp.Name = ruleName
|
||||||
resp.Type = utils.Mutation.String()
|
resp.Type = utils.Mutation.String()
|
||||||
defer func() {
|
defer func() {
|
||||||
resp.RuleStats.ProcessingTime = time.Since(startTime)
|
resp.RuleStats.ProcessingTime = time.Since(startTime)
|
||||||
glog.V(4).Infof("finished applying overlay rule %q (%v)", resp.Name, resp.RuleStats.ProcessingTime)
|
glog.V(4).Infof("finished applying overlay rule %q (%v)", resp.Name, resp.RuleStats.ProcessingTime)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// if referenced path not present, we skip processing the rule and report violation
|
|
||||||
if invalidPaths := variables.ValidateVariables(ctx, rule.Mutation.Overlay); len(invalidPaths) != 0 {
|
|
||||||
resp.Success = true
|
|
||||||
resp.PathNotPresent = true
|
|
||||||
resp.Message = fmt.Sprintf("referenced path not present: %s", invalidPaths)
|
|
||||||
glog.V(3).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resp.Message)
|
|
||||||
return resp, resource
|
|
||||||
}
|
|
||||||
|
|
||||||
// substitute variables
|
|
||||||
// first pass we substitute all the JMESPATH substitution for the variable
|
|
||||||
// variable: {{<JMESPATH>}}
|
|
||||||
// if a JMESPATH fails, we dont return error but variable is substitured with nil and error log
|
|
||||||
overlay := variables.SubstituteVariables(ctx, rule.Mutation.Overlay)
|
|
||||||
|
|
||||||
patches, overlayerr := processOverlayPatches(resource.UnstructuredContent(), overlay)
|
patches, overlayerr := processOverlayPatches(resource.UnstructuredContent(), overlay)
|
||||||
// resource does not satisfy the overlay pattern, we don't apply this rule
|
// resource does not satisfy the overlay pattern, we don't apply this rule
|
||||||
if !reflect.DeepEqual(overlayerr, overlayError{}) {
|
if !reflect.DeepEqual(overlayerr, overlayError{}) {
|
||||||
|
@ -55,19 +37,19 @@ func ProcessOverlay(ctx context.EvalInterface, rule kyverno.Rule, resource unstr
|
||||||
// condition key is not present in the resource, don't apply this rule
|
// condition key is not present in the resource, don't apply this rule
|
||||||
// consider as success
|
// consider as success
|
||||||
case conditionNotPresent:
|
case conditionNotPresent:
|
||||||
glog.V(3).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg())
|
glog.V(3).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", ruleName, resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg())
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
return resp, resource
|
return resp, resource
|
||||||
// conditions are not met, don't apply this rule
|
// conditions are not met, don't apply this rule
|
||||||
case conditionFailure:
|
case conditionFailure:
|
||||||
glog.V(3).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg())
|
glog.V(3).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", ruleName, resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg())
|
||||||
//TODO: send zero response and not consider this as applied?
|
//TODO: send zero response and not consider this as applied?
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
resp.Message = overlayerr.ErrorMsg()
|
resp.Message = overlayerr.ErrorMsg()
|
||||||
return resp, resource
|
return resp, resource
|
||||||
// rule application failed
|
// rule application failed
|
||||||
case overlayFailure:
|
case overlayFailure:
|
||||||
glog.Errorf("Resource %s/%s/%s: failed to process overlay: %v in the rule %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg(), rule.Name)
|
glog.Errorf("Resource %s/%s/%s: failed to process overlay: %v in the rule %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), overlayerr.ErrorMsg(), ruleName)
|
||||||
resp.Success = false
|
resp.Success = false
|
||||||
resp.Message = fmt.Sprintf("failed to process overlay: %v", overlayerr.ErrorMsg())
|
resp.Message = fmt.Sprintf("failed to process overlay: %v", overlayerr.ErrorMsg())
|
||||||
return resp, resource
|
return resp, resource
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -9,9 +8,7 @@ import (
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/mutate"
|
"github.com/nirmata/kyverno/pkg/engine/mutate"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/rbac"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/response"
|
"github.com/nirmata/kyverno/pkg/engine/response"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/utils"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/variables"
|
"github.com/nirmata/kyverno/pkg/engine/variables"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
|
@ -36,72 +33,60 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
|
||||||
glog.V(4).Infof("started applying mutation rules of policy %q (%v)", policy.Name, startTime)
|
glog.V(4).Infof("started applying mutation rules of policy %q (%v)", policy.Name, startTime)
|
||||||
defer endMutateResultResponse(&resp, startTime)
|
defer endMutateResultResponse(&resp, startTime)
|
||||||
|
|
||||||
incrementAppliedRuleCount := func() {
|
|
||||||
// rules applied successfully count
|
|
||||||
resp.PolicyResponse.RulesAppliedCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedResource := policyContext.NewResource
|
patchedResource := policyContext.NewResource
|
||||||
|
|
||||||
for _, rule := range policy.Spec.Rules {
|
for _, rule := range policy.Spec.Rules {
|
||||||
|
var ruleResponse response.RuleResponse
|
||||||
//TODO: to be checked before calling the resources as well
|
//TODO: to be checked before calling the resources as well
|
||||||
if !rule.HasMutate() && !strings.Contains(PodControllers, resource.GetKind()) {
|
if !rule.HasMutate() && !strings.Contains(PodControllers, resource.GetKind()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if paths := validateGeneralRuleInfoVariables(ctx, rule); len(paths) != 0 {
|
|
||||||
glog.Infof("referenced path not present in rule %s, resource %s/%s/%s, path: %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), paths)
|
|
||||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules,
|
|
||||||
newPathNotPresentRuleResponse(rule.Name, utils.Mutation.String(), fmt.Sprintf("path not present in rule info: %s", paths)))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
if !rbac.MatchAdmissionInfo(rule, policyContext.AdmissionInfo) {
|
|
||||||
glog.V(3).Infof("rule '%s' cannot be applied on %s/%s/%s, admission permission: %v",
|
|
||||||
rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), policyContext.AdmissionInfo)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
glog.V(4).Infof("Time: Mutate matchAdmissionInfo %v", time.Since(startTime))
|
glog.V(4).Infof("Time: Mutate matchAdmissionInfo %v", time.Since(startTime))
|
||||||
|
|
||||||
// check if the resource satisfies the filter conditions defined in the rule
|
// check if the resource satisfies the filter conditions defined in the rule
|
||||||
//TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
|
//TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
|
||||||
// dont statisfy a policy rule resource description
|
// dont statisfy a policy rule resource description
|
||||||
ok := MatchesResourceDescription(resource, rule)
|
if err := MatchesResourceDescription(resource, rule, policyContext.AdmissionInfo); err != nil {
|
||||||
if !ok {
|
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule:\n%s", resource.GetNamespace(), resource.GetName(), err.Error())
|
||||||
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// operate on the copy of the conditions, as we perform variable substitution
|
||||||
|
copyConditions := copyConditions(rule.Conditions)
|
||||||
// evaluate pre-conditions
|
// evaluate pre-conditions
|
||||||
if !variables.EvaluateConditions(ctx, rule.Conditions) {
|
// - handle variable subsitutions
|
||||||
|
if !variables.EvaluateConditions(ctx, copyConditions) {
|
||||||
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
|
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutation := rule.Mutation.DeepCopy()
|
||||||
// Process Overlay
|
// Process Overlay
|
||||||
if rule.Mutation.Overlay != nil {
|
if mutation.Overlay != nil {
|
||||||
var ruleResponse response.RuleResponse
|
overlay := mutation.Overlay
|
||||||
ruleResponse, patchedResource = mutate.ProcessOverlay(ctx, rule, patchedResource)
|
// subsiitue the variables
|
||||||
if ruleResponse.Success {
|
var err error
|
||||||
// - variable substitution path is not present
|
if overlay, err = variables.SubstituteVars(ctx, overlay); err != nil {
|
||||||
if ruleResponse.PathNotPresent {
|
// variable subsitution failed
|
||||||
glog.V(4).Infof(ruleResponse.Message)
|
ruleResponse.Success = false
|
||||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
|
ruleResponse.Message = err.Error()
|
||||||
continue
|
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleResponse, patchedResource = mutate.ProcessOverlay(rule.Name, overlay, patchedResource)
|
||||||
|
if ruleResponse.Success {
|
||||||
// - overlay pattern does not match the resource conditions
|
// - overlay pattern does not match the resource conditions
|
||||||
if ruleResponse.Patches == nil {
|
if ruleResponse.Patches == nil {
|
||||||
glog.V(4).Infof(ruleResponse.Message)
|
glog.V(4).Infof(ruleResponse.Message)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.Infof("Mutate overlay in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
|
glog.V(4).Infof("Mutate overlay in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
|
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
|
||||||
incrementAppliedRuleCount()
|
incrementAppliedRuleCount(&resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process Patches
|
// Process Patches
|
||||||
|
@ -110,7 +95,7 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
|
||||||
ruleResponse, patchedResource = mutate.ProcessPatches(rule, patchedResource)
|
ruleResponse, patchedResource = mutate.ProcessPatches(rule, patchedResource)
|
||||||
glog.Infof("Mutate patches in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
|
glog.Infof("Mutate patches in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
|
||||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
|
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
|
||||||
incrementAppliedRuleCount()
|
incrementAppliedRuleCount(&resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert annotation to podtemplate if resource is pod controller
|
// insert annotation to podtemplate if resource is pod controller
|
||||||
|
@ -121,7 +106,7 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
|
||||||
|
|
||||||
if strings.Contains(PodControllers, resource.GetKind()) {
|
if strings.Contains(PodControllers, resource.GetKind()) {
|
||||||
var ruleResponse response.RuleResponse
|
var ruleResponse response.RuleResponse
|
||||||
ruleResponse, patchedResource = mutate.ProcessOverlay(ctx, podTemplateRule, patchedResource)
|
ruleResponse, patchedResource = mutate.ProcessOverlay(rule.Name, podTemplateRule, patchedResource)
|
||||||
if !ruleResponse.Success {
|
if !ruleResponse.Success {
|
||||||
glog.Errorf("Failed to insert annotation to podTemplate of %s/%s/%s: %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), ruleResponse.Message)
|
glog.Errorf("Failed to insert annotation to podTemplate of %s/%s/%s: %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), ruleResponse.Message)
|
||||||
continue
|
continue
|
||||||
|
@ -137,6 +122,9 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
|
||||||
resp.PatchedResource = patchedResource
|
resp.PatchedResource = patchedResource
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
func incrementAppliedRuleCount(resp *response.EngineResponse) {
|
||||||
|
resp.PolicyResponse.RulesAppliedCount++
|
||||||
|
}
|
||||||
|
|
||||||
func startMutateResultResponse(resp *response.EngineResponse, policy kyverno.ClusterPolicy, resource unstructured.Unstructured) {
|
func startMutateResultResponse(resp *response.EngineResponse, policy kyverno.ClusterPolicy, resource unstructured.Unstructured) {
|
||||||
// set policy information
|
// set policy information
|
||||||
|
|
|
@ -3,7 +3,6 @@ package engine
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
|
@ -152,52 +151,7 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) {
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
NewResource: *resourceUnstructured}
|
NewResource: *resourceUnstructured}
|
||||||
er := Mutate(policyContext)
|
er := Mutate(policyContext)
|
||||||
assert.Assert(t, er.PolicyResponse.Rules[0].PathNotPresent, true)
|
expectedErrorStr := "variable(s) not found or has nil values: [/spec/name/{{request.object.metadata.name1}}]"
|
||||||
}
|
t.Log(er.PolicyResponse.Rules[0].Message)
|
||||||
|
assert.Equal(t, er.PolicyResponse.Rules[0].Message, expectedErrorStr)
|
||||||
func Test_variableSubstitutionPathNotExist_InRuleInfo(t *testing.T) {
|
|
||||||
resourceRaw := []byte(`{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": {
|
|
||||||
"name": "check-root-user"
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
|
|
||||||
policyraw := []byte(`{
|
|
||||||
"apiVersion": "kyverno.io/v1",
|
|
||||||
"kind": "ClusterPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "test-validate-variables"
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "test-match",
|
|
||||||
"match": {
|
|
||||||
"resources": {
|
|
||||||
"kinds": [
|
|
||||||
"{{request.kind}}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
|
|
||||||
var policy kyverno.ClusterPolicy
|
|
||||||
assert.NilError(t, json.Unmarshal(policyraw, &policy))
|
|
||||||
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
|
|
||||||
ctx := context.NewContext()
|
|
||||||
ctx.AddResource(resourceRaw)
|
|
||||||
|
|
||||||
policyContext := PolicyContext{
|
|
||||||
Policy: policy,
|
|
||||||
Context: ctx,
|
|
||||||
NewResource: *resourceUnstructured}
|
|
||||||
er := Mutate(policyContext)
|
|
||||||
assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message, "path not present in rule info"))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
package policy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/variables"
|
|
||||||
)
|
|
||||||
|
|
||||||
//ContainsUserInfo returns error is userInfo is defined
|
|
||||||
func ContainsUserInfo(policy kyverno.ClusterPolicy) error {
|
|
||||||
// iterate of the policy rules to identify if userInfo is used
|
|
||||||
for idx, rule := range policy.Spec.Rules {
|
|
||||||
if err := userInfoDefined(rule.MatchResources.UserInfo); err != nil {
|
|
||||||
return fmt.Errorf("path: spec/rules[%d]/match/%s", idx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := userInfoDefined(rule.ExcludeResources.UserInfo); err != nil {
|
|
||||||
return fmt.Errorf("path: spec/rules[%d]/exclude/%s", idx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// variable defined with user information
|
|
||||||
// - condition.key
|
|
||||||
// - condition.value
|
|
||||||
// - mutate.overlay
|
|
||||||
// - validate.pattern
|
|
||||||
// - validate.anyPattern[*]
|
|
||||||
// variables to filter
|
|
||||||
// - 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)
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
if err := variables.CheckVariables(rule.Validation.Pattern, filterVars, "/"); err != nil {
|
|
||||||
return fmt.Errorf("path: spec/rules[%d]/validate/pattern%s", idx, err)
|
|
||||||
}
|
|
||||||
for idx2, pattern := range rule.Validation.AnyPattern {
|
|
||||||
if err := variables.CheckVariables(pattern, filterVars, "/"); err != nil {
|
|
||||||
return fmt.Errorf("path: spec/rules[%d]/validate/anyPattern[%d]%s", idx, idx2, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func userInfoDefined(ui kyverno.UserInfo) error {
|
|
||||||
if len(ui.Roles) > 0 {
|
|
||||||
return fmt.Errorf("roles")
|
|
||||||
}
|
|
||||||
if len(ui.ClusterRoles) > 0 {
|
|
||||||
return fmt.Errorf("clusterRoles")
|
|
||||||
}
|
|
||||||
if len(ui.Subjects) > 0 {
|
|
||||||
return fmt.Errorf("subjects")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
package rbac
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
|
||||||
utils "github.com/nirmata/kyverno/pkg/utils"
|
|
||||||
authenticationv1 "k8s.io/api/authentication/v1"
|
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
//SaPrefix defines the prefix for service accounts
|
|
||||||
SaPrefix = "system:serviceaccount:"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MatchAdmissionInfo return true if the rule can be applied to the request
|
|
||||||
func MatchAdmissionInfo(rule kyverno.Rule, requestInfo kyverno.RequestInfo) bool {
|
|
||||||
// when processing existing resource, it does not contain requestInfo
|
|
||||||
// skip permission checking
|
|
||||||
if reflect.DeepEqual(requestInfo, kyverno.RequestInfo{}) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !validateMatch(rule.MatchResources, requestInfo) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return validateExclude(rule.ExcludeResources, requestInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// match:
|
|
||||||
// roles: role1, role2
|
|
||||||
// clusterRoles: clusterRole1,clusterRole2
|
|
||||||
// subjects: subject1, subject2
|
|
||||||
// validateMatch return true if (role1 || role2) and (clusterRole1 || clusterRole2)
|
|
||||||
// and (subject1 || subject2) are found in requestInfo, OR operation for each list
|
|
||||||
func validateMatch(match kyverno.MatchResources, requestInfo kyverno.RequestInfo) bool {
|
|
||||||
if len(match.Roles) > 0 {
|
|
||||||
if !matchRoleRefs(match.Roles, requestInfo.Roles) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(match.ClusterRoles) > 0 {
|
|
||||||
if !matchRoleRefs(match.ClusterRoles, requestInfo.ClusterRoles) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(match.Subjects) > 0 {
|
|
||||||
if !matchSubjects(match.Subjects, requestInfo.AdmissionUserInfo) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// exclude:
|
|
||||||
// roles: role1, role2
|
|
||||||
// clusterRoles: clusterRole1,clusterRole2
|
|
||||||
// subjects: subject1, subject2
|
|
||||||
// validateExclude return true if none of the above found in requestInfo
|
|
||||||
// otherwise return false immediately means rule should not be applied
|
|
||||||
func validateExclude(exclude kyverno.ExcludeResources, requestInfo kyverno.RequestInfo) bool {
|
|
||||||
if len(exclude.Roles) > 0 {
|
|
||||||
if matchRoleRefs(exclude.Roles, requestInfo.Roles) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(exclude.ClusterRoles) > 0 {
|
|
||||||
if matchRoleRefs(exclude.ClusterRoles, requestInfo.ClusterRoles) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(exclude.Subjects) > 0 {
|
|
||||||
if matchSubjects(exclude.Subjects, requestInfo.AdmissionUserInfo) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchRoleRefs return true if one of ruleRoleRefs exist in resourceRoleRefs
|
|
||||||
func matchRoleRefs(ruleRoleRefs, resourceRoleRefs []string) bool {
|
|
||||||
for _, ruleRoleRef := range ruleRoleRefs {
|
|
||||||
if utils.ContainsString(resourceRoleRefs, ruleRoleRef) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchSubjects return true if one of ruleSubjects exist in userInfo
|
|
||||||
func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.UserInfo) bool {
|
|
||||||
userGroups := append(userInfo.Groups, userInfo.Username)
|
|
||||||
for _, subject := range ruleSubjects {
|
|
||||||
switch subject.Kind {
|
|
||||||
case "ServiceAccount":
|
|
||||||
if len(userInfo.Username) <= len(SaPrefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
subjectServiceAccount := subject.Namespace + ":" + subject.Name
|
|
||||||
if userInfo.Username[len(SaPrefix):] == subjectServiceAccount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case "User", "Group":
|
|
||||||
if utils.ContainsString(userGroups, subject.Name) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,305 +0,0 @@
|
||||||
package rbac
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
|
||||||
"gotest.tools/assert"
|
|
||||||
authenticationv1 "k8s.io/api/authentication/v1"
|
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_matchAdmissionInfo(t *testing.T) {
|
|
||||||
flag.Parse()
|
|
||||||
flag.Set("logtostderr", "true")
|
|
||||||
flag.Set("v", "3")
|
|
||||||
tests := []struct {
|
|
||||||
rule kyverno.Rule
|
|
||||||
info kyverno.RequestInfo
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
rule: kyverno.Rule{
|
|
||||||
MatchResources: kyverno.MatchResources{},
|
|
||||||
},
|
|
||||||
info: kyverno.RequestInfo{},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rule: kyverno.Rule{
|
|
||||||
MatchResources: kyverno.MatchResources{
|
|
||||||
UserInfo: kyverno.UserInfo{
|
|
||||||
Roles: []string{"ns-a:role-a"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
Roles: []string{"ns-a:role-a"},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rule: kyverno.Rule{
|
|
||||||
MatchResources: kyverno.MatchResources{
|
|
||||||
UserInfo: kyverno.UserInfo{
|
|
||||||
Roles: []string{"ns-a:role-a"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
Roles: []string{"ns-a:role"},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rule: kyverno.Rule{
|
|
||||||
MatchResources: kyverno.MatchResources{
|
|
||||||
UserInfo: kyverno.UserInfo{
|
|
||||||
Subjects: testSubjects(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
AdmissionUserInfo: authenticationv1.UserInfo{
|
|
||||||
Username: "serviceaccount:mynamespace:mysa",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rule: kyverno.Rule{
|
|
||||||
ExcludeResources: kyverno.ExcludeResources{
|
|
||||||
UserInfo: kyverno.UserInfo{
|
|
||||||
Subjects: testSubjects(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
AdmissionUserInfo: authenticationv1.UserInfo{
|
|
||||||
UID: "1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rule: kyverno.Rule{
|
|
||||||
ExcludeResources: kyverno.ExcludeResources{
|
|
||||||
UserInfo: kyverno.UserInfo{
|
|
||||||
Subjects: testSubjects(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
AdmissionUserInfo: authenticationv1.UserInfo{
|
|
||||||
Username: "kubernetes-admin",
|
|
||||||
Groups: []string{"system:masters", "system:authenticated"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
assert.Assert(t, test.expected == MatchAdmissionInfo(test.rule, test.info))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_validateMatch(t *testing.T) {
|
|
||||||
requestInfo := []struct {
|
|
||||||
info kyverno.RequestInfo
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
Roles: []string{},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
Roles: []string{"ns-b:role-b"},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
Roles: []string{"ns:role"},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
matchRoles := kyverno.MatchResources{
|
|
||||||
UserInfo: kyverno.UserInfo{
|
|
||||||
Roles: []string{"ns-a:role-a", "ns-b:role-b"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, info := range requestInfo {
|
|
||||||
assert.Assert(t, info.expected == validateMatch(matchRoles, info.info))
|
|
||||||
}
|
|
||||||
|
|
||||||
requestInfo = []struct {
|
|
||||||
info kyverno.RequestInfo
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
ClusterRoles: []string{},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
ClusterRoles: []string{"role-b"},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
ClusterRoles: []string{"clusterrole-b"},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
ClusterRoles: []string{"clusterrole-a", "clusterrole-b"},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
ClusterRoles: []string{"fake-a", "fake-b"},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
matchClusterRoles := kyverno.MatchResources{
|
|
||||||
UserInfo: kyverno.UserInfo{
|
|
||||||
ClusterRoles: []string{"clusterrole-a", "clusterrole-b"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, info := range requestInfo {
|
|
||||||
assert.Assert(t, info.expected == validateMatch(matchClusterRoles, info.info))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_validateExclude(t *testing.T) {
|
|
||||||
requestInfo := []struct {
|
|
||||||
info kyverno.RequestInfo
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
Roles: []string{},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
Roles: []string{"ns-b:role-b"},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
Roles: []string{"ns:role"},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
excludeRoles := kyverno.ExcludeResources{
|
|
||||||
UserInfo: kyverno.UserInfo{
|
|
||||||
Roles: []string{"ns-a:role-a", "ns-b:role-b"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, info := range requestInfo {
|
|
||||||
assert.Assert(t, info.expected == validateExclude(excludeRoles, info.info))
|
|
||||||
}
|
|
||||||
|
|
||||||
requestInfo = []struct {
|
|
||||||
info kyverno.RequestInfo
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
ClusterRoles: []string{},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
ClusterRoles: []string{"role-b"},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
ClusterRoles: []string{"clusterrole-b"},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: kyverno.RequestInfo{
|
|
||||||
ClusterRoles: []string{"fake-a", "fake-b"},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
excludeClusterRoles := kyverno.ExcludeResources{
|
|
||||||
UserInfo: kyverno.UserInfo{
|
|
||||||
ClusterRoles: []string{"clusterrole-a", "clusterrole-b"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, info := range requestInfo {
|
|
||||||
assert.Assert(t, info.expected == validateExclude(excludeClusterRoles, info.info))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_matchSubjects(t *testing.T) {
|
|
||||||
group := authenticationv1.UserInfo{
|
|
||||||
Username: "kubernetes-admin",
|
|
||||||
Groups: []string{"system:masters", "system:authenticated"},
|
|
||||||
}
|
|
||||||
|
|
||||||
sa := authenticationv1.UserInfo{
|
|
||||||
Username: "system:serviceaccount:mynamespace:mysa",
|
|
||||||
Groups: []string{"system:serviceaccounts", "system:serviceaccounts:mynamespace", "system:authenticated"},
|
|
||||||
}
|
|
||||||
|
|
||||||
user := authenticationv1.UserInfo{
|
|
||||||
Username: "system:kube-scheduler",
|
|
||||||
Groups: []string{"system:authenticated"},
|
|
||||||
}
|
|
||||||
|
|
||||||
subjects := testSubjects()
|
|
||||||
|
|
||||||
assert.Assert(t, matchSubjects(subjects, sa))
|
|
||||||
assert.Assert(t, !matchSubjects(subjects, user))
|
|
||||||
assert.Assert(t, matchSubjects(subjects, group))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSubjects() []rbacv1.Subject {
|
|
||||||
return []rbacv1.Subject{
|
|
||||||
{
|
|
||||||
Kind: "User",
|
|
||||||
Name: "kube-scheduler",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Kind: "Group",
|
|
||||||
Name: "system:masters",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Kind: "ServiceAccount",
|
|
||||||
Name: "mysa",
|
|
||||||
Namespace: "mynamespace",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -65,8 +65,6 @@ type RuleResponse struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
// statistics
|
// statistics
|
||||||
RuleStats `json:",inline"`
|
RuleStats `json:",inline"`
|
||||||
// PathNotPresent indicates whether referenced path in variable substitution exist
|
|
||||||
PathNotPresent bool `json:"pathNotPresent"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//ToString ...
|
//ToString ...
|
||||||
|
@ -121,13 +119,3 @@ func (er EngineResponse) getRules(success bool) []string {
|
||||||
}
|
}
|
||||||
return rules
|
return rules
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPathNotPresent checks if the referenced path(in variable substitution) exist
|
|
||||||
func (er EngineResponse) IsPathNotPresent() bool {
|
|
||||||
for _, r := range er.PolicyResponse.Rules {
|
|
||||||
if r.PathNotPresent {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"errors"
|
||||||
"strings"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/nirmata/kyverno/pkg/utils"
|
||||||
|
authenticationv1 "k8s.io/api/authentication/v1"
|
||||||
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/wildcard"
|
"github.com/minio/minio/pkg/wildcard"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/response"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/variables"
|
|
||||||
"github.com/nirmata/kyverno/pkg/utils"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
@ -26,236 +27,176 @@ type EngineStats struct {
|
||||||
RulesAppliedCount int
|
RulesAppliedCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
//MatchesResourceDescription checks if the resource matches resource desription of the rule or not
|
func checkKind(kinds []string, resourceKind string) bool {
|
||||||
func MatchesResourceDescription(resource unstructured.Unstructured, rule kyverno.Rule) bool {
|
|
||||||
matches := rule.MatchResources.ResourceDescription
|
|
||||||
exclude := rule.ExcludeResources.ResourceDescription
|
|
||||||
|
|
||||||
if len(matches.Kinds) > 0 {
|
|
||||||
if !findKind(matches.Kinds, resource.GetKind()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
name := resource.GetName()
|
|
||||||
|
|
||||||
namespace := resource.GetNamespace()
|
|
||||||
|
|
||||||
if matches.Name != "" {
|
|
||||||
// Matches
|
|
||||||
if !wildcard.Match(matches.Name, name) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Matches
|
|
||||||
// check if the resource namespace is defined in the list of namespace pattern
|
|
||||||
if len(matches.Namespaces) > 0 && !utils.ContainsNamepace(matches.Namespaces, namespace) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Matches
|
|
||||||
if matches.Selector != nil {
|
|
||||||
selector, err := metav1.LabelSelectorAsSelector(matches.Selector)
|
|
||||||
if err != nil {
|
|
||||||
glog.Error(err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !selector.Matches(labels.Set(resource.GetLabels())) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
excludeName := func(name string) Condition {
|
|
||||||
if exclude.Name == "" {
|
|
||||||
return NotEvaluate
|
|
||||||
}
|
|
||||||
if wildcard.Match(exclude.Name, name) {
|
|
||||||
return Skip
|
|
||||||
}
|
|
||||||
return Process
|
|
||||||
}
|
|
||||||
|
|
||||||
excludeNamespace := func(namespace string) Condition {
|
|
||||||
if len(exclude.Namespaces) == 0 {
|
|
||||||
return NotEvaluate
|
|
||||||
}
|
|
||||||
if utils.ContainsNamepace(exclude.Namespaces, namespace) {
|
|
||||||
return Skip
|
|
||||||
}
|
|
||||||
return Process
|
|
||||||
}
|
|
||||||
|
|
||||||
excludeSelector := func(labelsMap map[string]string) Condition {
|
|
||||||
if exclude.Selector == nil {
|
|
||||||
return NotEvaluate
|
|
||||||
}
|
|
||||||
selector, err := metav1.LabelSelectorAsSelector(exclude.Selector)
|
|
||||||
// if the label selector is incorrect, should be fail or
|
|
||||||
if err != nil {
|
|
||||||
glog.Error(err)
|
|
||||||
return Skip
|
|
||||||
}
|
|
||||||
if selector.Matches(labels.Set(labelsMap)) {
|
|
||||||
return Skip
|
|
||||||
}
|
|
||||||
return Process
|
|
||||||
}
|
|
||||||
|
|
||||||
excludeKind := func(kind string) Condition {
|
|
||||||
if len(exclude.Kinds) == 0 {
|
|
||||||
return NotEvaluate
|
|
||||||
}
|
|
||||||
|
|
||||||
if findKind(exclude.Kinds, kind) {
|
|
||||||
return Skip
|
|
||||||
}
|
|
||||||
|
|
||||||
return Process
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0 -> dont check
|
|
||||||
// 1 -> is not to be exclude
|
|
||||||
// 2 -> to be exclude
|
|
||||||
excludeEval := []Condition{}
|
|
||||||
|
|
||||||
if ret := excludeName(resource.GetName()); ret != NotEvaluate {
|
|
||||||
excludeEval = append(excludeEval, ret)
|
|
||||||
}
|
|
||||||
if ret := excludeNamespace(resource.GetNamespace()); ret != NotEvaluate {
|
|
||||||
excludeEval = append(excludeEval, ret)
|
|
||||||
}
|
|
||||||
if ret := excludeSelector(resource.GetLabels()); ret != NotEvaluate {
|
|
||||||
excludeEval = append(excludeEval, ret)
|
|
||||||
}
|
|
||||||
if ret := excludeKind(resource.GetKind()); ret != NotEvaluate {
|
|
||||||
excludeEval = append(excludeEval, ret)
|
|
||||||
}
|
|
||||||
// Filtered NotEvaluate
|
|
||||||
|
|
||||||
if len(excludeEval) == 0 {
|
|
||||||
// nothing to exclude
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return func() bool {
|
|
||||||
for _, ret := range excludeEval {
|
|
||||||
if ret == Process {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
//Condition type for conditions
|
|
||||||
type Condition int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NotEvaluate to not-evaluate to condition
|
|
||||||
NotEvaluate Condition = 0
|
|
||||||
// Process to process the condition
|
|
||||||
Process Condition = 1
|
|
||||||
// Skip to skip the condition
|
|
||||||
Skip Condition = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseResourceInfoFromObject get kind/namepace/name from resource
|
|
||||||
func ParseResourceInfoFromObject(rawResource []byte) string {
|
|
||||||
|
|
||||||
kind := ParseKindFromObject(rawResource)
|
|
||||||
namespace := ParseNamespaceFromObject(rawResource)
|
|
||||||
name := ParseNameFromObject(rawResource)
|
|
||||||
return strings.Join([]string{kind, namespace, name}, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
//ParseKindFromObject get kind from resource
|
|
||||||
func ParseKindFromObject(bytes []byte) string {
|
|
||||||
var objectJSON map[string]interface{}
|
|
||||||
json.Unmarshal(bytes, &objectJSON)
|
|
||||||
|
|
||||||
return objectJSON["kind"].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
//ParseNameFromObject extracts resource name from JSON obj
|
|
||||||
func ParseNameFromObject(bytes []byte) string {
|
|
||||||
var objectJSON map[string]interface{}
|
|
||||||
json.Unmarshal(bytes, &objectJSON)
|
|
||||||
meta, ok := objectJSON["metadata"]
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
metaMap, ok := meta.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if name, ok := metaMap["name"].(string); ok {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseNamespaceFromObject extracts the namespace from the JSON obj
|
|
||||||
func ParseNamespaceFromObject(bytes []byte) string {
|
|
||||||
var objectJSON map[string]interface{}
|
|
||||||
json.Unmarshal(bytes, &objectJSON)
|
|
||||||
meta, ok := objectJSON["metadata"]
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
metaMap, ok := meta.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if name, ok := metaMap["namespace"].(string); ok {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func findKind(kinds []string, kindGVK string) bool {
|
|
||||||
for _, kind := range kinds {
|
for _, kind := range kinds {
|
||||||
if kind == kindGVK {
|
if resourceKind == kind {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkName(name, resourceName string) bool {
|
||||||
|
return wildcard.Match(name, resourceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkNameSpace(namespaces []string, resourceNameSpace string) bool {
|
||||||
|
for _, namespace := range namespaces {
|
||||||
|
if resourceNameSpace == namespace {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateGeneralRuleInfoVariables validate variable subtition defined in
|
func checkSelector(labelSelector *metav1.LabelSelector, resourceLabels map[string]string) (bool, error) {
|
||||||
// - MatchResources
|
selector, err := metav1.LabelSelectorAsSelector(labelSelector)
|
||||||
// - ExcludeResources
|
|
||||||
// - Conditions
|
|
||||||
func validateGeneralRuleInfoVariables(ctx context.EvalInterface, rule kyverno.Rule) string {
|
|
||||||
var tempRule kyverno.Rule
|
|
||||||
var tempRulePattern interface{}
|
|
||||||
|
|
||||||
tempRule.MatchResources = rule.MatchResources
|
|
||||||
tempRule.ExcludeResources = rule.ExcludeResources
|
|
||||||
tempRule.Conditions = rule.Conditions
|
|
||||||
|
|
||||||
raw, err := json.Marshal(tempRule)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Infof("failed to serilize rule info while validating variable substitution: %v", err)
|
glog.Error(err)
|
||||||
return ""
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(raw, &tempRulePattern); err != nil {
|
if selector.Matches(labels.Set(resourceLabels)) {
|
||||||
glog.Infof("failed to serilize rule info while validating variable substitution: %v", err)
|
return true, nil
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return variables.ValidateVariables(ctx, tempRulePattern)
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPathNotPresentRuleResponse(rname, rtype, msg string) response.RuleResponse {
|
func doesResourceMatchConditionBlock(conditionBlock kyverno.ResourceDescription, userInfo kyverno.UserInfo, admissionInfo kyverno.RequestInfo, resource unstructured.Unstructured) []error {
|
||||||
return response.RuleResponse{
|
var errs []error
|
||||||
Name: rname,
|
if len(conditionBlock.Kinds) > 0 {
|
||||||
Type: rtype,
|
if !checkKind(conditionBlock.Kinds, resource.GetKind()) {
|
||||||
Message: msg,
|
errs = append(errs, fmt.Errorf("resource kind does not match conditionBlock"))
|
||||||
Success: true,
|
}
|
||||||
PathNotPresent: true,
|
|
||||||
}
|
}
|
||||||
|
if conditionBlock.Name != "" {
|
||||||
|
if !checkName(conditionBlock.Name, resource.GetName()) {
|
||||||
|
errs = append(errs, fmt.Errorf("resource name does not match conditionBlock"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(conditionBlock.Namespaces) > 0 {
|
||||||
|
if !checkNameSpace(conditionBlock.Namespaces, resource.GetNamespace()) {
|
||||||
|
errs = append(errs, fmt.Errorf("resource namespace does not match conditionBlock"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if conditionBlock.Selector != nil {
|
||||||
|
hasPassed, err := checkSelector(conditionBlock.Selector, resource.GetLabels())
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("could not parse selector block of the policy in conditionBlock: %v", err))
|
||||||
|
} else {
|
||||||
|
if !hasPassed {
|
||||||
|
errs = append(errs, fmt.Errorf("resource does not match selector of given conditionBlock"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(userInfo.Roles) > 0 {
|
||||||
|
if !doesSliceContainsAnyOfTheseValues(userInfo.Roles, admissionInfo.Roles...) {
|
||||||
|
errs = append(errs, fmt.Errorf("user info does not match roles for the given conditionBlock"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(userInfo.ClusterRoles) > 0 {
|
||||||
|
if !doesSliceContainsAnyOfTheseValues(userInfo.ClusterRoles, admissionInfo.ClusterRoles...) {
|
||||||
|
errs = append(errs, fmt.Errorf("user info does not match clustersRoles for the given conditionBlock"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(userInfo.Subjects) > 0 {
|
||||||
|
if !matchSubjects(userInfo.Subjects, admissionInfo.AdmissionUserInfo) {
|
||||||
|
errs = append(errs, fmt.Errorf("user info does not match subject for the given conditionBlock"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchSubjects return true if one of ruleSubjects exist in userInfo
|
||||||
|
func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.UserInfo) bool {
|
||||||
|
const SaPrefix = "system:serviceaccount:"
|
||||||
|
|
||||||
|
userGroups := append(userInfo.Groups, userInfo.Username)
|
||||||
|
for _, subject := range ruleSubjects {
|
||||||
|
switch subject.Kind {
|
||||||
|
case "ServiceAccount":
|
||||||
|
if len(userInfo.Username) <= len(SaPrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
subjectServiceAccount := subject.Namespace + ":" + subject.Name
|
||||||
|
if userInfo.Username[len(SaPrefix):] == subjectServiceAccount {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case "User", "Group":
|
||||||
|
if utils.ContainsString(userGroups, subject.Name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func doesSliceContainsAnyOfTheseValues(slice []string, values ...string) bool {
|
||||||
|
|
||||||
|
var sliceElementsMap = make(map[string]bool, len(slice))
|
||||||
|
for _, sliceElement := range slice {
|
||||||
|
sliceElementsMap[sliceElement] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value := range values {
|
||||||
|
if sliceElementsMap[value] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//MatchesResourceDescription checks if the resource matches resource description of the rule or not
|
||||||
|
func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef kyverno.Rule, admissionInfoRef kyverno.RequestInfo) error {
|
||||||
|
rule := *ruleRef.DeepCopy()
|
||||||
|
resource := *resourceRef.DeepCopy()
|
||||||
|
admissionInfo := *admissionInfoRef.DeepCopy()
|
||||||
|
|
||||||
|
var reasonsForFailure []error
|
||||||
|
|
||||||
|
if reflect.DeepEqual(admissionInfo, kyverno.RequestInfo{}) {
|
||||||
|
rule.MatchResources.UserInfo = kyverno.UserInfo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checking if resource matches the rule
|
||||||
|
if !reflect.DeepEqual(rule.MatchResources.ResourceDescription, kyverno.ResourceDescription{}) {
|
||||||
|
matchErrs := doesResourceMatchConditionBlock(rule.MatchResources.ResourceDescription, rule.MatchResources.UserInfo, admissionInfo, resource)
|
||||||
|
reasonsForFailure = append(reasonsForFailure, matchErrs...)
|
||||||
|
} else {
|
||||||
|
reasonsForFailure = append(reasonsForFailure, fmt.Errorf("match block in rule cannot be empty"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// checking if resource has been excluded
|
||||||
|
if !reflect.DeepEqual(rule.ExcludeResources.ResourceDescription, kyverno.ResourceDescription{}) {
|
||||||
|
excludeErrs := doesResourceMatchConditionBlock(rule.ExcludeResources.ResourceDescription, rule.ExcludeResources.UserInfo, admissionInfo, resource)
|
||||||
|
if excludeErrs == nil {
|
||||||
|
reasonsForFailure = append(reasonsForFailure, fmt.Errorf("resource has been excluded since it matches the exclude block"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creating final error
|
||||||
|
var errorMessage = "rule has failed to match resource for the following reasons:"
|
||||||
|
for i, reasonForFailure := range reasonsForFailure {
|
||||||
|
if reasonForFailure != nil {
|
||||||
|
errorMessage += "\n" + fmt.Sprint(i+1) + ". " + reasonForFailure.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reasonsForFailure) > 0 {
|
||||||
|
return errors.New(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func copyConditions(original []kyverno.Condition) []kyverno.Condition {
|
||||||
|
var copy []kyverno.Condition
|
||||||
|
for _, condition := range original {
|
||||||
|
copy = append(copy, *condition.DeepCopy())
|
||||||
|
}
|
||||||
|
return copy
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,88 @@ package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
context "github.com/nirmata/kyverno/pkg/engine/context"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/utils"
|
"github.com/nirmata/kyverno/pkg/engine/utils"
|
||||||
"gotest.tools/assert"
|
|
||||||
authenticationv1 "k8s.io/api/authentication/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMatchesResourceDescription(t *testing.T) {
|
||||||
|
tcs := []struct {
|
||||||
|
Description string
|
||||||
|
AdmissionInfo kyverno.RequestInfo
|
||||||
|
Resource []byte
|
||||||
|
Policy []byte
|
||||||
|
areErrorsExpected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Description: "Should match pod and not exclude it",
|
||||||
|
AdmissionInfo: kyverno.RequestInfo{
|
||||||
|
ClusterRoles: []string{"admin"},
|
||||||
|
},
|
||||||
|
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
|
||||||
|
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"hello-world"},"clusterRoles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
|
||||||
|
areErrorsExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Should exclude resource since it matches the exclude block",
|
||||||
|
AdmissionInfo: kyverno.RequestInfo{
|
||||||
|
ClusterRoles: []string{"system:node"},
|
||||||
|
},
|
||||||
|
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
|
||||||
|
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"hello-world"},"clusterRoles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
|
||||||
|
areErrorsExpected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Should not fail if in sync mode, if admission info is empty it should still match resources with specific clusterRoles",
|
||||||
|
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
|
||||||
|
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"hello-world"},"clusterRoles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
|
||||||
|
areErrorsExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Should fail since resource does not match policy",
|
||||||
|
AdmissionInfo: kyverno.RequestInfo{
|
||||||
|
ClusterRoles: []string{"admin"},
|
||||||
|
},
|
||||||
|
Resource: []byte(`{"apiVersion":"v1","kind":"Service","metadata":{"name":"hello-world","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
|
||||||
|
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"hello-world"},"clusterRoles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
|
||||||
|
areErrorsExpected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Should not fail since resource does not match exclude block",
|
||||||
|
AdmissionInfo: kyverno.RequestInfo{
|
||||||
|
ClusterRoles: []string{"system:node"},
|
||||||
|
},
|
||||||
|
Resource: []byte(`{"apiVersion":"v1","kind":"Pod","metadata":{"name":"hello-world2","labels":{"name":"hello-world"}},"spec":{"containers":[{"name":"hello-world","image":"hello-world","ports":[{"containerPort":81}],"resources":{"limits":{"memory":"30Mi","cpu":"0.2"},"requests":{"memory":"20Mi","cpu":"0.1"}}}]}}`),
|
||||||
|
Policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"hello-world-policy"},"spec":{"background":false,"rules":[{"name":"hello-world-policy","match":{"resources":{"kinds":["Pod"]}},"exclude":{"resources":{"name":"hello-world"},"clusterRoles":["system:node"]},"mutate":{"overlay":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
|
||||||
|
areErrorsExpected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range tcs {
|
||||||
|
var policy kyverno.Policy
|
||||||
|
err := json.Unmarshal(tc.Policy, &policy)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Testcase %d invalid policy raw", i+1)
|
||||||
|
}
|
||||||
|
resource, _ := utils.ConvertToUnstructured(tc.Resource)
|
||||||
|
|
||||||
|
for _, rule := range policy.Spec.Rules {
|
||||||
|
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo)
|
||||||
|
if err != nil {
|
||||||
|
if !tc.areErrorsExpected {
|
||||||
|
t.Errorf("Testcase %d Unexpected error: %v", i+1, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if tc.areErrorsExpected {
|
||||||
|
t.Errorf("Testcase %d Expected Error but recieved no error", i+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Match multiple kinds
|
// Match multiple kinds
|
||||||
func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
|
func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
|
||||||
rawResource := []byte(`{
|
rawResource := []byte(`{
|
||||||
|
@ -67,7 +138,10 @@ func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
|
||||||
}
|
}
|
||||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
||||||
|
|
||||||
assert.Assert(t, MatchesResourceDescription(*resource, rule))
|
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err != nil {
|
||||||
|
t.Errorf("Testcase has failed due to the following:%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match resource name
|
// Match resource name
|
||||||
|
@ -125,7 +199,9 @@ func TestResourceDescriptionMatch_Name(t *testing.T) {
|
||||||
}
|
}
|
||||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
||||||
|
|
||||||
assert.Assert(t, MatchesResourceDescription(*resource, rule))
|
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err != nil {
|
||||||
|
t.Errorf("Testcase has failed due to the following:%v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match resource regex
|
// Match resource regex
|
||||||
|
@ -183,7 +259,9 @@ func TestResourceDescriptionMatch_Name_Regex(t *testing.T) {
|
||||||
}
|
}
|
||||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
||||||
|
|
||||||
assert.Assert(t, MatchesResourceDescription(*resource, rule))
|
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err != nil {
|
||||||
|
t.Errorf("Testcase has failed due to the following:%v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match expressions for labels to not match
|
// Match expressions for labels to not match
|
||||||
|
@ -249,7 +327,9 @@ func TestResourceDescriptionMatch_Label_Expression_NotMatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
||||||
|
|
||||||
assert.Assert(t, MatchesResourceDescription(*resource, rule))
|
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err != nil {
|
||||||
|
t.Errorf("Testcase has failed due to the following:%v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match label expression in matching set
|
// Match label expression in matching set
|
||||||
|
@ -316,7 +396,9 @@ func TestResourceDescriptionMatch_Label_Expression_Match(t *testing.T) {
|
||||||
}
|
}
|
||||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
|
||||||
|
|
||||||
assert.Assert(t, MatchesResourceDescription(*resource, rule))
|
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err != nil {
|
||||||
|
t.Errorf("Testcase has failed due to the following:%v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for exclude conditions
|
// check for exclude conditions
|
||||||
|
@ -394,118 +476,7 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
|
||||||
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription},
|
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription},
|
||||||
ExcludeResources: kyverno.ExcludeResources{ResourceDescription: resourceDescriptionExclude}}
|
ExcludeResources: kyverno.ExcludeResources{ResourceDescription: resourceDescriptionExclude}}
|
||||||
|
|
||||||
assert.Assert(t, !MatchesResourceDescription(*resource, rule))
|
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}); err == nil {
|
||||||
}
|
t.Errorf("Testcase has failed due to the following:\n Function has returned no error, even though it was suposed to fail")
|
||||||
|
|
||||||
func Test_validateGeneralRuleInfoVariables(t *testing.T) {
|
|
||||||
rawResource := []byte(`
|
|
||||||
{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Pod",
|
|
||||||
"metadata": {
|
|
||||||
"name": "image-with-hostpath",
|
|
||||||
"labels": {
|
|
||||||
"app.type": "prod",
|
|
||||||
"namespace": "my-namespace"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"containers": [
|
|
||||||
{
|
|
||||||
"name": "image-with-hostpath",
|
|
||||||
"image": "docker.io/nautiker/curl",
|
|
||||||
"volumeMounts": [
|
|
||||||
{
|
|
||||||
"name": "var-lib-etcd",
|
|
||||||
"mountPath": "/var/lib"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"volumes": [
|
|
||||||
{
|
|
||||||
"name": "var-lib-etcd",
|
|
||||||
"emptyDir": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
policyRaw := []byte(`{
|
|
||||||
"apiVersion": "kyverno.io/v1",
|
|
||||||
"kind": "ClusterPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "test-validate-variables"
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "test-match",
|
|
||||||
"match": {
|
|
||||||
"Subjects": [
|
|
||||||
{
|
|
||||||
"kind": "User",
|
|
||||||
"name": "{{request.userInfo.username1}}}"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"resources": {
|
|
||||||
"kind": "{{request.object.kind}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "test-exclude",
|
|
||||||
"match": {
|
|
||||||
"resources": {
|
|
||||||
"namespaces": [
|
|
||||||
"{{request.object.namespace}}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "test-condition",
|
|
||||||
"preconditions": [
|
|
||||||
{
|
|
||||||
"key": "{{serviceAccountName}}",
|
|
||||||
"operator": "NotEqual",
|
|
||||||
"value": "testuser"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
|
|
||||||
userReqInfo := kyverno.RequestInfo{
|
|
||||||
AdmissionUserInfo: authenticationv1.UserInfo{
|
|
||||||
Username: "user1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var policy kyverno.ClusterPolicy
|
|
||||||
assert.NilError(t, json.Unmarshal(policyRaw, &policy))
|
|
||||||
|
|
||||||
ctx := context.NewContext()
|
|
||||||
var err error
|
|
||||||
err = ctx.AddResource(rawResource)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
err = ctx.AddUserInfo(userReqInfo)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
err = ctx.AddSA("system:serviceaccount:test:testuser")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectPaths := []string{"request.userInfo.username1", "request.object.namespace", ""}
|
|
||||||
|
|
||||||
for i, rule := range policy.Spec.Rules {
|
|
||||||
invalidPaths := validateGeneralRuleInfoVariables(ctx, rule)
|
|
||||||
assert.Assert(t, invalidPaths == expectPaths[i], fmt.Sprintf("result not match, got invalidPaths %s", invalidPaths))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
//ValidationFailureReason defeins type for Validation Failure reason
|
|
||||||
type ValidationFailureReason int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// PathNotPresent if path is not present
|
|
||||||
PathNotPresent ValidationFailureReason = iota
|
|
||||||
// Rulefailure if the rule failed
|
|
||||||
Rulefailure
|
|
||||||
)
|
|
||||||
|
|
||||||
// convertToString converts value to string
|
// convertToString converts value to string
|
||||||
func convertToString(value interface{}) (string, error) {
|
func convertToString(value interface{}) (string, error) {
|
||||||
switch typed := value.(type) {
|
switch typed := value.(type) {
|
||||||
|
@ -44,14 +34,3 @@ func getRawKeyIfWrappedWithAttributes(str string) string {
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//ValidationError stores error for validation error
|
|
||||||
type ValidationError struct {
|
|
||||||
StatusCode ValidationFailureReason
|
|
||||||
ErrorMsg string
|
|
||||||
}
|
|
||||||
|
|
||||||
// newValidatePatternError returns an validation error using the ValidationFailureReason and errorMsg
|
|
||||||
func newValidatePatternError(reason ValidationFailureReason, msg string) ValidationError {
|
|
||||||
return ValidationError{StatusCode: reason, ErrorMsg: msg}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,29 +10,18 @@ import (
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/anchor"
|
"github.com/nirmata/kyverno/pkg/engine/anchor"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/operator"
|
"github.com/nirmata/kyverno/pkg/engine/operator"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/variables"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateResourceWithPattern is a start of element-by-element validation process
|
// ValidateResourceWithPattern is a start of element-by-element validation process
|
||||||
// It assumes that validation is started from root, so "/" is passed
|
// It assumes that validation is started from root, so "/" is passed
|
||||||
func ValidateResourceWithPattern(ctx context.EvalInterface, resource, pattern interface{}) (string, ValidationError) {
|
func ValidateResourceWithPattern(resource, pattern interface{}) (string, error) {
|
||||||
// if referenced path is not present, we skip processing the rule and report violation
|
|
||||||
if invalidPaths := variables.ValidateVariables(ctx, pattern); len(invalidPaths) != 0 {
|
|
||||||
return "", newValidatePatternError(PathNotPresent, invalidPaths)
|
|
||||||
}
|
|
||||||
|
|
||||||
// first pass we substitute all the JMESPATH substitution for the variable
|
|
||||||
// variable: {{<JMESPATH>}}
|
|
||||||
// if a JMESPATH fails, we dont return error but variable is substitured with nil and error log
|
|
||||||
pattern = variables.SubstituteVariables(ctx, pattern)
|
|
||||||
path, err := validateResourceElement(resource, pattern, pattern, "/")
|
path, err := validateResourceElement(resource, pattern, pattern, "/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return path, newValidatePatternError(Rulefailure, err.Error())
|
return path, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", ValidationError{}
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
|
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
|
||||||
|
@ -71,7 +60,7 @@ func validateResourceElement(resourceElement, patternElement, originPattern inte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ValidateValueWithPattern(resourceElement, patternElement) {
|
if !ValidateValueWithPattern(resourceElement, patternElement) {
|
||||||
return path, fmt.Errorf("Validation rule failed at '%s' to validate value %v with pattern %v", path, resourceElement, patternElement)
|
return path, fmt.Errorf("Validation rule failed at '%s' to validate value '%v' with pattern '%v'", path, resourceElement, patternElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
"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/response"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/utils"
|
"github.com/nirmata/kyverno/pkg/engine/utils"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/validate"
|
"github.com/nirmata/kyverno/pkg/engine/validate"
|
||||||
|
@ -94,32 +91,21 @@ func validateResource(ctx context.EvalInterface, policy kyverno.ClusterPolicy, r
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
if paths := validateGeneralRuleInfoVariables(ctx, rule); len(paths) != 0 {
|
|
||||||
glog.Infof("referenced path not present in rule %s/, resource %s/%s/%s, path: %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), paths)
|
|
||||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules,
|
|
||||||
newPathNotPresentRuleResponse(rule.Name, utils.Validation.String(), fmt.Sprintf("path not present: %s", paths)))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !rbac.MatchAdmissionInfo(rule, admissionInfo) {
|
|
||||||
glog.V(3).Infof("rule '%s' cannot be applied on %s/%s/%s, admission permission: %v",
|
|
||||||
rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), admissionInfo)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
glog.V(4).Infof("Time: Validate matchAdmissionInfo %v", time.Since(startTime))
|
glog.V(4).Infof("Time: Validate matchAdmissionInfo %v", time.Since(startTime))
|
||||||
|
|
||||||
// check if the resource satisfies the filter conditions defined in the rule
|
// check if the resource satisfies the filter conditions defined in the rule
|
||||||
// TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
|
// TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
|
||||||
// dont statisfy a policy rule resource description
|
// dont statisfy a policy rule resource description
|
||||||
ok := MatchesResourceDescription(resource, rule)
|
if err := MatchesResourceDescription(resource, rule, admissionInfo); err != nil {
|
||||||
if !ok {
|
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule:\n%s", resource.GetNamespace(), resource.GetName(), err.Error())
|
||||||
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// operate on the copy of the conditions, as we perform variable substitution
|
||||||
|
copyConditions := copyConditions(rule.Conditions)
|
||||||
// evaluate pre-conditions
|
// evaluate pre-conditions
|
||||||
if !variables.EvaluateConditions(ctx, rule.Conditions) {
|
// - handle variable subsitutions
|
||||||
|
if !variables.EvaluateConditions(ctx, copyConditions) {
|
||||||
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
|
glog.V(4).Infof("resource %s/%s does not satisfy the conditions for the rule ", resource.GetNamespace(), resource.GetName())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -182,27 +168,29 @@ func validatePatterns(ctx context.EvalInterface, resource unstructured.Unstructu
|
||||||
resp.RuleStats.ProcessingTime = time.Since(startTime)
|
resp.RuleStats.ProcessingTime = time.Since(startTime)
|
||||||
glog.V(4).Infof("finished applying validation rule %q (%v)", resp.Name, resp.RuleStats.ProcessingTime)
|
glog.V(4).Infof("finished applying validation rule %q (%v)", resp.Name, resp.RuleStats.ProcessingTime)
|
||||||
}()
|
}()
|
||||||
|
// work on a copy of validation rule
|
||||||
|
validationRule := rule.Validation.DeepCopy()
|
||||||
|
|
||||||
// either pattern or anyPattern can be specified in Validation rule
|
// either pattern or anyPattern can be specified in Validation rule
|
||||||
if rule.Validation.Pattern != nil {
|
if validationRule.Pattern != nil {
|
||||||
path, err := validate.ValidateResourceWithPattern(ctx, resource.Object, rule.Validation.Pattern)
|
// substitute variables in the pattern
|
||||||
if !reflect.DeepEqual(err, validate.ValidationError{}) {
|
pattern := validationRule.Pattern
|
||||||
switch err.StatusCode {
|
var err error
|
||||||
case validate.PathNotPresent:
|
if pattern, err = variables.SubstituteVars(ctx, pattern); err != nil {
|
||||||
resp.Success = true
|
// variable subsitution failed
|
||||||
resp.PathNotPresent = true
|
resp.Success = false
|
||||||
resp.Message = fmt.Sprintf("referenced path not present: %s", err.ErrorMsg)
|
resp.Message = fmt.Sprintf("Validation error: %s; Validation rule '%s' failed. '%s'",
|
||||||
glog.V(4).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resp.Message)
|
rule.Validation.Message, rule.Name, err)
|
||||||
case validate.Rulefailure:
|
|
||||||
// rule application failed
|
|
||||||
glog.V(4).Infof("Validation rule '%s' failed at '%s' for resource %s/%s/%s. %s: %v", rule.Name, path, resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Validation.Message, err)
|
|
||||||
resp.Success = false
|
|
||||||
resp.Message = fmt.Sprintf("Validation error: %s; Validation rule '%s' failed at path '%s'",
|
|
||||||
rule.Validation.Message, rule.Name, path)
|
|
||||||
}
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if path, err := validate.ValidateResourceWithPattern(resource.Object, pattern); err != nil {
|
||||||
|
// validation failed
|
||||||
|
resp.Success = false
|
||||||
|
resp.Message = fmt.Sprintf("Validation error: %s; Validation rule '%s' failed at path '%s'",
|
||||||
|
rule.Validation.Message, rule.Name, path)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
// rule application successful
|
// rule application successful
|
||||||
glog.V(4).Infof("rule %s pattern validated successfully on resource %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
|
glog.V(4).Infof("rule %s pattern validated successfully on resource %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName())
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
|
@ -210,55 +198,45 @@ func validatePatterns(ctx context.EvalInterface, resource unstructured.Unstructu
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// using anyPattern we can define multiple patterns and only one of them has to be successfully validated
|
if validationRule.AnyPattern != nil {
|
||||||
// return directly if one pattern succeed
|
var failedSubstitutionsErrors []error
|
||||||
// if none succeed, report violation / policyerror(TODO)
|
var failedAnyPatternsErrors []error
|
||||||
if rule.Validation.AnyPattern != nil {
|
var err error
|
||||||
var ruleFailureErrs []error
|
for idx, pattern := range validationRule.AnyPattern {
|
||||||
var failedPaths, invalidPaths []string
|
if pattern, err = variables.SubstituteVars(ctx, pattern); err != nil {
|
||||||
for index, pattern := range rule.Validation.AnyPattern {
|
// variable subsitution failed
|
||||||
path, err := validate.ValidateResourceWithPattern(ctx, resource.Object, pattern)
|
failedSubstitutionsErrors = append(failedSubstitutionsErrors, err)
|
||||||
// this pattern was successfully validated
|
continue
|
||||||
if reflect.DeepEqual(err, validate.ValidationError{}) {
|
}
|
||||||
glog.V(4).Infof("anyPattern %v successfully validated on resource %s/%s/%s", pattern, resource.GetKind(), resource.GetNamespace(), resource.GetName())
|
_, err := validate.ValidateResourceWithPattern(resource.Object, pattern)
|
||||||
|
if err == nil {
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
resp.Message = fmt.Sprintf("Validation rule '%s' anyPattern[%d] succeeded.", rule.Name, index)
|
resp.Message = fmt.Sprintf("Validation rule '%s' anyPattern[%d] succeeded.", rule.Name, idx)
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
glog.V(4).Infof("Validation error: %s; Validation rule %s anyPattern[%d] for %s/%s/%s",
|
||||||
switch err.StatusCode {
|
rule.Validation.Message, rule.Name, idx, resource.GetKind(), resource.GetNamespace(), resource.GetName())
|
||||||
case validate.PathNotPresent:
|
patternErr := fmt.Errorf("anyPattern[%d] failed; %s", idx, err)
|
||||||
invalidPaths = append(invalidPaths, err.ErrorMsg)
|
failedAnyPatternsErrors = append(failedAnyPatternsErrors, patternErr)
|
||||||
case validate.Rulefailure:
|
|
||||||
glog.V(4).Infof("Validation error: %s; Validation rule %s anyPattern[%d] failed at path %s for %s/%s/%s",
|
|
||||||
rule.Validation.Message, rule.Name, index, path, resource.GetKind(), resource.GetNamespace(), resource.GetName())
|
|
||||||
ruleFailureErrs = append(ruleFailureErrs, errors.New(err.ErrorMsg))
|
|
||||||
failedPaths = append(failedPaths, path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PathNotPresent
|
// Subsitution falures
|
||||||
if len(invalidPaths) != 0 {
|
if len(failedSubstitutionsErrors) > 0 {
|
||||||
resp.Success = true
|
resp.Success = false
|
||||||
resp.PathNotPresent = true
|
resp.Message = fmt.Sprintf("Subsitutions failed at paths: %v", failedSubstitutionsErrors)
|
||||||
resp.Message = fmt.Sprintf("referenced path not present: %s", strings.Join(invalidPaths, ";"))
|
|
||||||
glog.V(4).Infof("Skip applying rule '%s' on resource '%s/%s/%s': %s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resp.Message)
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// none of the anyPatterns succeed: len(ruleFailureErrs) > 0
|
// Any Pattern validation errors
|
||||||
glog.V(4).Infof("none of anyPattern comply with resource: %v", ruleFailureErrs)
|
if len(failedAnyPatternsErrors) > 0 {
|
||||||
resp.Success = false
|
var errorStr []string
|
||||||
var errorStr []string
|
for _, err := range failedAnyPatternsErrors {
|
||||||
for index, err := range ruleFailureErrs {
|
errorStr = append(errorStr, err.Error())
|
||||||
glog.V(4).Infof("anyPattern[%d] failed at path %s: %v", index, failedPaths[index], err)
|
}
|
||||||
str := fmt.Sprintf("Validation rule %s anyPattern[%d] failed at path %s.", rule.Name, index, failedPaths[index])
|
resp.Success = false
|
||||||
errorStr = append(errorStr, str)
|
resp.Message = fmt.Sprintf("Validation rule '%s' failed. %s", rule.Name, errorStr)
|
||||||
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Message = fmt.Sprintf("Validation error: %s; %s", rule.Validation.Message, strings.Join(errorStr, " "))
|
|
||||||
return resp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.RuleResponse{}
|
return response.RuleResponse{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -294,7 +294,7 @@ func TestValidate_Fail_anyPattern(t *testing.T) {
|
||||||
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
er := Validate(PolicyContext{Policy: policy, NewResource: *resourceUnstructured})
|
||||||
msgs := []string{"Validation error: A namespace is required; Validation rule check-default-namespace anyPattern[0] failed at path /metadata/namespace/. Validation rule check-default-namespace anyPattern[1] failed at path /metadata/namespace/."}
|
msgs := []string{"Validation rule 'check-default-namespace' failed. [anyPattern[0] failed; Validation rule failed at '/metadata/namespace/' to validate value '<nil>' with pattern '?*' anyPattern[1] failed; Validation rule failed at '/metadata/namespace/' to validate value '<nil>' with pattern '!default']"}
|
||||||
for index, r := range er.PolicyResponse.Rules {
|
for index, r := range er.PolicyResponse.Rules {
|
||||||
assert.Equal(t, r.Message, msgs[index])
|
assert.Equal(t, r.Message, msgs[index])
|
||||||
}
|
}
|
||||||
|
@ -1309,8 +1309,8 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) {
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
NewResource: *resourceUnstructured}
|
NewResource: *resourceUnstructured}
|
||||||
er := Validate(policyContext)
|
er := Validate(policyContext)
|
||||||
assert.Assert(t, er.PolicyResponse.Rules[0].Success, true)
|
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
|
||||||
assert.Assert(t, er.PolicyResponse.Rules[0].PathNotPresent, true)
|
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation error: ; Validation rule 'test-path-not-exist' failed. 'variable(s) not found or has nil values: [/spec/containers/0/name/{{request.object.metadata.name1}}]'")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfies(t *testing.T) {
|
func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfies(t *testing.T) {
|
||||||
|
@ -1399,10 +1399,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfies(t *t
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
NewResource: *resourceUnstructured}
|
NewResource: *resourceUnstructured}
|
||||||
er := Validate(policyContext)
|
er := Validate(policyContext)
|
||||||
assert.Assert(t, er.PolicyResponse.Rules[0].Success == true)
|
assert.Assert(t, er.PolicyResponse.Rules[0].Success)
|
||||||
assert.Assert(t, er.PolicyResponse.Rules[0].PathNotPresent == false)
|
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation rule 'test-path-not-exist' anyPattern[1] succeeded.")
|
||||||
expectMsg := "Validation rule 'test-path-not-exist' anyPattern[1] succeeded."
|
|
||||||
assert.Assert(t, er.PolicyResponse.Rules[0].Message == expectMsg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *testing.T) {
|
func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *testing.T) {
|
||||||
|
@ -1491,8 +1489,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
NewResource: *resourceUnstructured}
|
NewResource: *resourceUnstructured}
|
||||||
er := Validate(policyContext)
|
er := Validate(policyContext)
|
||||||
assert.Assert(t, er.PolicyResponse.Rules[0].Success, true)
|
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
|
||||||
assert.Assert(t, er.PolicyResponse.Rules[0].PathNotPresent, true)
|
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Subsitutions failed at paths: [variable(s) not found or has nil values: [/spec/template/spec/containers/0/name/{{request.object.metadata.name1}}] variable(s) not found or has nil values: [/spec/template/spec/containers/0/name/{{request.object.metadata.name2}}]]")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatternSatisfy(t *testing.T) {
|
func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatternSatisfy(t *testing.T) {
|
||||||
|
@ -1582,8 +1580,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatter
|
||||||
NewResource: *resourceUnstructured}
|
NewResource: *resourceUnstructured}
|
||||||
er := Validate(policyContext)
|
er := Validate(policyContext)
|
||||||
|
|
||||||
expectedMsg := "Validation error: ; Validation rule test-path-not-exist anyPattern[0] failed at path /spec/template/spec/containers/0/name/. Validation rule test-path-not-exist anyPattern[1] failed at path /spec/template/spec/containers/0/name/."
|
// expectedMsg := "Validation error: ; Validation rule test-path-not-exist anyPattern[0] failed at path /spec/template/spec/containers/0/name/. Validation rule test-path-not-exist anyPattern[1] failed at path /spec/template/spec/containers/0/name/."
|
||||||
assert.Assert(t, er.PolicyResponse.Rules[0].Success == false)
|
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
|
||||||
assert.Assert(t, er.PolicyResponse.Rules[0].PathNotPresent == false)
|
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "Validation rule 'test-path-not-exist' failed. [anyPattern[0] failed; Validation rule failed at '/spec/template/spec/containers/0/name/' to validate value 'pod-test-pod' with pattern 'test*' anyPattern[1] failed; Validation rule failed at '/spec/template/spec/containers/0/name/' to validate value 'pod-test-pod' with pattern 'test*']")
|
||||||
assert.Assert(t, er.PolicyResponse.Rules[0].Message == expectedMsg)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
//Evaluate evaluates the condition
|
//Evaluate evaluates the condition
|
||||||
func Evaluate(ctx context.EvalInterface, condition kyverno.Condition) bool {
|
func Evaluate(ctx context.EvalInterface, condition kyverno.Condition) bool {
|
||||||
// get handler for the operator
|
// get handler for the operator
|
||||||
handle := operator.CreateOperatorHandler(ctx, condition.Operator, SubstituteVariables)
|
handle := operator.CreateOperatorHandler(ctx, condition.Operator, SubstituteVars)
|
||||||
if handle == nil {
|
if handle == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,25 +25,36 @@ type EqualHandler struct {
|
||||||
|
|
||||||
//Evaluate evaluates expression with Equal Operator
|
//Evaluate evaluates expression with Equal Operator
|
||||||
func (eh EqualHandler) Evaluate(key, value interface{}) bool {
|
func (eh EqualHandler) Evaluate(key, value interface{}) bool {
|
||||||
|
var err error
|
||||||
|
//TODO: decouple variables from evaluation
|
||||||
// substitute the variables
|
// substitute the variables
|
||||||
nKey := eh.subHandler(eh.ctx, key)
|
if key, err = eh.subHandler(eh.ctx, key); err != nil {
|
||||||
nValue := eh.subHandler(eh.ctx, value)
|
// Failed to resolve the variable
|
||||||
|
glog.Infof("Failed to resolve variables in key: %s: %v", key, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if value, err = eh.subHandler(eh.ctx, value); err != nil {
|
||||||
|
// Failed to resolve the variable
|
||||||
|
glog.Infof("Failed to resolve variables in value: %s: %v", value, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// key and value need to be of same type
|
// key and value need to be of same type
|
||||||
switch typedKey := nKey.(type) {
|
switch typedKey := key.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
return eh.validateValuewithBoolPattern(typedKey, nValue)
|
return eh.validateValuewithBoolPattern(typedKey, value)
|
||||||
case int:
|
case int:
|
||||||
return eh.validateValuewithIntPattern(int64(typedKey), nValue)
|
return eh.validateValuewithIntPattern(int64(typedKey), value)
|
||||||
case int64:
|
case int64:
|
||||||
return eh.validateValuewithIntPattern(typedKey, nValue)
|
return eh.validateValuewithIntPattern(typedKey, value)
|
||||||
case float64:
|
case float64:
|
||||||
return eh.validateValuewithFloatPattern(typedKey, nValue)
|
return eh.validateValuewithFloatPattern(typedKey, value)
|
||||||
case string:
|
case string:
|
||||||
return eh.validateValuewithStringPattern(typedKey, nValue)
|
return eh.validateValuewithStringPattern(typedKey, value)
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
return eh.validateValueWithMapPattern(typedKey, nValue)
|
return eh.validateValueWithMapPattern(typedKey, value)
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
return eh.validateValueWithSlicePattern(typedKey, nValue)
|
return eh.validateValueWithSlicePattern(typedKey, value)
|
||||||
default:
|
default:
|
||||||
glog.Errorf("Unsupported type %v", typedKey)
|
glog.Errorf("Unsupported type %v", typedKey)
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -25,25 +25,35 @@ type NotEqualHandler struct {
|
||||||
|
|
||||||
//Evaluate evaluates expression with NotEqual Operator
|
//Evaluate evaluates expression with NotEqual Operator
|
||||||
func (neh NotEqualHandler) Evaluate(key, value interface{}) bool {
|
func (neh NotEqualHandler) Evaluate(key, value interface{}) bool {
|
||||||
|
var err error
|
||||||
|
//TODO: decouple variables from evaluation
|
||||||
// substitute the variables
|
// substitute the variables
|
||||||
nKey := neh.subHandler(neh.ctx, key)
|
if key, err = neh.subHandler(neh.ctx, key); err != nil {
|
||||||
nValue := neh.subHandler(neh.ctx, value)
|
// Failed to resolve the variable
|
||||||
|
glog.Infof("Failed to resolve variables in key: %s: %v", key, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if value, err = neh.subHandler(neh.ctx, value); err != nil {
|
||||||
|
// Failed to resolve the variable
|
||||||
|
glog.Infof("Failed to resolve variables in value: %s: %v", value, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
// key and value need to be of same type
|
// key and value need to be of same type
|
||||||
switch typedKey := nKey.(type) {
|
switch typedKey := key.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
return neh.validateValuewithBoolPattern(typedKey, nValue)
|
return neh.validateValuewithBoolPattern(typedKey, value)
|
||||||
case int:
|
case int:
|
||||||
return neh.validateValuewithIntPattern(int64(typedKey), nValue)
|
return neh.validateValuewithIntPattern(int64(typedKey), value)
|
||||||
case int64:
|
case int64:
|
||||||
return neh.validateValuewithIntPattern(typedKey, nValue)
|
return neh.validateValuewithIntPattern(typedKey, value)
|
||||||
case float64:
|
case float64:
|
||||||
return neh.validateValuewithFloatPattern(typedKey, nValue)
|
return neh.validateValuewithFloatPattern(typedKey, value)
|
||||||
case string:
|
case string:
|
||||||
return neh.validateValuewithStringPattern(typedKey, nValue)
|
return neh.validateValuewithStringPattern(typedKey, value)
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
return neh.validateValueWithMapPattern(typedKey, nValue)
|
return neh.validateValueWithMapPattern(typedKey, value)
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
return neh.validateValueWithSlicePattern(typedKey, nValue)
|
return neh.validateValueWithSlicePattern(typedKey, value)
|
||||||
default:
|
default:
|
||||||
glog.Error("Unsupported type %V", typedKey)
|
glog.Error("Unsupported type %V", typedKey)
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -17,7 +17,7 @@ type OperatorHandler interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
//VariableSubstitutionHandler defines the handler function for variable substitution
|
//VariableSubstitutionHandler defines the handler function for variable substitution
|
||||||
type VariableSubstitutionHandler = func(ctx context.EvalInterface, pattern interface{}) interface{}
|
type VariableSubstitutionHandler = func(ctx context.EvalInterface, pattern interface{}) (interface{}, error)
|
||||||
|
|
||||||
//CreateOperatorHandler returns the operator handler based on the operator used in condition
|
//CreateOperatorHandler returns the operator handler based on the operator used in condition
|
||||||
func CreateOperatorHandler(ctx context.EvalInterface, op kyverno.ConditionOperator, subHandler VariableSubstitutionHandler) OperatorHandler {
|
func CreateOperatorHandler(ctx context.EvalInterface, op kyverno.ConditionOperator, subHandler VariableSubstitutionHandler) OperatorHandler {
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
package variables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValidateVariables validates if referenced path is present
|
|
||||||
// return empty string if all paths are valid, otherwise return invalid path
|
|
||||||
func ValidateVariables(ctx context.EvalInterface, pattern interface{}) string {
|
|
||||||
var pathsNotPresent []string
|
|
||||||
variableList := extractVariables(pattern)
|
|
||||||
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(pathsNotPresent) != 0 {
|
|
||||||
return strings.Join(pathsNotPresent, ";")
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractVariables extracts variables in the pattern
|
|
||||||
func extractVariables(pattern interface{}) [][]string {
|
|
||||||
switch typedPattern := pattern.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
return extractMap(typedPattern)
|
|
||||||
case []interface{}:
|
|
||||||
return extractArray(typedPattern)
|
|
||||||
case string:
|
|
||||||
return extractValue(typedPattern)
|
|
||||||
default:
|
|
||||||
fmt.Printf("variable type %T", typedPattern)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractMap(patternMap map[string]interface{}) [][]string {
|
|
||||||
var variableList [][]string
|
|
||||||
|
|
||||||
for _, patternElement := range patternMap {
|
|
||||||
if vars := extractVariables(patternElement); vars != nil {
|
|
||||||
variableList = append(variableList, vars...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return variableList
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractArray(patternList []interface{}) [][]string {
|
|
||||||
var variableList [][]string
|
|
||||||
|
|
||||||
for _, patternElement := range patternList {
|
|
||||||
if vars := extractVariables(patternElement); vars != nil {
|
|
||||||
variableList = append(variableList, vars...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return variableList
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractValue(valuePattern string) [][]string {
|
|
||||||
operatorVariable := getOperator(valuePattern)
|
|
||||||
variable := valuePattern[len(operatorVariable):]
|
|
||||||
return extractValueVariable(variable)
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns multiple variable match groups
|
|
||||||
func extractValueVariable(valuePattern string) [][]string {
|
|
||||||
variableRegex := regexp.MustCompile(variableRegex)
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,175 +0,0 @@
|
||||||
package variables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
|
||||||
"gotest.tools/assert"
|
|
||||||
authenticationv1 "k8s.io/api/authentication/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_ExtractVariables(t *testing.T) {
|
|
||||||
patternRaw := []byte(`
|
|
||||||
{
|
|
||||||
"name": "ns-owner-{{request.userInfo.username}}",
|
|
||||||
"data": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"apiGroups": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"resources": [
|
|
||||||
"namespaces"
|
|
||||||
],
|
|
||||||
"verbs": [
|
|
||||||
"*"
|
|
||||||
],
|
|
||||||
"resourceNames": [
|
|
||||||
"{{request.object.metadata.name}}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
var pattern interface{}
|
|
||||||
if err := json.Unmarshal(patternRaw, &pattern); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := extractVariables(pattern)
|
|
||||||
|
|
||||||
result := [][]string{{"{{request.userInfo.username}}", "request.userInfo.username"},
|
|
||||||
{"{{request.object.metadata.name}}", "request.object.metadata.name"}}
|
|
||||||
|
|
||||||
assert.Assert(t, len(vars) == len(result), fmt.Sprintf("result does not match, var: %s", vars))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_ValidateVariables_NoVariable(t *testing.T) {
|
|
||||||
patternRaw := []byte(`
|
|
||||||
{
|
|
||||||
"name": "ns-owner",
|
|
||||||
"data": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"apiGroups": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"resources": [
|
|
||||||
"namespaces"
|
|
||||||
],
|
|
||||||
"verbs": [
|
|
||||||
"*"
|
|
||||||
],
|
|
||||||
"resourceNames": [
|
|
||||||
"Pod"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
resourceRaw := []byte(`
|
|
||||||
{
|
|
||||||
"metadata": {
|
|
||||||
"name": "temp",
|
|
||||||
"namespace": "n1"
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"namespace": "n1",
|
|
||||||
"name": "temp1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
// userInfo
|
|
||||||
userReqInfo := kyverno.RequestInfo{
|
|
||||||
AdmissionUserInfo: authenticationv1.UserInfo{
|
|
||||||
Username: "user1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var pattern, resource interface{}
|
|
||||||
assert.NilError(t, json.Unmarshal(patternRaw, &pattern))
|
|
||||||
assert.NilError(t, json.Unmarshal(resourceRaw, &resource))
|
|
||||||
|
|
||||||
var err error
|
|
||||||
ctx := context.NewContext()
|
|
||||||
err = ctx.AddResource(resourceRaw)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
err = ctx.AddUserInfo(userReqInfo)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
invalidPaths := ValidateVariables(ctx, pattern)
|
|
||||||
assert.Assert(t, len(invalidPaths) == 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_ValidateVariables(t *testing.T) {
|
|
||||||
patternRaw := []byte(`
|
|
||||||
{
|
|
||||||
"name": "ns-owner-{{request.userInfo.username}}",
|
|
||||||
"data": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"apiGroups": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"resources": [
|
|
||||||
"namespaces"
|
|
||||||
],
|
|
||||||
"verbs": [
|
|
||||||
"*"
|
|
||||||
],
|
|
||||||
"resourceNames": [
|
|
||||||
"{{request.object.metadata.name1}}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
resourceRaw := []byte(`
|
|
||||||
{
|
|
||||||
"metadata": {
|
|
||||||
"name": "temp",
|
|
||||||
"namespace": "n1"
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"namespace": "n1",
|
|
||||||
"name": "temp1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
// userInfo
|
|
||||||
userReqInfo := kyverno.RequestInfo{
|
|
||||||
AdmissionUserInfo: authenticationv1.UserInfo{
|
|
||||||
Username: "user1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var pattern, resource interface{}
|
|
||||||
assert.NilError(t, json.Unmarshal(patternRaw, &pattern))
|
|
||||||
assert.NilError(t, json.Unmarshal(resourceRaw, &resource))
|
|
||||||
|
|
||||||
ctx := context.NewContext()
|
|
||||||
var err error
|
|
||||||
err = ctx.AddResource(resourceRaw)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
err = ctx.AddUserInfo(userReqInfo)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidPaths := ValidateVariables(ctx, pattern)
|
|
||||||
assert.Assert(t, len(invalidPaths) > 0)
|
|
||||||
}
|
|
|
@ -1,146 +0,0 @@
|
||||||
package variables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/operator"
|
|
||||||
)
|
|
||||||
|
|
||||||
const variableRegex = `\{\{([^{}]*)\}\}`
|
|
||||||
|
|
||||||
//SubstituteVariables substitutes the JMESPATH with variable substitution
|
|
||||||
// supported substitutions
|
|
||||||
// - no operator + variable(string,object)
|
|
||||||
// unsupported substitutions
|
|
||||||
// - operator + variable(object) -> as we dont support operators with object types
|
|
||||||
func SubstituteVariables(ctx context.EvalInterface, pattern interface{}) interface{} {
|
|
||||||
// var err error
|
|
||||||
switch typedPattern := pattern.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
return substituteMap(ctx, typedPattern)
|
|
||||||
case []interface{}:
|
|
||||||
return substituteArray(ctx, typedPattern)
|
|
||||||
case string:
|
|
||||||
// variable substitution
|
|
||||||
return substituteValue(ctx, typedPattern)
|
|
||||||
default:
|
|
||||||
return pattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func substituteMap(ctx context.EvalInterface, patternMap map[string]interface{}) map[string]interface{} {
|
|
||||||
for key, patternElement := range patternMap {
|
|
||||||
value := SubstituteVariables(ctx, patternElement)
|
|
||||||
patternMap[key] = value
|
|
||||||
}
|
|
||||||
return patternMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func substituteArray(ctx context.EvalInterface, patternList []interface{}) []interface{} {
|
|
||||||
for idx, patternElement := range patternList {
|
|
||||||
value := SubstituteVariables(ctx, patternElement)
|
|
||||||
patternList[idx] = value
|
|
||||||
}
|
|
||||||
return patternList
|
|
||||||
}
|
|
||||||
|
|
||||||
func substituteValue(ctx context.EvalInterface, valuePattern string) interface{} {
|
|
||||||
// patterns supported
|
|
||||||
// - operator + string
|
|
||||||
// operator + variable
|
|
||||||
operatorVariable := getOperator(valuePattern)
|
|
||||||
variable := valuePattern[len(operatorVariable):]
|
|
||||||
// substitute variable with value
|
|
||||||
value := getValueQuery(ctx, variable)
|
|
||||||
if operatorVariable == "" {
|
|
||||||
// default or operator.Equal
|
|
||||||
// equal + string variable
|
|
||||||
// object variable
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
// operator + string variable
|
|
||||||
switch value.(type) {
|
|
||||||
case string:
|
|
||||||
return string(operatorVariable) + value.(string)
|
|
||||||
default:
|
|
||||||
glog.Infof("cannot use operator with object variables. operator used %s in pattern %v", string(operatorVariable), valuePattern)
|
|
||||||
var emptyInterface interface{}
|
|
||||||
return emptyInterface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getValueQuery(ctx context.EvalInterface, valuePattern string) interface{} {
|
|
||||||
var emptyInterface interface{}
|
|
||||||
// extract variable {{<variable>}}
|
|
||||||
validRegex := regexp.MustCompile(variableRegex)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// we do not support multiple 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
|
|
||||||
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", varName, err)
|
|
||||||
subs[varName] = emptyInterface
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if variable == nil {
|
|
||||||
subs[varName] = emptyInterface
|
|
||||||
} else {
|
|
||||||
subs[varName] = 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 {
|
|
||||||
operatorVariable := operator.GetOperatorFromStringPattern(pattern)
|
|
||||||
if operatorVariable == operator.Equal {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return string(operatorVariable)
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package variables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
//CheckVariables checks if the variable regex has been used
|
|
||||||
func CheckVariables(pattern interface{}, variables []string, path string) error {
|
|
||||||
switch typedPattern := pattern.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
return checkMap(typedPattern, variables, path)
|
|
||||||
case []interface{}:
|
|
||||||
return checkArray(typedPattern, variables, path)
|
|
||||||
case string:
|
|
||||||
return checkValue(typedPattern, variables, path)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkMap(patternMap map[string]interface{}, variables []string, path string) error {
|
|
||||||
for patternKey, patternElement := range patternMap {
|
|
||||||
|
|
||||||
if err := CheckVariables(patternElement, variables, path+patternKey+"/"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkArray(patternList []interface{}, variables []string, path string) error {
|
|
||||||
for idx, patternElement := range patternList {
|
|
||||||
if err := CheckVariables(patternElement, variables, path+strconv.Itoa(idx)+"/"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkValue(valuePattern string, variables []string, path string) error {
|
|
||||||
operatorVariable := getOperator(valuePattern)
|
|
||||||
variable := valuePattern[len(operatorVariable):]
|
|
||||||
if checkValueVariable(variable, variables) {
|
|
||||||
return fmt.Errorf(path + valuePattern)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkValueVariable(valuePattern string, variables []string) bool {
|
|
||||||
variableRegex := regexp.MustCompile(variableRegex)
|
|
||||||
groups := variableRegex.FindAllStringSubmatch(valuePattern, -1)
|
|
||||||
if len(groups) == 0 {
|
|
||||||
// no variables
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// 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 {
|
|
||||||
for _, regex := range regexs {
|
|
||||||
varRegex := regexp.MustCompile(regex)
|
|
||||||
found := varRegex.FindString(pattern)
|
|
||||||
if found != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -58,12 +58,16 @@ func Test_variablesub1(t *testing.T) {
|
||||||
|
|
||||||
resultMap := []byte(`{"data":{"rules":[{"apiGroups":[""],"resourceNames":["temp"],"resources":["namespaces"],"verbs":["*"]}]},"kind":"ClusterRole","name":"ns-owner-user1"}`)
|
resultMap := []byte(`{"data":{"rules":[{"apiGroups":[""],"resourceNames":["temp"],"resources":["namespaces"],"verbs":["*"]}]},"kind":"ClusterRole","name":"ns-owner-user1"}`)
|
||||||
|
|
||||||
var pattern, resource interface{}
|
var pattern, patternCopy, resource interface{}
|
||||||
var err error
|
var err error
|
||||||
err = json.Unmarshal(patternMap, &pattern)
|
err = json.Unmarshal(patternMap, &pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
err = json.Unmarshal(patternMap, &patternCopy)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
err = json.Unmarshal(resourceRaw, &resource)
|
err = json.Unmarshal(resourceRaw, &resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
@ -80,12 +84,15 @@ func Test_variablesub1(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := SubstituteVariables(ctx, pattern)
|
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
|
||||||
resultRaw, err := json.Marshal(value)
|
t.Error(err)
|
||||||
|
}
|
||||||
|
resultRaw, err := json.Marshal(patternCopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(resultMap, resultRaw) {
|
|
||||||
|
if !reflect.DeepEqual(resultRaw, resultMap) {
|
||||||
t.Log(string(resultMap))
|
t.Log(string(resultMap))
|
||||||
t.Log(string(resultRaw))
|
t.Log(string(resultRaw))
|
||||||
t.Error("result does not match")
|
t.Error("result does not match")
|
||||||
|
@ -139,12 +146,16 @@ func Test_variablesub_multiple(t *testing.T) {
|
||||||
|
|
||||||
resultMap := []byte(`{"data":{"rules":[{"apiGroups":[""],"resourceNames":["temp"],"resources":["namespaces"],"verbs":["*"]}]},"kind":"ClusterRole","name":"ns-owner-n1-user1-bindings"}`)
|
resultMap := []byte(`{"data":{"rules":[{"apiGroups":[""],"resourceNames":["temp"],"resources":["namespaces"],"verbs":["*"]}]},"kind":"ClusterRole","name":"ns-owner-n1-user1-bindings"}`)
|
||||||
|
|
||||||
var pattern, resource interface{}
|
var pattern, patternCopy, resource interface{}
|
||||||
var err error
|
var err error
|
||||||
err = json.Unmarshal(patternMap, &pattern)
|
err = json.Unmarshal(patternMap, &pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
err = json.Unmarshal(patternMap, &patternCopy)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(resourceRaw, &resource)
|
err = json.Unmarshal(resourceRaw, &resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -163,11 +174,14 @@ func Test_variablesub_multiple(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := SubstituteVariables(ctx, pattern)
|
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
|
||||||
resultRaw, err := json.Marshal(value)
|
t.Error(err)
|
||||||
|
}
|
||||||
|
resultRaw, err := json.Marshal(patternCopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(resultMap, resultRaw) {
|
if !reflect.DeepEqual(resultMap, resultRaw) {
|
||||||
t.Log(string(resultMap))
|
t.Log(string(resultMap))
|
||||||
t.Log(string(resultRaw))
|
t.Log(string(resultRaw))
|
||||||
|
@ -219,12 +233,16 @@ func Test_variablesubstitution(t *testing.T) {
|
||||||
Username: "user1",
|
Username: "user1",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var pattern, resource interface{}
|
var pattern, patternCopy, resource interface{}
|
||||||
var err error
|
var err error
|
||||||
err = json.Unmarshal(patternMap, &pattern)
|
err = json.Unmarshal(patternMap, &pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
err = json.Unmarshal(patternMap, &patternCopy)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(resourceRaw, &resource)
|
err = json.Unmarshal(resourceRaw, &resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -243,8 +261,10 @@ func Test_variablesubstitution(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := SubstituteVariables(ctx, pattern)
|
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
|
||||||
resultRaw, err := json.Marshal(value)
|
t.Error(err)
|
||||||
|
}
|
||||||
|
resultRaw, err := json.Marshal(patternCopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -279,12 +299,16 @@ func Test_variableSubstitutionValue(t *testing.T) {
|
||||||
|
|
||||||
resultMap := []byte(`{"spec":{"name":"temp"}}`)
|
resultMap := []byte(`{"spec":{"name":"temp"}}`)
|
||||||
|
|
||||||
var pattern, resource interface{}
|
var pattern, patternCopy, resource interface{}
|
||||||
var err error
|
var err error
|
||||||
err = json.Unmarshal(patternMap, &pattern)
|
err = json.Unmarshal(patternMap, &pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
err = json.Unmarshal(patternMap, &patternCopy)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(resourceRaw, &resource)
|
err = json.Unmarshal(resourceRaw, &resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -298,8 +322,10 @@ func Test_variableSubstitutionValue(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := SubstituteVariables(ctx, pattern)
|
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
|
||||||
resultRaw, err := json.Marshal(value)
|
t.Error(err)
|
||||||
|
}
|
||||||
|
resultRaw, err := json.Marshal(patternCopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -331,12 +357,16 @@ func Test_variableSubstitutionValueOperatorNotEqual(t *testing.T) {
|
||||||
`)
|
`)
|
||||||
resultMap := []byte(`{"spec":{"name":"!temp"}}`)
|
resultMap := []byte(`{"spec":{"name":"!temp"}}`)
|
||||||
|
|
||||||
var pattern, resource interface{}
|
var pattern, patternCopy, resource interface{}
|
||||||
var err error
|
var err error
|
||||||
err = json.Unmarshal(patternMap, &pattern)
|
err = json.Unmarshal(patternMap, &pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
err = json.Unmarshal(patternMap, &patternCopy)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(resourceRaw, &resource)
|
err = json.Unmarshal(resourceRaw, &resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -350,14 +380,16 @@ func Test_variableSubstitutionValueOperatorNotEqual(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := SubstituteVariables(ctx, pattern)
|
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
|
||||||
resultRaw, err := json.Marshal(value)
|
t.Error(err)
|
||||||
|
}
|
||||||
|
resultRaw, err := json.Marshal(patternCopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
t.Log(string(resultRaw))
|
|
||||||
t.Log(string(resultMap))
|
|
||||||
if !reflect.DeepEqual(resultMap, resultRaw) {
|
if !reflect.DeepEqual(resultMap, resultRaw) {
|
||||||
|
t.Log(string(resultRaw))
|
||||||
|
t.Log(string(resultMap))
|
||||||
t.Error("result does not match")
|
t.Error("result does not match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -383,14 +415,17 @@ func Test_variableSubstitutionValueFail(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
resultMap := []byte(`{"spec":{"name":null}}`)
|
|
||||||
|
|
||||||
var pattern, resource interface{}
|
var pattern, patternCopy, resource interface{}
|
||||||
var err error
|
var err error
|
||||||
err = json.Unmarshal(patternMap, &pattern)
|
err = json.Unmarshal(patternMap, &pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
err = json.Unmarshal(patternMap, &patternCopy)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(resourceRaw, &resource)
|
err = json.Unmarshal(resourceRaw, &resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -404,16 +439,11 @@ func Test_variableSubstitutionValueFail(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := SubstituteVariables(ctx, pattern)
|
if _, err := SubstituteVars(ctx, patternCopy); err == nil {
|
||||||
resultRaw, err := json.Marshal(value)
|
t.Log("expected to fails")
|
||||||
if err != nil {
|
t.Fail()
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
t.Log(string(resultRaw))
|
|
||||||
t.Log(string(resultMap))
|
|
||||||
if !reflect.DeepEqual(resultMap, resultRaw) {
|
|
||||||
t.Error("result does not match")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_variableSubstitutionObject(t *testing.T) {
|
func Test_variableSubstitutionObject(t *testing.T) {
|
||||||
|
@ -444,12 +474,16 @@ func Test_variableSubstitutionObject(t *testing.T) {
|
||||||
`)
|
`)
|
||||||
resultMap := []byte(`{"spec":{"variable":{"var1":"temp1","var2":"temp2","varNested":{"var1":"temp1"}}}}`)
|
resultMap := []byte(`{"spec":{"variable":{"var1":"temp1","var2":"temp2","varNested":{"var1":"temp1"}}}}`)
|
||||||
|
|
||||||
var pattern, resource interface{}
|
var pattern, patternCopy, resource interface{}
|
||||||
var err error
|
var err error
|
||||||
err = json.Unmarshal(patternMap, &pattern)
|
err = json.Unmarshal(patternMap, &pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
err = json.Unmarshal(patternMap, &patternCopy)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(resourceRaw, &resource)
|
err = json.Unmarshal(resourceRaw, &resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -463,14 +497,16 @@ func Test_variableSubstitutionObject(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := SubstituteVariables(ctx, pattern)
|
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
|
||||||
resultRaw, err := json.Marshal(value)
|
t.Error(err)
|
||||||
|
}
|
||||||
|
resultRaw, err := json.Marshal(patternCopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
t.Log(string(resultRaw))
|
|
||||||
t.Log(string(resultMap))
|
|
||||||
if !reflect.DeepEqual(resultMap, resultRaw) {
|
if !reflect.DeepEqual(resultMap, resultRaw) {
|
||||||
|
t.Log(string(resultRaw))
|
||||||
|
t.Log(string(resultMap))
|
||||||
t.Error("result does not match")
|
t.Error("result does not match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -504,12 +540,16 @@ func Test_variableSubstitutionObjectOperatorNotEqualFail(t *testing.T) {
|
||||||
|
|
||||||
resultMap := []byte(`{"spec":{"variable":null}}`)
|
resultMap := []byte(`{"spec":{"variable":null}}`)
|
||||||
|
|
||||||
var pattern, resource interface{}
|
var pattern, patternCopy, resource interface{}
|
||||||
var err error
|
var err error
|
||||||
err = json.Unmarshal(patternMap, &pattern)
|
err = json.Unmarshal(patternMap, &pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
err = json.Unmarshal(patternMap, &patternCopy)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(resourceRaw, &resource)
|
err = json.Unmarshal(resourceRaw, &resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -523,14 +563,16 @@ func Test_variableSubstitutionObjectOperatorNotEqualFail(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := SubstituteVariables(ctx, pattern)
|
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
|
||||||
resultRaw, err := json.Marshal(value)
|
t.Error(err)
|
||||||
|
}
|
||||||
|
resultRaw, err := json.Marshal(patternCopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
t.Log(string(resultRaw))
|
|
||||||
t.Log(string(resultMap))
|
|
||||||
if !reflect.DeepEqual(resultMap, resultRaw) {
|
if !reflect.DeepEqual(resultMap, resultRaw) {
|
||||||
|
t.Log(string(resultRaw))
|
||||||
|
t.Log(string(resultMap))
|
||||||
t.Error("result does not match")
|
t.Error("result does not match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -565,12 +607,16 @@ func Test_variableSubstitutionMultipleObject(t *testing.T) {
|
||||||
|
|
||||||
resultMap := []byte(`{"spec":{"var":"temp1","variable":{"var1":"temp1","var2":"temp2","varNested":{"var1":"temp1"}}}}`)
|
resultMap := []byte(`{"spec":{"var":"temp1","variable":{"var1":"temp1","var2":"temp2","varNested":{"var1":"temp1"}}}}`)
|
||||||
|
|
||||||
var pattern, resource interface{}
|
var pattern, patternCopy, resource interface{}
|
||||||
var err error
|
var err error
|
||||||
err = json.Unmarshal(patternMap, &pattern)
|
err = json.Unmarshal(patternMap, &pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
err = json.Unmarshal(patternMap, &patternCopy)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(resourceRaw, &resource)
|
err = json.Unmarshal(resourceRaw, &resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -584,14 +630,16 @@ func Test_variableSubstitutionMultipleObject(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := SubstituteVariables(ctx, pattern)
|
if _, err := SubstituteVars(ctx, patternCopy); err != nil {
|
||||||
resultRaw, err := json.Marshal(value)
|
t.Error(err)
|
||||||
|
}
|
||||||
|
resultRaw, err := json.Marshal(patternCopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
t.Log(string(resultRaw))
|
|
||||||
t.Log(string(resultMap))
|
|
||||||
if !reflect.DeepEqual(resultMap, resultRaw) {
|
if !reflect.DeepEqual(resultMap, resultRaw) {
|
||||||
|
t.Log(string(resultRaw))
|
||||||
|
t.Log(string(resultMap))
|
||||||
t.Error("result does not match")
|
t.Error("result does not match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
175
pkg/engine/variables/vars.go
Normal file
175
pkg/engine/variables/vars.go
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
package variables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||||
|
"github.com/nirmata/kyverno/pkg/engine/operator"
|
||||||
|
)
|
||||||
|
|
||||||
|
const variableRegex = `\{\{([^{}]*)\}\}`
|
||||||
|
|
||||||
|
//SubstituteVars replaces the variables with the values defined in the context
|
||||||
|
// - if any variable is invaid or has nil value, it is considered as a failed varable substitution
|
||||||
|
func SubstituteVars(ctx context.EvalInterface, pattern interface{}) (interface{}, error) {
|
||||||
|
errs := []error{}
|
||||||
|
pattern = subVars(ctx, pattern, "", &errs)
|
||||||
|
if len(errs) == 0 {
|
||||||
|
// no error while parsing the pattern
|
||||||
|
return pattern, nil
|
||||||
|
}
|
||||||
|
return pattern, fmt.Errorf("variable(s) not found or has nil values: %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func subVars(ctx context.EvalInterface, pattern interface{}, path string, errs *[]error) interface{} {
|
||||||
|
switch typedPattern := pattern.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
return subMap(ctx, typedPattern, path, errs)
|
||||||
|
case []interface{}:
|
||||||
|
return subArray(ctx, typedPattern, path, errs)
|
||||||
|
case string:
|
||||||
|
return subVal(ctx, typedPattern, path, errs)
|
||||||
|
default:
|
||||||
|
return pattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func subMap(ctx context.EvalInterface, patternMap map[string]interface{}, path string, errs *[]error) map[string]interface{} {
|
||||||
|
for key, patternElement := range patternMap {
|
||||||
|
curPath := path + "/" + key
|
||||||
|
value := subVars(ctx, patternElement, curPath, errs)
|
||||||
|
patternMap[key] = value
|
||||||
|
|
||||||
|
}
|
||||||
|
return patternMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func subArray(ctx context.EvalInterface, patternList []interface{}, path string, errs *[]error) []interface{} {
|
||||||
|
for idx, patternElement := range patternList {
|
||||||
|
curPath := path + "/" + strconv.Itoa(idx)
|
||||||
|
value := subVars(ctx, patternElement, curPath, errs)
|
||||||
|
patternList[idx] = value
|
||||||
|
}
|
||||||
|
return patternList
|
||||||
|
}
|
||||||
|
|
||||||
|
func subVal(ctx context.EvalInterface, valuePattern interface{}, path string, errs *[]error) interface{} {
|
||||||
|
var emptyInterface interface{}
|
||||||
|
valueStr, ok := valuePattern.(string)
|
||||||
|
if !ok {
|
||||||
|
glog.Infof("failed to convert %v to string", valuePattern)
|
||||||
|
return emptyInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
operatorVariable := getOp(valueStr)
|
||||||
|
variable := valueStr[len(operatorVariable):]
|
||||||
|
// substitute variable with value
|
||||||
|
value, failedVars := getValQuery(ctx, variable)
|
||||||
|
// if there are failedVars at this level
|
||||||
|
// capture as error and the path to the variables
|
||||||
|
for _, failedVar := range failedVars {
|
||||||
|
failedPath := path + "/" + failedVar
|
||||||
|
*errs = append(*errs, NewInvalidPath(failedPath))
|
||||||
|
}
|
||||||
|
if operatorVariable == "" {
|
||||||
|
// default or operator.Equal
|
||||||
|
// equal + string value
|
||||||
|
// object variable
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
// operator + string variable
|
||||||
|
switch typedValue := value.(type) {
|
||||||
|
case string:
|
||||||
|
return string(operatorVariable) + typedValue
|
||||||
|
default:
|
||||||
|
glog.Infof("cannot use operator with object variables. operator used %s in pattern %v", string(operatorVariable), valuePattern)
|
||||||
|
return emptyInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOp(pattern string) string {
|
||||||
|
operatorVariable := operator.GetOperatorFromStringPattern(pattern)
|
||||||
|
if operatorVariable == operator.Equal {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(operatorVariable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getValQuery(ctx context.EvalInterface, valuePattern string) (interface{}, []string) {
|
||||||
|
var emptyInterface interface{}
|
||||||
|
validRegex := regexp.MustCompile(variableRegex)
|
||||||
|
groups := validRegex.FindAllStringSubmatch(valuePattern, -1)
|
||||||
|
// there can be multiple varialbes in a single value pattern
|
||||||
|
varMap, failedVars := getVal(ctx, groups)
|
||||||
|
if len(varMap) == 0 && len(failedVars) == 0 {
|
||||||
|
// no variables
|
||||||
|
// return original value
|
||||||
|
return valuePattern, nil
|
||||||
|
}
|
||||||
|
if isAllStrings(varMap) {
|
||||||
|
newVal := valuePattern
|
||||||
|
for key, value := range varMap {
|
||||||
|
if val, ok := value.(string); ok {
|
||||||
|
newVal = strings.Replace(newVal, key, val, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newVal, failedVars
|
||||||
|
}
|
||||||
|
// multiple substitution per statement for non-string types are not supported
|
||||||
|
for _, value := range varMap {
|
||||||
|
return value, failedVars
|
||||||
|
}
|
||||||
|
return emptyInterface, failedVars
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVal(ctx context.EvalInterface, groups [][]string) (map[string]interface{}, []string) {
|
||||||
|
substiutions := map[string]interface{}{}
|
||||||
|
var failedVars []string
|
||||||
|
for _, group := range groups {
|
||||||
|
// 0th is the string
|
||||||
|
varName := group[0]
|
||||||
|
varValue := group[1]
|
||||||
|
variable, err := ctx.Query(varValue)
|
||||||
|
// err !=nil -> invalid expression
|
||||||
|
// err == nil && variable == nil -> variable is empty or path is not present
|
||||||
|
// a variable with empty value is considered as a failed variable
|
||||||
|
if err != nil || (err == nil && variable == nil) {
|
||||||
|
// could not find the variable at the given path
|
||||||
|
failedVars = append(failedVars, varName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
substiutions[varName] = variable
|
||||||
|
}
|
||||||
|
return substiutions, failedVars
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAllStrings(subVar map[string]interface{}) bool {
|
||||||
|
if len(subVar) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, value := range subVar {
|
||||||
|
if _, ok := value.(string); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//InvalidPath stores the path to failed variable
|
||||||
|
type InvalidPath struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *InvalidPath) Error() string {
|
||||||
|
return e.path
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewInvalidPath returns a new Invalid Path error
|
||||||
|
func NewInvalidPath(path string) *InvalidPath {
|
||||||
|
return &InvalidPath{path: path}
|
||||||
|
}
|
130
pkg/engine/variables/vars_test.go
Normal file
130
pkg/engine/variables/vars_test.go
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
package variables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_subVars_success(t *testing.T) {
|
||||||
|
patternMap := []byte(`
|
||||||
|
{
|
||||||
|
"kind": "{{request.object.metadata.name}}",
|
||||||
|
"name": "ns-owner-{{request.object.metadata.name}}",
|
||||||
|
"data": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"apiGroups": [
|
||||||
|
"{{request.object.metadata.name}}"
|
||||||
|
],
|
||||||
|
"resources": [
|
||||||
|
"namespaces"
|
||||||
|
],
|
||||||
|
"verbs": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"resourceNames": [
|
||||||
|
"{{request.object.metadata.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
resourceRaw := []byte(`
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"name": "temp",
|
||||||
|
"namespace": "n1"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"namespace": "n1",
|
||||||
|
"name": "temp1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
var pattern, resource interface{}
|
||||||
|
var err error
|
||||||
|
err = json.Unmarshal(patternMap, &pattern)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(resourceRaw, &resource)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
// context
|
||||||
|
ctx := context.NewContext()
|
||||||
|
err = ctx.AddResource(resourceRaw)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := SubstituteVars(ctx, pattern); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_subVars_failed(t *testing.T) {
|
||||||
|
patternMap := []byte(`
|
||||||
|
{
|
||||||
|
"kind": "{{request.object.metadata.name1}}",
|
||||||
|
"name": "ns-owner-{{request.object.metadata.name}}",
|
||||||
|
"data": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"apiGroups": [
|
||||||
|
"{{request.object.metadata.name}}"
|
||||||
|
],
|
||||||
|
"resources": [
|
||||||
|
"namespaces"
|
||||||
|
],
|
||||||
|
"verbs": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"resourceNames": [
|
||||||
|
"{{request.object.metadata.name1}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
resourceRaw := []byte(`
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"name": "temp",
|
||||||
|
"namespace": "n1"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"namespace": "n1",
|
||||||
|
"name": "temp1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
var pattern, resource interface{}
|
||||||
|
var err error
|
||||||
|
err = json.Unmarshal(patternMap, &pattern)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(resourceRaw, &resource)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
// context
|
||||||
|
ctx := context.NewContext()
|
||||||
|
err = ctx.AddResource(resourceRaw)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := SubstituteVars(ctx, pattern); err == nil {
|
||||||
|
t.Error("error is expected")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,42 +1,24 @@
|
||||||
package cleanup
|
package cleanup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
dclient "github.com/nirmata/kyverno/pkg/dclient"
|
dclient "github.com/nirmata/kyverno/pkg/dclient"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const timoutMins = 2
|
|
||||||
const timeout = time.Minute * timoutMins // 2 minutes
|
|
||||||
|
|
||||||
func (c *Controller) processGR(gr kyverno.GenerateRequest) error {
|
func (c *Controller) processGR(gr kyverno.GenerateRequest) error {
|
||||||
// 1-Corresponding policy has been deleted
|
// 1- Corresponding policy has been deleted
|
||||||
_, err := c.pLister.Get(gr.Spec.Policy)
|
// then we dont delete the generated resources
|
||||||
if errors.IsNotFound(err) {
|
|
||||||
glog.V(4).Infof("delete GR %s", gr.Name)
|
|
||||||
return c.control.Delete(gr.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2- Check for elapsed time since update
|
// 2- The trigger resource is deleted, then delete the generated resources
|
||||||
if gr.Status.State == kyverno.Completed {
|
if !ownerResourceExists(c.client, gr) {
|
||||||
glog.V(4).Infof("checking if owner exists for gr %s", gr.Name)
|
if err := deleteGeneratedResources(c.client, gr); err != nil {
|
||||||
if !ownerResourceExists(c.client, gr) {
|
return err
|
||||||
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
|
// - trigger-resource is deleted
|
||||||
}
|
// - generated-resources are deleted
|
||||||
createTime := gr.GetCreationTimestamp()
|
// - > Now delete the GenerateRequest CR
|
||||||
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 successfully in %d minutes", gr.Name, timoutMins)
|
|
||||||
glog.V(4).Infof("delete GR %s", gr.Name)
|
|
||||||
return c.control.Delete(gr.Name)
|
return c.control.Delete(gr.Name)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -44,16 +26,22 @@ func (c *Controller) processGR(gr kyverno.GenerateRequest) error {
|
||||||
|
|
||||||
func ownerResourceExists(client *dclient.Client, gr kyverno.GenerateRequest) bool {
|
func ownerResourceExists(client *dclient.Client, gr kyverno.GenerateRequest) bool {
|
||||||
_, err := client.GetResource(gr.Spec.Resource.Kind, gr.Spec.Resource.Namespace, gr.Spec.Resource.Name)
|
_, err := client.GetResource(gr.Spec.Resource.Kind, gr.Spec.Resource.Namespace, gr.Spec.Resource.Name)
|
||||||
if err != nil {
|
// trigger resources has been deleted
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
glog.V(4).Infof("Failed to get resource %s/%s/%s: error : %s", gr.Spec.Resource.Kind, gr.Spec.Resource.Namespace, gr.Spec.Resource.Name, err)
|
||||||
|
}
|
||||||
|
// if there was an error while querying the resources we dont delete the generated resources
|
||||||
|
// but expect the deletion in next reconciliation loop
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteGeneratedResources(client *dclient.Client, gr kyverno.GenerateRequest) error {
|
func deleteGeneratedResources(client *dclient.Client, gr kyverno.GenerateRequest) error {
|
||||||
for _, genResource := range gr.Status.GeneratedResources {
|
for _, genResource := range gr.Status.GeneratedResources {
|
||||||
err := client.DeleteResource(genResource.Kind, genResource.Namespace, genResource.Name, false)
|
err := client.DeleteResource(genResource.Kind, genResource.Namespace, genResource.Name, false)
|
||||||
if errors.IsNotFound(err) {
|
if apierrors.IsNotFound(err) {
|
||||||
glog.V(4).Infof("resource %s/%s/%s not found, will no delete", genResource.Kind, genResource.Namespace, genResource.Name)
|
glog.V(4).Infof("resource %s/%s/%s not found, will no delete", genResource.Kind, genResource.Namespace, genResource.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package generate
|
package generate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
|
@ -12,10 +11,8 @@ import (
|
||||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/validate"
|
"github.com/nirmata/kyverno/pkg/engine/validate"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/variables"
|
"github.com/nirmata/kyverno/pkg/engine/variables"
|
||||||
"github.com/nirmata/kyverno/pkg/policyviolation"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Controller) processGR(gr *kyverno.GenerateRequest) error {
|
func (c *Controller) processGR(gr *kyverno.GenerateRequest) error {
|
||||||
|
@ -29,24 +26,10 @@ func (c *Controller) processGR(gr *kyverno.GenerateRequest) error {
|
||||||
glog.V(4).Infof("resource does not exist or is yet to be created, requeuing: %v", err)
|
glog.V(4).Infof("resource does not exist or is yet to be created, requeuing: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 - Apply the generate policy on the resource
|
// 2 - Apply the generate policy on the resource
|
||||||
genResources, err = c.applyGenerate(*resource, *gr)
|
genResources, err = c.applyGenerate(*resource, *gr)
|
||||||
switch e := err.(type) {
|
|
||||||
case *Violation:
|
|
||||||
// Generate event
|
|
||||||
// - resource -> rule failed and created PV
|
|
||||||
// - policy -> failed to apply of resource and created PV
|
|
||||||
c.pvGenerator.Add(generatePV(*gr, *resource, e))
|
|
||||||
default:
|
|
||||||
// Generate event
|
|
||||||
// - resource -> rule failed
|
|
||||||
// - policy -> failed tp apply on resource
|
|
||||||
glog.V(4).Info(e)
|
|
||||||
}
|
|
||||||
// 3 - Report Events
|
// 3 - Report Events
|
||||||
reportEvents(err, c.eventGen, *gr, *resource)
|
reportEvents(err, c.eventGen, *gr, *resource)
|
||||||
|
|
||||||
// 4 - Update Status
|
// 4 - Update Status
|
||||||
return updateStatus(c.statusControl, *gr, err, genResources)
|
return updateStatus(c.statusControl, *gr, err, genResources)
|
||||||
}
|
}
|
||||||
|
@ -96,15 +79,8 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
|
||||||
return nil, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pv := buildPathNotPresentPV(engineResponse); pv != nil {
|
|
||||||
c.pvGenerator.Add(pv...)
|
|
||||||
// variable substitiution fails in ruleInfo (match,exclude,condition)
|
|
||||||
// the overall policy should not apply to resource
|
|
||||||
return nil, fmt.Errorf("referenced path not present in generate policy %s", policy.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the generate rule on resource
|
// Apply the generate rule on resource
|
||||||
return applyGeneratePolicy(c.client, policyContext, gr.Status.State)
|
return applyGeneratePolicy(c.client, policyContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateStatus(statusControl StatusControlInterface, gr kyverno.GenerateRequest, err error, genResources []kyverno.ResourceSpec) error {
|
func updateStatus(statusControl StatusControlInterface, gr kyverno.GenerateRequest, err error, genResources []kyverno.ResourceSpec) error {
|
||||||
|
@ -116,7 +92,7 @@ func updateStatus(statusControl StatusControlInterface, gr kyverno.GenerateReque
|
||||||
return statusControl.Success(gr, genResources)
|
return statusControl.Success(gr, genResources)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyGeneratePolicy(client *dclient.Client, policyContext engine.PolicyContext, state kyverno.GenerateRequestState) ([]kyverno.ResourceSpec, error) {
|
func applyGeneratePolicy(client *dclient.Client, policyContext engine.PolicyContext) ([]kyverno.ResourceSpec, error) {
|
||||||
// List of generatedResources
|
// List of generatedResources
|
||||||
var genResources []kyverno.ResourceSpec
|
var genResources []kyverno.ResourceSpec
|
||||||
// Get the response as the actions to be performed on the resource
|
// Get the response as the actions to be performed on the resource
|
||||||
|
@ -135,7 +111,7 @@ func applyGeneratePolicy(client *dclient.Client, policyContext engine.PolicyCont
|
||||||
if !rule.HasGenerate() {
|
if !rule.HasGenerate() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
genResource, err := applyRule(client, rule, resource, ctx, state, processExisting)
|
genResource, err := applyRule(client, rule, resource, ctx, processExisting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -145,65 +121,64 @@ func applyGeneratePolicy(client *dclient.Client, policyContext engine.PolicyCont
|
||||||
return genResources, nil
|
return genResources, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.Unstructured, ctx context.EvalInterface, state kyverno.GenerateRequestState, processExisting bool) (kyverno.ResourceSpec, error) {
|
func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.Unstructured, ctx context.EvalInterface, processExisting bool) (kyverno.ResourceSpec, error) {
|
||||||
var rdata map[string]interface{}
|
var rdata map[string]interface{}
|
||||||
var err error
|
var err error
|
||||||
|
var mode ResourceMode
|
||||||
var noGenResource kyverno.ResourceSpec
|
var noGenResource kyverno.ResourceSpec
|
||||||
|
// convert to unstructured Resource
|
||||||
if invalidPaths := variables.ValidateVariables(ctx, rule.Generation.ResourceSpec); len(invalidPaths) != 0 {
|
genUnst, err := getUnstrRule(rule.Generation.DeepCopy())
|
||||||
return noGenResource, NewViolation(rule.Name, fmt.Errorf("path not present in generate resource spec: %s", invalidPaths))
|
if err != nil {
|
||||||
|
return noGenResource, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable substitutions
|
||||||
|
// format : {{<variable_name}}
|
||||||
|
// - if there is variables that are not defined the context -> results in error and rule is not applied
|
||||||
|
// - valid variables are replaced with the values
|
||||||
|
if _, err := variables.SubstituteVars(ctx, genUnst.Object); err != nil {
|
||||||
|
return noGenResource, err
|
||||||
|
}
|
||||||
|
genKind, _, err := unstructured.NestedString(genUnst.Object, "kind")
|
||||||
|
if err != nil {
|
||||||
|
return noGenResource, err
|
||||||
|
}
|
||||||
|
genName, _, err := unstructured.NestedString(genUnst.Object, "name")
|
||||||
|
if err != nil {
|
||||||
|
return noGenResource, err
|
||||||
|
}
|
||||||
|
genNamespace, _, err := unstructured.NestedString(genUnst.Object, "namespace")
|
||||||
|
if err != nil {
|
||||||
|
return noGenResource, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// variable substitution
|
|
||||||
// - name
|
|
||||||
// - namespace
|
|
||||||
// - clone.name
|
|
||||||
// - clone.namespace
|
|
||||||
gen := variableSubsitutionForAttributes(rule.Generation, ctx)
|
|
||||||
// Resource to be generated
|
// Resource to be generated
|
||||||
newGenResource := kyverno.ResourceSpec{
|
newGenResource := kyverno.ResourceSpec{
|
||||||
Kind: gen.Kind,
|
Kind: genKind,
|
||||||
Namespace: gen.Namespace,
|
Namespace: genNamespace,
|
||||||
Name: gen.Name,
|
Name: genName,
|
||||||
|
}
|
||||||
|
genData, _, err := unstructured.NestedMap(genUnst.Object, "data")
|
||||||
|
if err != nil {
|
||||||
|
return noGenResource, err
|
||||||
|
}
|
||||||
|
genCopy, _, err := unstructured.NestedMap(genUnst.Object, "clone")
|
||||||
|
if err != nil {
|
||||||
|
return noGenResource, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DATA
|
if genData != nil {
|
||||||
if gen.Data != nil {
|
rdata, mode, err = manageData(genKind, genNamespace, genName, genData, client, resource)
|
||||||
if rdata, err = handleData(rule.Name, gen, client, resource, ctx, state); err != nil {
|
} else {
|
||||||
glog.V(4).Info(err)
|
rdata, mode, err = manageClone(genKind, genNamespace, genName, genCopy, client, resource)
|
||||||
switch e := err.(type) {
|
|
||||||
case *ParseFailed, *NotFound, *ConfigNotFound:
|
|
||||||
// handled errors
|
|
||||||
case *Violation:
|
|
||||||
// create policy violation
|
|
||||||
return noGenResource, e
|
|
||||||
default:
|
|
||||||
// errors that cant be handled
|
|
||||||
return noGenResource, e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rdata == nil {
|
|
||||||
// existing resource contains the configuration
|
|
||||||
return newGenResource, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// CLONE
|
if err != nil {
|
||||||
if gen.Clone != (kyverno.CloneFrom{}) {
|
return noGenResource, err
|
||||||
if rdata, err = handleClone(rule.Name, gen, client, resource, ctx, state); err != nil {
|
}
|
||||||
glog.V(4).Info(err)
|
|
||||||
switch e := err.(type) {
|
if rdata == nil {
|
||||||
case *NotFound:
|
// existing resource contains the configuration
|
||||||
// handled errors
|
return newGenResource, nil
|
||||||
return noGenResource, e
|
|
||||||
default:
|
|
||||||
// errors that cant be handled
|
|
||||||
return noGenResource, e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rdata == nil {
|
|
||||||
// resource already exists
|
|
||||||
return newGenResource, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if processExisting {
|
if processExisting {
|
||||||
// handle existing resources
|
// handle existing resources
|
||||||
|
@ -211,153 +186,141 @@ func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.
|
||||||
// we do not create new resource
|
// we do not create new resource
|
||||||
return noGenResource, err
|
return noGenResource, err
|
||||||
}
|
}
|
||||||
// Create the generate resource
|
|
||||||
|
// build the resource template
|
||||||
newResource := &unstructured.Unstructured{}
|
newResource := &unstructured.Unstructured{}
|
||||||
newResource.SetUnstructuredContent(rdata)
|
newResource.SetUnstructuredContent(rdata)
|
||||||
newResource.SetName(gen.Name)
|
newResource.SetName(genName)
|
||||||
newResource.SetNamespace(gen.Namespace)
|
newResource.SetNamespace(genNamespace)
|
||||||
// Reset resource version
|
|
||||||
newResource.SetResourceVersion("")
|
|
||||||
|
|
||||||
glog.V(4).Infof("creating resource %v", newResource)
|
// manage labels
|
||||||
_, err = client.CreateResource(gen.Kind, gen.Namespace, newResource, false)
|
// - app.kubernetes.io/managed-by: kyverno
|
||||||
if err != nil {
|
// - kyverno.io/generated-by: kind/namespace/name (trigger resource)
|
||||||
glog.Info(err)
|
manageLabels(newResource, resource)
|
||||||
return noGenResource, err
|
|
||||||
|
if mode == Create {
|
||||||
|
// Reset resource version
|
||||||
|
newResource.SetResourceVersion("")
|
||||||
|
// Create the resource
|
||||||
|
glog.V(4).Infof("Creating new resource %s/%s/%s", genKind, genNamespace, genName)
|
||||||
|
_, err = client.CreateResource(genKind, genNamespace, newResource, false)
|
||||||
|
if err != nil {
|
||||||
|
// Failed to create resource
|
||||||
|
return noGenResource, err
|
||||||
|
}
|
||||||
|
glog.V(4).Infof("Created new resource %s/%s/%s", genKind, genNamespace, genName)
|
||||||
|
|
||||||
|
} else if mode == Update {
|
||||||
|
glog.V(4).Infof("Updating existing resource %s/%s/%s", genKind, genNamespace, genName)
|
||||||
|
// Update the resource
|
||||||
|
_, err := client.UpdateResource(genKind, genNamespace, newResource, false)
|
||||||
|
if err != nil {
|
||||||
|
// Failed to update resource
|
||||||
|
return noGenResource, err
|
||||||
|
}
|
||||||
|
glog.V(4).Infof("Updated existing resource %s/%s/%s", genKind, genNamespace, genName)
|
||||||
}
|
}
|
||||||
glog.V(4).Infof("created new resource %s %s %s ", gen.Kind, gen.Namespace, gen.Name)
|
|
||||||
return newGenResource, nil
|
return newGenResource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func variableSubsitutionForAttributes(gen kyverno.Generation, ctx context.EvalInterface) kyverno.Generation {
|
func manageData(kind, namespace, name string, data map[string]interface{}, client *dclient.Client, resource unstructured.Unstructured) (map[string]interface{}, ResourceMode, error) {
|
||||||
// Name
|
// check if resource to be generated exists
|
||||||
name := gen.Name
|
obj, err := client.GetResource(kind, namespace, name)
|
||||||
namespace := gen.Namespace
|
|
||||||
newNameVar := variables.SubstituteVariables(ctx, name)
|
|
||||||
|
|
||||||
if newName, ok := newNameVar.(string); ok {
|
|
||||||
gen.Name = newName
|
|
||||||
}
|
|
||||||
|
|
||||||
newNamespaceVar := variables.SubstituteVariables(ctx, namespace)
|
|
||||||
if newNamespace, ok := newNamespaceVar.(string); ok {
|
|
||||||
gen.Namespace = newNamespace
|
|
||||||
}
|
|
||||||
|
|
||||||
if gen.Clone != (kyverno.CloneFrom{}) {
|
|
||||||
// Clone
|
|
||||||
cloneName := gen.Clone.Name
|
|
||||||
cloneNamespace := gen.Clone.Namespace
|
|
||||||
|
|
||||||
newcloneNameVar := variables.SubstituteVariables(ctx, cloneName)
|
|
||||||
if newcloneName, ok := newcloneNameVar.(string); ok {
|
|
||||||
gen.Clone.Name = newcloneName
|
|
||||||
}
|
|
||||||
newcloneNamespaceVar := variables.SubstituteVariables(ctx, cloneNamespace)
|
|
||||||
if newcloneNamespace, ok := newcloneNamespaceVar.(string); ok {
|
|
||||||
gen.Clone.Namespace = newcloneNamespace
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return gen
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleData(ruleName string, generateRule kyverno.Generation, client *dclient.Client, resource unstructured.Unstructured, ctx context.EvalInterface, state kyverno.GenerateRequestState) (map[string]interface{}, error) {
|
|
||||||
if invalidPaths := variables.ValidateVariables(ctx, generateRule.Data); len(invalidPaths) != 0 {
|
|
||||||
return nil, NewViolation(ruleName, fmt.Errorf("path not present in generate data: %s", invalidPaths))
|
|
||||||
}
|
|
||||||
|
|
||||||
//work on copy
|
|
||||||
copyDataTemp := reflect.Indirect(reflect.ValueOf(generateRule.Data))
|
|
||||||
copyData := copyDataTemp.Interface()
|
|
||||||
newData := variables.SubstituteVariables(ctx, copyData)
|
|
||||||
|
|
||||||
// check if resource exists
|
|
||||||
obj, err := client.GetResource(generateRule.Kind, generateRule.Namespace, generateRule.Name)
|
|
||||||
glog.V(4).Info(err)
|
|
||||||
if apierrors.IsNotFound(err) {
|
if apierrors.IsNotFound(err) {
|
||||||
glog.V(4).Info(string(state))
|
glog.V(4).Infof("Resource %s/%s/%s does not exists, will try to create", kind, namespace, name)
|
||||||
// Resource does not exist
|
return data, Create, nil
|
||||||
if state == "" {
|
|
||||||
// Processing the request first time
|
|
||||||
rdata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&newData)
|
|
||||||
glog.V(4).Info(err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, NewParseFailed(newData, err)
|
|
||||||
}
|
|
||||||
return rdata, nil
|
|
||||||
}
|
|
||||||
glog.V(4).Info("Creating violation")
|
|
||||||
// State : Failed,Completed
|
|
||||||
// request has been processed before, so dont create the resource
|
|
||||||
// report Violation to notify the error
|
|
||||||
return nil, NewViolation(ruleName, NewNotFound(generateRule.Kind, generateRule.Namespace, generateRule.Name))
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//something wrong while fetching resource
|
//something wrong while fetching resource
|
||||||
return nil, err
|
// client-errors
|
||||||
|
return nil, Skip, err
|
||||||
}
|
}
|
||||||
// Resource exists; verfiy the content of the resource
|
// Resource exists; verfiy the content of the resource
|
||||||
ok, err := checkResource(ctx, newData, obj)
|
err = checkResource(data, obj)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
//something wrong with configuration
|
// Existing resource does contain the mentioned configuration in spec, skip processing the resource as it is already in expected state
|
||||||
glog.V(4).Info(err)
|
return nil, Skip, nil
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
if !ok {
|
|
||||||
return nil, NewConfigNotFound(newData, generateRule.Kind, generateRule.Namespace, generateRule.Name)
|
glog.V(4).Infof("Resource %s/%s/%s exists but missing required configuration, will try to update", kind, namespace, name)
|
||||||
}
|
return data, Update, nil
|
||||||
// Existing resource does contain the required
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleClone(ruleName string, generateRule kyverno.Generation, client *dclient.Client, resource unstructured.Unstructured, ctx context.EvalInterface, state kyverno.GenerateRequestState) (map[string]interface{}, error) {
|
func manageClone(kind, namespace, name string, clone map[string]interface{}, client *dclient.Client, resource unstructured.Unstructured) (map[string]interface{}, ResourceMode, error) {
|
||||||
if invalidPaths := variables.ValidateVariables(ctx, generateRule.Clone); len(invalidPaths) != 0 {
|
// check if resource to be generated exists
|
||||||
return nil, NewViolation(ruleName, fmt.Errorf("path not present in generate clone: %s", invalidPaths))
|
_, err := client.GetResource(kind, namespace, name)
|
||||||
}
|
|
||||||
|
|
||||||
// check if resource exists
|
|
||||||
_, err := client.GetResource(generateRule.Kind, generateRule.Namespace, generateRule.Name)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// resource exists
|
// resource does exists, not need to process further as it is already in expected state
|
||||||
return nil, nil
|
return nil, Skip, nil
|
||||||
}
|
}
|
||||||
|
//TODO: check this
|
||||||
if !apierrors.IsNotFound(err) {
|
if !apierrors.IsNotFound(err) {
|
||||||
//something wrong while fetching resource
|
//something wrong while fetching resource
|
||||||
return nil, err
|
return nil, Skip, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// get reference clone resource
|
newRNs, _, err := unstructured.NestedString(clone, "namespace")
|
||||||
obj, err := client.GetResource(generateRule.Kind, generateRule.Clone.Namespace, generateRule.Clone.Name)
|
if err != nil {
|
||||||
if apierrors.IsNotFound(err) {
|
return nil, Skip, err
|
||||||
return nil, NewNotFound(generateRule.Kind, generateRule.Clone.Namespace, generateRule.Clone.Name)
|
}
|
||||||
}
|
newRName, _, err := unstructured.NestedString(clone, "name")
|
||||||
|
if err != nil {
|
||||||
|
return nil, Skip, err
|
||||||
|
}
|
||||||
|
// Short-circuit if the resource to be generated and the clone is the same
|
||||||
|
if newRNs == namespace && newRName == name {
|
||||||
|
// attempting to clone it self, this will fail -> short-ciruit it
|
||||||
|
return nil, Skip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(4).Infof("check if resource %s/%s/%s exists", kind, newRNs, newRName)
|
||||||
|
// check if the resource as reference in clone exists?
|
||||||
|
obj, err := client.GetResource(kind, newRNs, newRName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, Skip, fmt.Errorf("reference clone resource %s/%s/%s not found. %v", kind, newRNs, newRName, err)
|
||||||
|
}
|
||||||
|
// create the resource based on the reference clone
|
||||||
|
return obj.UnstructuredContent(), Create, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceMode defines the mode for generated resource
|
||||||
|
type ResourceMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
//Skip : failed to process rule, will not update the resource
|
||||||
|
Skip ResourceMode = "SKIP"
|
||||||
|
//Create : create a new resource
|
||||||
|
Create = "CREATE"
|
||||||
|
//Update : update/overwrite the new resource
|
||||||
|
Update = "UPDATE"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkResource(newResourceSpec interface{}, resource *unstructured.Unstructured) error {
|
||||||
|
// check if the resource spec if a subset of the resource
|
||||||
|
if path, err := validate.ValidateResourceWithPattern(resource.Object, newResourceSpec); err != nil {
|
||||||
|
glog.V(4).Infof("Failed to match the resource at path %s: err %v", path, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUnstrRule(rule *kyverno.Generation) (*unstructured.Unstructured, error) {
|
||||||
|
ruleData, err := json.Marshal(rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//something wrong while fetching resource
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return obj.UnstructuredContent(), nil
|
return ConvertToUnstructured(ruleData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkResource(ctx context.EvalInterface, newResourceSpec interface{}, resource *unstructured.Unstructured) (bool, error) {
|
//ConvertToUnstructured converts the resource to unstructured format
|
||||||
// check if the resource spec if a subset of the resource
|
func ConvertToUnstructured(data []byte) (*unstructured.Unstructured, error) {
|
||||||
path, err := validate.ValidateResourceWithPattern(ctx, resource.Object, newResourceSpec)
|
resource := &unstructured.Unstructured{}
|
||||||
if !reflect.DeepEqual(err, validate.ValidationError{}) {
|
err := resource.UnmarshalJSON(data)
|
||||||
glog.V(4).Infof("config not a subset of resource. failed at path %s: %v", path, err)
|
if err != nil {
|
||||||
return false, errors.New(err.ErrorMsg)
|
return nil, err
|
||||||
}
|
}
|
||||||
return true, nil
|
return resource, nil
|
||||||
}
|
|
||||||
|
|
||||||
func generatePV(gr kyverno.GenerateRequest, resource unstructured.Unstructured, err *Violation) policyviolation.Info {
|
|
||||||
|
|
||||||
info := policyviolation.Info{
|
|
||||||
PolicyName: gr.Spec.Policy,
|
|
||||||
Resource: resource,
|
|
||||||
Rules: []kyverno.ViolatedRule{{
|
|
||||||
Name: err.rule,
|
|
||||||
Type: "Generation",
|
|
||||||
Message: err.Error(),
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
return info
|
|
||||||
}
|
}
|
||||||
|
|
57
pkg/generate/labels.go
Normal file
57
pkg/generate/labels.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
)
|
||||||
|
|
||||||
|
func manageLabels(unstr *unstructured.Unstructured, triggerResource unstructured.Unstructured) {
|
||||||
|
// add managedBY label if not defined
|
||||||
|
labels := unstr.GetLabels()
|
||||||
|
if labels == nil {
|
||||||
|
labels = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle managedBy label
|
||||||
|
managedBy(labels)
|
||||||
|
// handle generatedBy label
|
||||||
|
generatedBy(labels, triggerResource)
|
||||||
|
|
||||||
|
// update the labels
|
||||||
|
unstr.SetLabels(labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func managedBy(labels map[string]string) {
|
||||||
|
// ManagedBy label
|
||||||
|
key := "app.kubernetes.io/managed-by"
|
||||||
|
value := "kyverno"
|
||||||
|
val, ok := labels[key]
|
||||||
|
if ok {
|
||||||
|
if val != value {
|
||||||
|
glog.Infof("resource managed by %s, kyverno wont over-ride the label", val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
// add label
|
||||||
|
labels[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generatedBy(labels map[string]string, triggerResource unstructured.Unstructured) {
|
||||||
|
key := "kyverno.io/generated-by"
|
||||||
|
value := fmt.Sprintf("%s-%s-%s", triggerResource.GetKind(), triggerResource.GetNamespace(), triggerResource.GetName())
|
||||||
|
val, ok := labels[key]
|
||||||
|
if ok {
|
||||||
|
if val != value {
|
||||||
|
glog.Infof("resource generated by %s, kyverno wont over-ride the label", val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
// add label
|
||||||
|
labels[key] = value
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,9 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/response"
|
|
||||||
"github.com/nirmata/kyverno/pkg/event"
|
"github.com/nirmata/kyverno/pkg/event"
|
||||||
"github.com/nirmata/kyverno/pkg/policyviolation"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,45 +18,9 @@ func reportEvents(err error, eventGen event.Interface, gr kyverno.GenerateReques
|
||||||
eventGen.Add(events...)
|
eventGen.Add(events...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch e := err.(type) {
|
glog.V(4).Infof("reporing events for %v", err)
|
||||||
case *Violation:
|
events := failedEvents(err, gr, resource)
|
||||||
// - resource -> rule failed and created PV
|
eventGen.Add(events...)
|
||||||
// - policy -> failed to apply of resource and created PV
|
|
||||||
glog.V(4).Infof("reporing events for %v", e)
|
|
||||||
events := failedEventsPV(err, gr, resource)
|
|
||||||
eventGen.Add(events...)
|
|
||||||
default:
|
|
||||||
// - resource -> rule failed
|
|
||||||
// - policy -> failed tp apply on resource
|
|
||||||
glog.V(4).Infof("reporing events for %v", e)
|
|
||||||
events := failedEvents(err, gr, resource)
|
|
||||||
eventGen.Add(events...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func failedEventsPV(err error, gr kyverno.GenerateRequest, resource unstructured.Unstructured) []event.Info {
|
|
||||||
var events []event.Info
|
|
||||||
// Cluster Policy
|
|
||||||
pe := event.Info{}
|
|
||||||
pe.Kind = "ClusterPolicy"
|
|
||||||
// cluserwide-resource
|
|
||||||
pe.Name = gr.Spec.Policy
|
|
||||||
pe.Reason = event.PolicyViolation.String()
|
|
||||||
pe.Source = event.GeneratePolicyController
|
|
||||||
pe.Message = fmt.Sprintf("policy failed to apply on resource %s/%s/%s creating violation: %v", resource.GetKind(), resource.GetNamespace(), resource.GetName(), err)
|
|
||||||
events = append(events, pe)
|
|
||||||
|
|
||||||
// Resource
|
|
||||||
re := event.Info{}
|
|
||||||
re.Kind = resource.GetKind()
|
|
||||||
re.Namespace = resource.GetNamespace()
|
|
||||||
re.Name = resource.GetName()
|
|
||||||
re.Reason = event.PolicyViolation.String()
|
|
||||||
re.Source = event.GeneratePolicyController
|
|
||||||
re.Message = fmt.Sprintf("policy %s failed to apply created violation: %v", gr.Spec.Policy, err)
|
|
||||||
events = append(events, re)
|
|
||||||
|
|
||||||
return events
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func failedEvents(err error, gr kyverno.GenerateRequest, resource unstructured.Unstructured) []event.Info {
|
func failedEvents(err error, gr kyverno.GenerateRequest, resource unstructured.Unstructured) []event.Info {
|
||||||
|
@ -110,13 +72,3 @@ func successEvents(gr kyverno.GenerateRequest, resource unstructured.Unstructure
|
||||||
|
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildPathNotPresentPV build violation info when referenced path not found
|
|
||||||
func buildPathNotPresentPV(er response.EngineResponse) []policyviolation.Info {
|
|
||||||
for _, rr := range er.PolicyResponse.Rules {
|
|
||||||
if rr.PathNotPresent {
|
|
||||||
return policyviolation.GeneratePVsFromEngineResponse([]response.EngineResponse{er})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,251 +0,0 @@
|
||||||
package apply
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
yaml "k8s.io/apimachinery/pkg/util/yaml"
|
|
||||||
memory "k8s.io/client-go/discovery/cached/memory"
|
|
||||||
dynamic "k8s.io/client-go/dynamic"
|
|
||||||
kubernetes "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/client-go/kubernetes/scheme"
|
|
||||||
"k8s.io/client-go/restmapper"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
applyExample = ` # Apply a policy to the resource.
|
|
||||||
kyverno apply @policy.yaml @resource.yaml
|
|
||||||
kyverno apply @policy.yaml @resourceDir/
|
|
||||||
kyverno apply @policy.yaml @resource.yaml --kubeconfig=$PATH_TO_KUBECONFIG_FILE`
|
|
||||||
|
|
||||||
defaultYamlSeparator = "---"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewCmdApply returns the apply command for kyverno
|
|
||||||
func NewCmdApply(in io.Reader, out, errout io.Writer) *cobra.Command {
|
|
||||||
var kubeconfig string
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "apply",
|
|
||||||
Short: "Apply policy on the resource(s)",
|
|
||||||
Example: applyExample,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
policy, resources := complete(kubeconfig, args)
|
|
||||||
output := applyPolicy(policy, resources)
|
|
||||||
fmt.Printf("%v\n", output)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "path to kubeconfig file")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func complete(kubeconfig string, args []string) (*kyverno.ClusterPolicy, []*resourceInfo) {
|
|
||||||
policyDir, resourceDir, err := validateDir(args)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to parse file path, err: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract policy
|
|
||||||
policy, err := extractPolicy(policyDir)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to extract policy: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract rawResource
|
|
||||||
resources, err := extractResource(resourceDir, kubeconfig)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to parse resource: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return policy, resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyPolicy(policy *kyverno.ClusterPolicy, resources []*resourceInfo) (output string) {
|
|
||||||
for _, resource := range resources {
|
|
||||||
patchedDocument, err := applyPolicyOnRaw(policy, resource.rawResource, resource.gvk)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Error applying policy on resource %s, err: %v\n", resource.gvk.Kind, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := prettyPrint(patchedDocument)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("JSON parse error: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
output = output + fmt.Sprintf("---\n%s", string(out))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyPolicyOnRaw(policy *kyverno.ClusterPolicy, rawResource []byte, gvk *metav1.GroupVersionKind) ([]byte, error) {
|
|
||||||
patchedResource := rawResource
|
|
||||||
var err error
|
|
||||||
|
|
||||||
rname := engine.ParseNameFromObject(rawResource)
|
|
||||||
rns := engine.ParseNamespaceFromObject(rawResource)
|
|
||||||
resource, err := ConvertToUnstructured(rawResource)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//TODO check if the kind information is present resource
|
|
||||||
// Process Mutation
|
|
||||||
engineResponse := engine.Mutate(engine.PolicyContext{Policy: *policy, NewResource: *resource})
|
|
||||||
if !engineResponse.IsSuccesful() {
|
|
||||||
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns)
|
|
||||||
for _, r := range engineResponse.PolicyResponse.Rules {
|
|
||||||
glog.Warning(r.Message)
|
|
||||||
}
|
|
||||||
} else if len(engineResponse.PolicyResponse.Rules) > 0 {
|
|
||||||
glog.Infof("Mutation from policy %s has applied successfully to %s %s/%s", policy.Name, gvk.Kind, rname, rns)
|
|
||||||
|
|
||||||
// Process Validation
|
|
||||||
engineResponse := engine.Validate(engine.PolicyContext{Policy: *policy, NewResource: *resource})
|
|
||||||
|
|
||||||
if !engineResponse.IsSuccesful() {
|
|
||||||
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns)
|
|
||||||
for _, r := range engineResponse.PolicyResponse.Rules {
|
|
||||||
glog.Warning(r.Message)
|
|
||||||
}
|
|
||||||
return patchedResource, fmt.Errorf("policy %s on resource %s/%s not satisfied", policy.Name, rname, rns)
|
|
||||||
} else if len(engineResponse.PolicyResponse.Rules) > 0 {
|
|
||||||
glog.Infof("Validation from policy %s has applied successfully to %s %s/%s", policy.Name, gvk.Kind, rname, rns)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return patchedResource, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractPolicy(fileDir string) (*kyverno.ClusterPolicy, error) {
|
|
||||||
policy := &kyverno.ClusterPolicy{}
|
|
||||||
|
|
||||||
file, err := loadFile(fileDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
policyBytes, err := yaml.ToJSON(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(policyBytes, policy); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode policy %s, err: %v", policy.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if policy.TypeMeta.Kind != "ClusterPolicy" {
|
|
||||||
return nil, fmt.Errorf("failed to parse policy")
|
|
||||||
}
|
|
||||||
|
|
||||||
return policy, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type resourceInfo struct {
|
|
||||||
rawResource []byte
|
|
||||||
gvk *metav1.GroupVersionKind
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractResource(fileDir, kubeconfig string) ([]*resourceInfo, error) {
|
|
||||||
var files []string
|
|
||||||
var resources []*resourceInfo
|
|
||||||
|
|
||||||
// check if applied on multiple resources
|
|
||||||
isDir, err := isDir(fileDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if isDir {
|
|
||||||
files, err = scanDir(fileDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
files = []string{fileDir}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dir := range files {
|
|
||||||
data, err := loadFile(dir)
|
|
||||||
if err != nil {
|
|
||||||
glog.Warningf("Error while loading file: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
dd := bytes.Split(data, []byte(defaultYamlSeparator))
|
|
||||||
|
|
||||||
for _, d := range dd {
|
|
||||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
|
||||||
obj, gvk, err := decode([]byte(d), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
glog.Warningf("Error while decoding YAML object, err: %s\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
actualObj, err := convertToActualObject(kubeconfig, gvk, obj)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(3).Infof("Failed to convert resource %s to actual k8s object: %v\n", gvk.Kind, err)
|
|
||||||
glog.V(3).Infof("Apply policy on raw resource.\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
raw, err := json.Marshal(actualObj)
|
|
||||||
if err != nil {
|
|
||||||
glog.Warningf("Error while marshalling manifest, err: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
gvkInfo := &metav1.GroupVersionKind{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind}
|
|
||||||
resources = append(resources, &resourceInfo{rawResource: raw, gvk: gvkInfo})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resources, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertToActualObject(kubeconfig string, gvk *schema.GroupVersionKind, obj runtime.Object) (interface{}, error) {
|
|
||||||
clientConfig, err := createClientConfig(kubeconfig)
|
|
||||||
if err != nil {
|
|
||||||
return obj, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamicClient, err := dynamic.NewForConfig(clientConfig)
|
|
||||||
if err != nil {
|
|
||||||
return obj, err
|
|
||||||
}
|
|
||||||
|
|
||||||
kclient, err := kubernetes.NewForConfig(clientConfig)
|
|
||||||
if err != nil {
|
|
||||||
return obj, err
|
|
||||||
}
|
|
||||||
|
|
||||||
asUnstructured := &unstructured.Unstructured{}
|
|
||||||
if err := scheme.Scheme.Convert(obj, asUnstructured, nil); err != nil {
|
|
||||||
return obj, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(kclient.Discovery()))
|
|
||||||
mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
|
|
||||||
if err != nil {
|
|
||||||
return obj, err
|
|
||||||
}
|
|
||||||
|
|
||||||
actualObj, err := dynamicClient.Resource(mapping.Resource).Namespace("default").Create(asUnstructured, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}})
|
|
||||||
if err != nil {
|
|
||||||
return obj, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return actualObj, nil
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
package apply
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
yamlv2 "gopkg.in/yaml.v2"
|
|
||||||
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
rest "k8s.io/client-go/rest"
|
|
||||||
clientcmd "k8s.io/client-go/tools/clientcmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createClientConfig(kubeconfig string) (*rest.Config, error) {
|
|
||||||
if kubeconfig == "" {
|
|
||||||
defaultKC := defaultKubeconfigPath()
|
|
||||||
if _, err := os.Stat(defaultKC); err == nil {
|
|
||||||
kubeconfig = defaultKC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientcmd.BuildConfigFromFlags("", kubeconfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultKubeconfigPath() string {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
glog.Warningf("Warning: failed to get home dir: %v\n", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Join(home, ".kube", "config")
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadFile(fileDir string) ([]byte, error) {
|
|
||||||
if _, err := os.Stat(fileDir); os.IsNotExist(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ioutil.ReadFile(fileDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateDir(args []string) (policyDir, resourceDir string, err error) {
|
|
||||||
if len(args) != 2 {
|
|
||||||
return "", "", fmt.Errorf("missing policy and/or resource manifest")
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(args[0], "@") {
|
|
||||||
policyDir = args[0][1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(args[1], "@") {
|
|
||||||
resourceDir = args[1][1:]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func prettyPrint(data []byte) ([]byte, error) {
|
|
||||||
out := make(map[interface{}]interface{})
|
|
||||||
if err := yamlv2.Unmarshal(data, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return yamlv2.Marshal(&out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDir(dir string) (bool, error) {
|
|
||||||
fi, err := os.Stat(dir)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fi.IsDir(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func scanDir(dir string) ([]string, error) {
|
|
||||||
var res []string
|
|
||||||
|
|
||||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("prevent panic by handling failure accessing a path %q: %v", dir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res = append(res, path)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error walking the path %q: %v", dir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res[1:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//ConvertToUnstructured converts the resource to unstructured format
|
|
||||||
func ConvertToUnstructured(data []byte) (*unstructured.Unstructured, error) {
|
|
||||||
resource := &unstructured.Unstructured{}
|
|
||||||
err := resource.UnmarshalJSON(data)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(4).Infof("failed to unmarshall resource: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resource, nil
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/nirmata/kyverno/pkg/kyverno/apply"
|
|
||||||
"github.com/nirmata/kyverno/pkg/kyverno/version"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewDefaultKyvernoCommand ...
|
|
||||||
func NewDefaultKyvernoCommand() *cobra.Command {
|
|
||||||
return NewKyvernoCommand(os.Stdin, os.Stdout, os.Stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewKyvernoCommand returns the new kynerno command
|
|
||||||
func NewKyvernoCommand(in io.Reader, out, errout io.Writer) *cobra.Command {
|
|
||||||
cmds := &cobra.Command{
|
|
||||||
Use: "kyverno",
|
|
||||||
Short: "kyverno manages native policies of Kubernetes",
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds.AddCommand(apply.NewCmdApply(in, out, errout))
|
|
||||||
cmds.AddCommand(version.NewCmdVersion(out))
|
|
||||||
return cmds
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/nirmata/kyverno/pkg/version"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewCmdVersion is a command to display the build version
|
|
||||||
func NewCmdVersion(cmdOut io.Writer) *cobra.Command {
|
|
||||||
|
|
||||||
versionCmd := &cobra.Command{
|
|
||||||
Use: "version",
|
|
||||||
Short: "",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
showVersion()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return versionCmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func showVersion() {
|
|
||||||
fmt.Printf("Version: %s\n", version.BuildVersion)
|
|
||||||
fmt.Printf("Time: %s\n", version.BuildTime)
|
|
||||||
fmt.Printf("Git commit ID: %s\n", version.BuildHash)
|
|
||||||
}
|
|
|
@ -1,250 +0,0 @@
|
||||||
package namespace
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
|
||||||
"github.com/nirmata/kyverno/pkg/config"
|
|
||||||
client "github.com/nirmata/kyverno/pkg/dclient"
|
|
||||||
"github.com/nirmata/kyverno/pkg/event"
|
|
||||||
"github.com/nirmata/kyverno/pkg/policy"
|
|
||||||
"github.com/nirmata/kyverno/pkg/policystore"
|
|
||||||
"github.com/nirmata/kyverno/pkg/policyviolation"
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
|
|
||||||
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
|
|
||||||
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1"
|
|
||||||
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
||||||
v1Informer "k8s.io/client-go/informers/core/v1"
|
|
||||||
"k8s.io/client-go/tools/cache"
|
|
||||||
"k8s.io/client-go/util/workqueue"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// maxRetries is the number of times a Namespace will be processed for a policy before its dropped from the queue
|
|
||||||
maxRetries = 15
|
|
||||||
)
|
|
||||||
|
|
||||||
//NamespaceController watches the 'Namespace' resource creation/update and applied the generation rules on them
|
|
||||||
type NamespaceController struct {
|
|
||||||
client *client.Client
|
|
||||||
kyvernoClient *kyvernoclient.Clientset
|
|
||||||
syncHandler func(nsKey string) error
|
|
||||||
enqueueNs func(ns *v1.Namespace)
|
|
||||||
|
|
||||||
//nsLister provides expansion to the namespace lister to inject GVK for the resource
|
|
||||||
nsLister NamespaceListerExpansion
|
|
||||||
// nsSynced returns true if the Namespace store has been synced at least once
|
|
||||||
nsSynced cache.InformerSynced
|
|
||||||
// pvLister can list/get policy violation from the shared informer's store
|
|
||||||
pLister kyvernolister.ClusterPolicyLister
|
|
||||||
// pSynced retrns true if the Policy store has been synced at least once
|
|
||||||
pSynced cache.InformerSynced
|
|
||||||
// API to send policy stats for aggregation
|
|
||||||
policyStatus policy.PolicyStatusInterface
|
|
||||||
// eventGen provides interface to generate evenets
|
|
||||||
eventGen event.Interface
|
|
||||||
// Namespaces that need to be synced
|
|
||||||
queue workqueue.RateLimitingInterface
|
|
||||||
// Resource manager, manages the mapping for already processed resource
|
|
||||||
rm resourceManager
|
|
||||||
// helpers to validate against current loaded configuration
|
|
||||||
configHandler config.Interface
|
|
||||||
// store to hold policy meta data for faster lookup
|
|
||||||
pMetaStore policystore.LookupInterface
|
|
||||||
// policy violation generator
|
|
||||||
pvGenerator policyviolation.GeneratorInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
//NewNamespaceController returns a new Controller to manage generation rules
|
|
||||||
func NewNamespaceController(kyvernoClient *kyvernoclient.Clientset,
|
|
||||||
client *client.Client,
|
|
||||||
nsInformer v1Informer.NamespaceInformer,
|
|
||||||
pInformer kyvernoinformer.ClusterPolicyInformer,
|
|
||||||
policyStatus policy.PolicyStatusInterface,
|
|
||||||
eventGen event.Interface,
|
|
||||||
configHandler config.Interface,
|
|
||||||
pvGenerator policyviolation.GeneratorInterface,
|
|
||||||
pMetaStore policystore.LookupInterface) *NamespaceController {
|
|
||||||
//TODO: do we need to event recorder for this controller?
|
|
||||||
// create the controller
|
|
||||||
nsc := &NamespaceController{
|
|
||||||
client: client,
|
|
||||||
kyvernoClient: kyvernoClient,
|
|
||||||
eventGen: eventGen,
|
|
||||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "namespace"),
|
|
||||||
configHandler: configHandler,
|
|
||||||
pMetaStore: pMetaStore,
|
|
||||||
pvGenerator: pvGenerator,
|
|
||||||
}
|
|
||||||
|
|
||||||
nsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
||||||
AddFunc: nsc.addNamespace,
|
|
||||||
UpdateFunc: nsc.updateNamespace,
|
|
||||||
DeleteFunc: nsc.deleteNamespace,
|
|
||||||
})
|
|
||||||
|
|
||||||
pInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
||||||
AddFunc: nsc.addPolicy,
|
|
||||||
UpdateFunc: nsc.updatePolicy,
|
|
||||||
})
|
|
||||||
|
|
||||||
nsc.enqueueNs = nsc.enqueue
|
|
||||||
nsc.syncHandler = nsc.syncNamespace
|
|
||||||
|
|
||||||
nsc.nsLister = NewNamespaceLister(nsInformer.Lister())
|
|
||||||
nsc.nsSynced = nsInformer.Informer().HasSynced
|
|
||||||
nsc.pLister = pInformer.Lister()
|
|
||||||
nsc.pSynced = pInformer.Informer().HasSynced
|
|
||||||
nsc.policyStatus = policyStatus
|
|
||||||
|
|
||||||
// resource manager
|
|
||||||
// rebuild after 300 seconds/ 5 mins
|
|
||||||
nsc.rm = NewResourceManager(300)
|
|
||||||
|
|
||||||
return nsc
|
|
||||||
}
|
|
||||||
func (nsc *NamespaceController) addPolicy(obj interface{}) {
|
|
||||||
p := obj.(*kyverno.ClusterPolicy)
|
|
||||||
// check if the policy has generate rule
|
|
||||||
if generateRuleExists(p) {
|
|
||||||
// process policy
|
|
||||||
nsc.processPolicy(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nsc *NamespaceController) updatePolicy(old, cur interface{}) {
|
|
||||||
curP := cur.(*kyverno.ClusterPolicy)
|
|
||||||
// check if the policy has generate rule
|
|
||||||
if generateRuleExists(curP) {
|
|
||||||
// process policy
|
|
||||||
nsc.processPolicy(curP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nsc *NamespaceController) addNamespace(obj interface{}) {
|
|
||||||
ns := obj.(*v1.Namespace)
|
|
||||||
glog.V(4).Infof("Adding Namespace %s", ns.Name)
|
|
||||||
nsc.enqueueNs(ns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nsc *NamespaceController) updateNamespace(old, cur interface{}) {
|
|
||||||
oldNs := old.(*v1.Namespace)
|
|
||||||
curNs := cur.(*v1.Namespace)
|
|
||||||
if curNs.ResourceVersion == oldNs.ResourceVersion {
|
|
||||||
// Periodic resync will send update events for all known Namespace.
|
|
||||||
// Two different versions of the same replica set will always have different RVs.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
glog.V(4).Infof("Updating Namesapce %s", curNs.Name)
|
|
||||||
//TODO: anything to be done here?
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nsc *NamespaceController) deleteNamespace(obj interface{}) {
|
|
||||||
ns, _ := obj.(*v1.Namespace)
|
|
||||||
glog.V(4).Infof("Deleting Namespace %s", ns.Name)
|
|
||||||
//TODO: anything to be done here?
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nsc *NamespaceController) enqueue(ns *v1.Namespace) {
|
|
||||||
key, err := cache.MetaNamespaceKeyFunc(ns)
|
|
||||||
if err != nil {
|
|
||||||
glog.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nsc.queue.Add(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Run to run the controller
|
|
||||||
func (nsc *NamespaceController) Run(workers int, stopCh <-chan struct{}) {
|
|
||||||
defer utilruntime.HandleCrash()
|
|
||||||
defer nsc.queue.ShutDown()
|
|
||||||
|
|
||||||
glog.Info("Starting namespace controller")
|
|
||||||
defer glog.Info("Shutting down namespace controller")
|
|
||||||
|
|
||||||
if ok := cache.WaitForCacheSync(stopCh, nsc.nsSynced, nsc.pSynced); !ok {
|
|
||||||
glog.Error("namespace generator: failed to sync cache")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < workers; i++ {
|
|
||||||
go wait.Until(nsc.worker, time.Second, stopCh)
|
|
||||||
}
|
|
||||||
<-stopCh
|
|
||||||
}
|
|
||||||
|
|
||||||
// worker runs a worker thread that just dequeues items, processes them, and marks them done.
|
|
||||||
// It enforces that the syncHandler is never invoked concurrently with the same key.
|
|
||||||
func (nsc *NamespaceController) worker() {
|
|
||||||
for nsc.processNextWorkItem() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nsc *NamespaceController) processNextWorkItem() bool {
|
|
||||||
key, quit := nsc.queue.Get()
|
|
||||||
if quit {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer nsc.queue.Done(key)
|
|
||||||
|
|
||||||
err := nsc.syncHandler(key.(string))
|
|
||||||
nsc.handleErr(err, key)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nsc *NamespaceController) handleErr(err error, key interface{}) {
|
|
||||||
if err == nil {
|
|
||||||
nsc.queue.Forget(key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if nsc.queue.NumRequeues(key) < maxRetries {
|
|
||||||
glog.V(2).Infof("Error syncing namespace %v: %v", key, err)
|
|
||||||
nsc.queue.AddRateLimited(key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
utilruntime.HandleError(err)
|
|
||||||
glog.V(2).Infof("Dropping namespace %q out of the queue: %v", key, err)
|
|
||||||
nsc.queue.Forget(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nsc *NamespaceController) syncNamespace(key string) error {
|
|
||||||
startTime := time.Now()
|
|
||||||
glog.V(4).Infof("Started syncing namespace %q (%v)", key, startTime)
|
|
||||||
defer func() {
|
|
||||||
glog.V(4).Infof("Finished syncing namespace %q (%v)", key, time.Since(startTime))
|
|
||||||
}()
|
|
||||||
namespace, err := nsc.nsLister.GetResource(key)
|
|
||||||
if errors.IsNotFound(err) {
|
|
||||||
glog.V(2).Infof("namespace %v has been deleted", key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Deep-copy otherwise we are mutating our cache.
|
|
||||||
// TODO: Deep-copy only when needed.
|
|
||||||
n := namespace.DeepCopy()
|
|
||||||
|
|
||||||
// skip processing namespace if its been filtered
|
|
||||||
// exclude the filtered resources
|
|
||||||
if nsc.configHandler.ToFilter("", namespace.Name, "") {
|
|
||||||
//TODO: improve the text
|
|
||||||
glog.V(4).Infof("excluding namespace %s as its a filtered resource", namespace.Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// process generate rules
|
|
||||||
engineResponses := nsc.processNamespace(*n)
|
|
||||||
// report errors
|
|
||||||
nsc.report(engineResponses)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
package namespace
|
|
||||||
|
|
||||||
import (
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
v1CoreLister "k8s.io/client-go/listers/core/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
//NamespaceListerExpansion ...
|
|
||||||
type NamespaceListerExpansion interface {
|
|
||||||
v1CoreLister.NamespaceLister
|
|
||||||
// List lists all Namespaces in the indexer.
|
|
||||||
ListResources(selector labels.Selector) (ret []*v1.Namespace, err error)
|
|
||||||
// GetsResource and injects gvk
|
|
||||||
GetResource(name string) (*v1.Namespace, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
//NamespaceLister ...
|
|
||||||
type NamespaceLister struct {
|
|
||||||
v1CoreLister.NamespaceLister
|
|
||||||
}
|
|
||||||
|
|
||||||
//NewNamespaceLister returns a new NamespaceLister
|
|
||||||
func NewNamespaceLister(nsLister v1CoreLister.NamespaceLister) NamespaceListerExpansion {
|
|
||||||
nsl := NamespaceLister{
|
|
||||||
nsLister,
|
|
||||||
}
|
|
||||||
return &nsl
|
|
||||||
}
|
|
||||||
|
|
||||||
//ListResources is a wrapper to List and adds the resource kind information
|
|
||||||
// as the lister is specific to a gvk we can harcode the values here
|
|
||||||
func (nsl *NamespaceLister) ListResources(selector labels.Selector) (ret []*v1.Namespace, err error) {
|
|
||||||
namespaces, err := nsl.List(selector)
|
|
||||||
for index := range namespaces {
|
|
||||||
namespaces[index].SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("Namespace"))
|
|
||||||
}
|
|
||||||
return namespaces, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//GetResource is a wrapper to get the resource and inject the GVK
|
|
||||||
func (nsl *NamespaceLister) GetResource(name string) (*v1.Namespace, error) {
|
|
||||||
namespace, err := nsl.Get(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("Namespace"))
|
|
||||||
return namespace, err
|
|
||||||
}
|
|
|
@ -1,216 +0,0 @@
|
||||||
package namespace
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
|
||||||
client "github.com/nirmata/kyverno/pkg/dclient"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/response"
|
|
||||||
policyctr "github.com/nirmata/kyverno/pkg/policy"
|
|
||||||
"github.com/nirmata/kyverno/pkg/policystore"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
type resourceManager interface {
|
|
||||||
ProcessResource(policy, pv, kind, ns, name, rv string) bool
|
|
||||||
//TODO removeResource(kind, ns, name string) error
|
|
||||||
RegisterResource(policy, pv, kind, ns, name, rv string)
|
|
||||||
// reload
|
|
||||||
Drop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceManager stores the details on already processed resources for caching
|
|
||||||
type ResourceManager struct {
|
|
||||||
// we drop and re-build the cache
|
|
||||||
// based on the memory consumer of by the map
|
|
||||||
data map[string]interface{}
|
|
||||||
mux sync.RWMutex
|
|
||||||
time time.Time
|
|
||||||
rebuildTime int64 // after how many seconds should we rebuild the cache
|
|
||||||
}
|
|
||||||
|
|
||||||
//NewResourceManager returns a new ResourceManager
|
|
||||||
func NewResourceManager(rebuildTime int64) *ResourceManager {
|
|
||||||
rm := ResourceManager{
|
|
||||||
data: make(map[string]interface{}),
|
|
||||||
time: time.Now(),
|
|
||||||
rebuildTime: rebuildTime,
|
|
||||||
}
|
|
||||||
// set time it was built
|
|
||||||
return &rm
|
|
||||||
}
|
|
||||||
|
|
||||||
var empty struct{}
|
|
||||||
|
|
||||||
//RegisterResource stores if the policy is processed on this resource version
|
|
||||||
func (rm *ResourceManager) RegisterResource(policy, pv, kind, ns, name, rv string) {
|
|
||||||
rm.mux.Lock()
|
|
||||||
defer rm.mux.Unlock()
|
|
||||||
// add the resource
|
|
||||||
key := buildKey(policy, pv, kind, ns, name, rv)
|
|
||||||
rm.data[key] = empty
|
|
||||||
}
|
|
||||||
|
|
||||||
//ProcessResource returns true if the policy was not applied on the resource
|
|
||||||
func (rm *ResourceManager) ProcessResource(policy, pv, kind, ns, name, rv string) bool {
|
|
||||||
rm.mux.RLock()
|
|
||||||
defer rm.mux.RUnlock()
|
|
||||||
|
|
||||||
key := buildKey(policy, pv, kind, ns, name, rv)
|
|
||||||
_, ok := rm.data[key]
|
|
||||||
return !ok
|
|
||||||
}
|
|
||||||
|
|
||||||
//Drop drop the cache after every rebuild interval mins
|
|
||||||
//TODO: or drop based on the size
|
|
||||||
func (rm *ResourceManager) Drop() {
|
|
||||||
timeSince := time.Since(rm.time)
|
|
||||||
glog.V(4).Infof("time since last cache reset time %v is %v", rm.time, timeSince)
|
|
||||||
glog.V(4).Infof("cache rebuild time %v", time.Duration(rm.rebuildTime)*time.Second)
|
|
||||||
if timeSince > time.Duration(rm.rebuildTime)*time.Second {
|
|
||||||
rm.mux.Lock()
|
|
||||||
defer rm.mux.Unlock()
|
|
||||||
rm.data = map[string]interface{}{}
|
|
||||||
rm.time = time.Now()
|
|
||||||
glog.V(4).Infof("dropping cache at time %v", rm.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func buildKey(policy, pv, kind, ns, name, rv string) string {
|
|
||||||
return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []response.EngineResponse {
|
|
||||||
// convert to unstructured
|
|
||||||
unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&namespace)
|
|
||||||
if err != nil {
|
|
||||||
glog.Infof("unable to convert to unstructured, not processing any policies: %v", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
nsc.rm.Drop()
|
|
||||||
|
|
||||||
ns := unstructured.Unstructured{Object: unstr}
|
|
||||||
|
|
||||||
// get all the policies that have a generate rule and resource description satisfies the namespace
|
|
||||||
// apply policy on resource
|
|
||||||
policies := listpolicies(ns, nsc.pMetaStore)
|
|
||||||
var engineResponses []response.EngineResponse
|
|
||||||
for _, policy := range policies {
|
|
||||||
// pre-processing, check if the policy and resource version has been processed before
|
|
||||||
if !nsc.rm.ProcessResource(policy.Name, policy.ResourceVersion, ns.GetKind(), ns.GetNamespace(), ns.GetName(), ns.GetResourceVersion()) {
|
|
||||||
glog.V(4).Infof("policy %s with resource version %s already processed on resource %s/%s/%s with resource version %s", policy.Name, policy.ResourceVersion, ns.GetKind(), ns.GetNamespace(), ns.GetName(), ns.GetResourceVersion())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
engineResponse := applyPolicy(nsc.client, ns, policy, nsc.policyStatus)
|
|
||||||
engineResponses = append(engineResponses, engineResponse)
|
|
||||||
|
|
||||||
// post-processing, register the resource as processed
|
|
||||||
nsc.rm.RegisterResource(policy.GetName(), policy.GetResourceVersion(), ns.GetKind(), ns.GetNamespace(), ns.GetName(), ns.GetResourceVersion())
|
|
||||||
}
|
|
||||||
return engineResponses
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateRuleExists(policy *kyverno.ClusterPolicy) bool {
|
|
||||||
for _, rule := range policy.Spec.Rules {
|
|
||||||
if rule.Generation != (kyverno.Generation{}) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nsc *NamespaceController) processPolicy(policy *kyverno.ClusterPolicy) {
|
|
||||||
filteredNamespaces := []*corev1.Namespace{}
|
|
||||||
// get namespaces that policy applies on
|
|
||||||
namespaces, err := nsc.nsLister.ListResources(labels.NewSelector())
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("failed to get list namespaces: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, namespace := range namespaces {
|
|
||||||
// convert to unstructured
|
|
||||||
unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(namespace)
|
|
||||||
if err != nil {
|
|
||||||
glog.Infof("unable to convert to unstructured, not processing any policies: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ns := unstructured.Unstructured{Object: unstr}
|
|
||||||
for _, rule := range policy.Spec.Rules {
|
|
||||||
if rule.Generation == (kyverno.Generation{}) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ok := engine.MatchesResourceDescription(ns, rule)
|
|
||||||
if !ok {
|
|
||||||
glog.V(4).Infof("namespace %s does not satisfy the resource description for the policy %s rule %s", ns.GetName(), policy.Name, rule.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
glog.V(4).Infof("namespace %s satisfies resource description for policy %s rule %s", ns.GetName(), policy.Name, rule.Name)
|
|
||||||
filteredNamespaces = append(filteredNamespaces, namespace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// list of namespaces that the policy applies on
|
|
||||||
for _, ns := range filteredNamespaces {
|
|
||||||
glog.V(4).Infof("policy %s with generate rule: namespace %s to be processed ", policy.Name, ns.Name)
|
|
||||||
nsc.addNamespace(ns)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func listpolicies(ns unstructured.Unstructured, pMetaStore policystore.LookupInterface) []kyverno.ClusterPolicy {
|
|
||||||
var filteredpolicies []kyverno.ClusterPolicy
|
|
||||||
glog.V(4).Infof("listing policies for namespace %s", ns.GetName())
|
|
||||||
policies, err := pMetaStore.ListAll()
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("failed to get list policies: %v", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, policy := range policies {
|
|
||||||
for _, rule := range policy.Spec.Rules {
|
|
||||||
if rule.Generation == (kyverno.Generation{}) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ok := engine.MatchesResourceDescription(ns, rule)
|
|
||||||
if !ok {
|
|
||||||
glog.V(4).Infof("namespace %s does not satisfy the resource description for the policy %s rule %s", ns.GetName(), policy.Name, rule.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
glog.V(4).Infof("namespace %s satisfies resource description for policy %s rule %s", ns.GetName(), policy.Name, rule.Name)
|
|
||||||
filteredpolicies = append(filteredpolicies, policy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filteredpolicies
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyPolicy(client *client.Client, resource unstructured.Unstructured, p kyverno.ClusterPolicy, policyStatus policyctr.PolicyStatusInterface) response.EngineResponse {
|
|
||||||
startTime := time.Now()
|
|
||||||
glog.V(4).Infof("Started apply policy %s on resource %s/%s/%s (%v)", p.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), startTime)
|
|
||||||
defer func() {
|
|
||||||
glog.V(4).Infof("Finished applying %s on resource %s/%s/%s (%v)", p.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName(), time.Since(startTime))
|
|
||||||
}()
|
|
||||||
// build context
|
|
||||||
ctx := context.NewContext()
|
|
||||||
ctx.AddResource(transformResource(resource))
|
|
||||||
|
|
||||||
policyContext := engine.PolicyContext{
|
|
||||||
NewResource: resource,
|
|
||||||
Policy: p,
|
|
||||||
Client: client,
|
|
||||||
Context: ctx,
|
|
||||||
}
|
|
||||||
engineResponse := engine.Generate(policyContext)
|
|
||||||
return engineResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func transformResource(resource unstructured.Unstructured) []byte {
|
|
||||||
data, err := resource.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("failed to marshall resource %v: %v", resource, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package namespace
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/nirmata/kyverno/pkg/engine/response"
|
|
||||||
"github.com/nirmata/kyverno/pkg/event"
|
|
||||||
"github.com/nirmata/kyverno/pkg/policyviolation"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (nsc *NamespaceController) report(engineResponses []response.EngineResponse) {
|
|
||||||
// generate events
|
|
||||||
eventInfos := generateEvents(engineResponses)
|
|
||||||
nsc.eventGen.Add(eventInfos...)
|
|
||||||
// generate policy violations
|
|
||||||
pvInfos := policyviolation.GeneratePVsFromEngineResponse(engineResponses)
|
|
||||||
nsc.pvGenerator.Add(pvInfos...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateEvents(ers []response.EngineResponse) []event.Info {
|
|
||||||
var eventInfos []event.Info
|
|
||||||
for _, er := range ers {
|
|
||||||
if er.IsSuccesful() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
eventInfos = append(eventInfos, generateEventsPerEr(er)...)
|
|
||||||
}
|
|
||||||
return eventInfos
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateEventsPerEr(er response.EngineResponse) []event.Info {
|
|
||||||
var eventInfos []event.Info
|
|
||||||
glog.V(4).Infof("reporting results for policy '%s' application on resource '%s/%s/%s'", er.PolicyResponse.Policy, er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name)
|
|
||||||
for _, rule := range er.PolicyResponse.Rules {
|
|
||||||
if rule.Success {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// generate event on resource for each failed rule
|
|
||||||
glog.V(4).Infof("generation event on resource '%s/%s' for policy '%s'", er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Name, er.PolicyResponse.Policy)
|
|
||||||
e := event.Info{}
|
|
||||||
e.Kind = er.PolicyResponse.Resource.Kind
|
|
||||||
e.Namespace = "" // event generate on namespace resource
|
|
||||||
e.Name = er.PolicyResponse.Resource.Name
|
|
||||||
e.Reason = "Failure"
|
|
||||||
e.Source = event.GeneratePolicyController
|
|
||||||
e.Message = fmt.Sprintf("policy '%s' (%s) rule '%s' not satisfied. %v", er.PolicyResponse.Policy, rule.Type, rule.Name, rule.Message)
|
|
||||||
eventInfos = append(eventInfos, e)
|
|
||||||
}
|
|
||||||
if er.IsSuccesful() {
|
|
||||||
return eventInfos
|
|
||||||
}
|
|
||||||
// generate a event on policy for all failed rules
|
|
||||||
glog.V(4).Infof("generation event on policy '%s'", er.PolicyResponse.Policy)
|
|
||||||
e := event.Info{}
|
|
||||||
e.Kind = "ClusterPolicy"
|
|
||||||
e.Namespace = ""
|
|
||||||
e.Name = er.PolicyResponse.Policy
|
|
||||||
e.Reason = "Failure"
|
|
||||||
e.Source = event.GeneratePolicyController
|
|
||||||
e.Message = fmt.Sprintf("policy '%s' rules '%v' on resource '%s/%s/%s' not stasified", er.PolicyResponse.Policy, er.GetFailedRules(), er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name)
|
|
||||||
return eventInfos
|
|
||||||
}
|
|
73
pkg/policy/background.go
Normal file
73
pkg/policy/background.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package policy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
|
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||||
|
"github.com/nirmata/kyverno/pkg/engine/variables"
|
||||||
|
)
|
||||||
|
|
||||||
|
//ContainsUserInfo returns error is userInfo is defined
|
||||||
|
func ContainsUserInfo(policy kyverno.ClusterPolicy) error {
|
||||||
|
var err error
|
||||||
|
// iterate of the policy rules to identify if userInfo is used
|
||||||
|
for idx, rule := range policy.Spec.Rules {
|
||||||
|
if path := userInfoDefined(rule.MatchResources.UserInfo); path != "" {
|
||||||
|
return fmt.Errorf("userInfo variable used at path: spec/rules[%d]/match/%s", idx, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if path := userInfoDefined(rule.ExcludeResources.UserInfo); path != "" {
|
||||||
|
return fmt.Errorf("userInfo variable used at path: spec/rules[%d]/exclude/%s", idx, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// variable defined with user information
|
||||||
|
// - condition.key
|
||||||
|
// - condition.value
|
||||||
|
// - mutate.overlay
|
||||||
|
// - validate.pattern
|
||||||
|
// - validate.anyPattern[*]
|
||||||
|
// variables to filter
|
||||||
|
// - request.userInfo*
|
||||||
|
// - serviceAccountName
|
||||||
|
// - serviceAccountNamespace
|
||||||
|
|
||||||
|
filterVars := []string{"request.userInfo*", "serviceAccountName", "serviceAccountNamespace"}
|
||||||
|
ctx := context.NewContext(filterVars...)
|
||||||
|
for condIdx, condition := range rule.Conditions {
|
||||||
|
if condition.Key, err = variables.SubstituteVars(ctx, condition.Key); err != nil {
|
||||||
|
return fmt.Errorf("userInfo variable used at spec/rules[%d]/condition[%d]/key", idx, condIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if condition.Value, err = variables.SubstituteVars(ctx, condition.Value); err != nil {
|
||||||
|
return fmt.Errorf("userInfo variable used at spec/rules[%d]/condition[%d]/value", idx, condIdx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule.Mutation.Overlay, err = variables.SubstituteVars(ctx, rule.Mutation.Overlay); err != nil {
|
||||||
|
return fmt.Errorf("userInfo variable used at spec/rules[%d]/mutate/overlay", idx)
|
||||||
|
}
|
||||||
|
if rule.Validation.Pattern, err = variables.SubstituteVars(ctx, rule.Validation.Pattern); err != nil {
|
||||||
|
return fmt.Errorf("userInfo variable used at spec/rules[%d]/validate/pattern", idx)
|
||||||
|
}
|
||||||
|
for idx2, pattern := range rule.Validation.AnyPattern {
|
||||||
|
if rule.Validation.AnyPattern[idx2], err = variables.SubstituteVars(ctx, pattern); err != nil {
|
||||||
|
return fmt.Errorf("userInfo variable used at spec/rules[%d]/validate/anyPattern[%d]", idx, idx2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func userInfoDefined(ui kyverno.UserInfo) string {
|
||||||
|
if len(ui.Roles) > 0 {
|
||||||
|
return "roles"
|
||||||
|
}
|
||||||
|
if len(ui.ClusterRoles) > 0 {
|
||||||
|
return "clusterRoles"
|
||||||
|
}
|
||||||
|
if len(ui.Subjects) > 0 {
|
||||||
|
return "subjects"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
|
@ -12,7 +12,6 @@ import (
|
||||||
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
|
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
|
||||||
"github.com/nirmata/kyverno/pkg/config"
|
"github.com/nirmata/kyverno/pkg/config"
|
||||||
client "github.com/nirmata/kyverno/pkg/dclient"
|
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/event"
|
||||||
"github.com/nirmata/kyverno/pkg/policystore"
|
"github.com/nirmata/kyverno/pkg/policystore"
|
||||||
"github.com/nirmata/kyverno/pkg/policyviolation"
|
"github.com/nirmata/kyverno/pkg/policyviolation"
|
||||||
|
@ -155,7 +154,7 @@ func (pc *PolicyController) addPolicy(obj interface{}) {
|
||||||
// TODO: code might seem vague, awaiting resolution of issue https://github.com/nirmata/kyverno/issues/598
|
// TODO: code might seem vague, awaiting resolution of issue https://github.com/nirmata/kyverno/issues/598
|
||||||
if p.Spec.Background == nil {
|
if p.Spec.Background == nil {
|
||||||
// if userInfo is not defined in policy we process the policy
|
// if userInfo is not defined in policy we process the policy
|
||||||
if err := policy.ContainsUserInfo(*p); err != nil {
|
if err := ContainsUserInfo(*p); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -164,7 +163,7 @@ func (pc *PolicyController) addPolicy(obj interface{}) {
|
||||||
}
|
}
|
||||||
// If userInfo is used then skip the policy
|
// If userInfo is used then skip the policy
|
||||||
// ideally this should be handled by background flag only
|
// ideally this should be handled by background flag only
|
||||||
if err := policy.ContainsUserInfo(*p); err != nil {
|
if err := ContainsUserInfo(*p); err != nil {
|
||||||
// contains userInfo used in policy
|
// contains userInfo used in policy
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -190,7 +189,7 @@ func (pc *PolicyController) updatePolicy(old, cur interface{}) {
|
||||||
// TODO: code might seem vague, awaiting resolution of issue https://github.com/nirmata/kyverno/issues/598
|
// TODO: code might seem vague, awaiting resolution of issue https://github.com/nirmata/kyverno/issues/598
|
||||||
if curP.Spec.Background == nil {
|
if curP.Spec.Background == nil {
|
||||||
// if userInfo is not defined in policy we process the policy
|
// if userInfo is not defined in policy we process the policy
|
||||||
if err := policy.ContainsUserInfo(*curP); err != nil {
|
if err := ContainsUserInfo(*curP); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -199,7 +198,7 @@ func (pc *PolicyController) updatePolicy(old, cur interface{}) {
|
||||||
}
|
}
|
||||||
// If userInfo is used then skip the policy
|
// If userInfo is used then skip the policy
|
||||||
// ideally this should be handled by background flag only
|
// ideally this should be handled by background flag only
|
||||||
if err := policy.ContainsUserInfo(*curP); err != nil {
|
if err := ContainsUserInfo(*curP); err != nil {
|
||||||
// contains userInfo used in policy
|
// contains userInfo used in policy
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ func Validate(p kyverno.ClusterPolicy) error {
|
||||||
// policy.spec.background -> "true"
|
// policy.spec.background -> "true"
|
||||||
// - cannot use variables with request.userInfo
|
// - cannot use variables with request.userInfo
|
||||||
// - cannot define userInfo(roles, cluserRoles, subjects) for filtering (match & exclude)
|
// - cannot define userInfo(roles, cluserRoles, subjects) for filtering (match & exclude)
|
||||||
return fmt.Errorf("userInfo not allowed in background policy mode. Failure path %s", err)
|
return fmt.Errorf("userInfo is not allowed in match or exclude when backgroud policy mode is true. Set spec.background=false to disable background mode for this policy rule. %s ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@ func Validate(p kyverno.ClusterPolicy) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1190,10 +1190,7 @@ func Test_BackGroundUserInfo_match_roles(t *testing.T) {
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
err = ContainsUserInfo(*policy)
|
err = ContainsUserInfo(*policy)
|
||||||
|
assert.Equal(t, err.Error(), "userInfo variable used at path: spec/rules[0]/match/roles")
|
||||||
if err.Error() != "path: spec/rules[0]/match/roles" {
|
|
||||||
t.Error("Incorrect Path")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_BackGroundUserInfo_match_clusterRoles(t *testing.T) {
|
func Test_BackGroundUserInfo_match_clusterRoles(t *testing.T) {
|
||||||
|
@ -1226,10 +1223,7 @@ func Test_BackGroundUserInfo_match_clusterRoles(t *testing.T) {
|
||||||
|
|
||||||
err = ContainsUserInfo(*policy)
|
err = ContainsUserInfo(*policy)
|
||||||
|
|
||||||
if err.Error() != "path: spec/rules[0]/match/clusterRoles" {
|
assert.Equal(t, err.Error(), "userInfo variable used at path: spec/rules[0]/match/clusterRoles")
|
||||||
t.Log(err)
|
|
||||||
t.Error("Incorrect Path")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_BackGroundUserInfo_match_subjects(t *testing.T) {
|
func Test_BackGroundUserInfo_match_subjects(t *testing.T) {
|
||||||
|
@ -1265,10 +1259,7 @@ func Test_BackGroundUserInfo_match_subjects(t *testing.T) {
|
||||||
|
|
||||||
err = ContainsUserInfo(*policy)
|
err = ContainsUserInfo(*policy)
|
||||||
|
|
||||||
if err.Error() != "path: spec/rules[0]/match/subjects" {
|
assert.Equal(t, err.Error(), "userInfo variable used at path: spec/rules[0]/match/subjects")
|
||||||
t.Log(err)
|
|
||||||
t.Error("Incorrect Path")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_BackGroundUserInfo_mutate_overlay1(t *testing.T) {
|
func Test_BackGroundUserInfo_mutate_overlay1(t *testing.T) {
|
||||||
|
@ -1300,7 +1291,7 @@ func Test_BackGroundUserInfo_mutate_overlay1(t *testing.T) {
|
||||||
|
|
||||||
err = ContainsUserInfo(*policy)
|
err = ContainsUserInfo(*policy)
|
||||||
|
|
||||||
if err.Error() != "path: spec/rules[0]/mutate/overlay/var1/{{request.userInfo}}" {
|
if err.Error() != "userInfo variable used at spec/rules[0]/mutate/overlay" {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.Error("Incorrect Path")
|
t.Error("Incorrect Path")
|
||||||
}
|
}
|
||||||
|
@ -1335,7 +1326,7 @@ func Test_BackGroundUserInfo_mutate_overlay2(t *testing.T) {
|
||||||
|
|
||||||
err = ContainsUserInfo(*policy)
|
err = ContainsUserInfo(*policy)
|
||||||
|
|
||||||
if err.Error() != "path: spec/rules[0]/mutate/overlay/var1/{{request.userInfo.userName}}" {
|
if err.Error() != "userInfo variable used at spec/rules[0]/mutate/overlay" {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.Error("Incorrect Path")
|
t.Error("Incorrect Path")
|
||||||
}
|
}
|
||||||
|
@ -1370,7 +1361,7 @@ func Test_BackGroundUserInfo_validate_pattern(t *testing.T) {
|
||||||
|
|
||||||
err = ContainsUserInfo(*policy)
|
err = ContainsUserInfo(*policy)
|
||||||
|
|
||||||
if err.Error() != "path: spec/rules[0]/validate/pattern/var1/{{request.userInfo}}" {
|
if err.Error() != "userInfo variable used at spec/rules[0]/validate/pattern" {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.Error("Incorrect Path")
|
t.Error("Incorrect Path")
|
||||||
}
|
}
|
||||||
|
@ -1409,7 +1400,7 @@ func Test_BackGroundUserInfo_validate_anyPattern(t *testing.T) {
|
||||||
|
|
||||||
err = ContainsUserInfo(*policy)
|
err = ContainsUserInfo(*policy)
|
||||||
|
|
||||||
if err.Error() != "path: spec/rules[0]/validate/anyPattern[1]/var1/{{request.userInfo}}" {
|
if err.Error() != "userInfo variable used at spec/rules[0]/validate/anyPattern[1]" {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.Error("Incorrect Path")
|
t.Error("Incorrect Path")
|
||||||
}
|
}
|
||||||
|
@ -1448,7 +1439,7 @@ func Test_BackGroundUserInfo_validate_anyPattern_multiple_var(t *testing.T) {
|
||||||
|
|
||||||
err = ContainsUserInfo(*policy)
|
err = ContainsUserInfo(*policy)
|
||||||
|
|
||||||
if err.Error() != "path: spec/rules[0]/validate/anyPattern[1]/var1/{{request.userInfo}}-{{temp}}" {
|
if err.Error() != "userInfo variable used at spec/rules[0]/validate/anyPattern[1]" {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.Error("Incorrect Path")
|
t.Error("Incorrect Path")
|
||||||
}
|
}
|
||||||
|
@ -1487,7 +1478,7 @@ func Test_BackGroundUserInfo_validate_anyPattern_serviceAccount(t *testing.T) {
|
||||||
|
|
||||||
err = ContainsUserInfo(*policy)
|
err = ContainsUserInfo(*policy)
|
||||||
|
|
||||||
if err.Error() != "path: spec/rules[0]/validate/anyPattern[1]/var1/{{serviceAccountName}}" {
|
if err.Error() != "userInfo variable used at spec/rules[0]/validate/anyPattern[1]" {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.Error("Incorrect Path")
|
t.Error("Incorrect Path")
|
||||||
}
|
}
|
|
@ -16,8 +16,8 @@ func GeneratePVsFromEngineResponse(ers []response.EngineResponse) (pvInfos []Inf
|
||||||
glog.V(4).Infof("resource %v, has not been assigned a name, not creating a policy violation for it", er.PolicyResponse.Resource)
|
glog.V(4).Infof("resource %v, has not been assigned a name, not creating a policy violation for it", er.PolicyResponse.Resource)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// skip when response succeed AND referenced paths exist
|
// skip when response succeed
|
||||||
if er.IsSuccesful() && !er.IsPathNotPresent() {
|
if er.IsSuccesful() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
glog.V(4).Infof("Building policy violation for engine response %v", er)
|
glog.V(4).Infof("Building policy violation for engine response %v", er)
|
||||||
|
@ -82,7 +82,7 @@ func buildPVInfo(er response.EngineResponse) Info {
|
||||||
func buildViolatedRules(er response.EngineResponse) []kyverno.ViolatedRule {
|
func buildViolatedRules(er response.EngineResponse) []kyverno.ViolatedRule {
|
||||||
var violatedRules []kyverno.ViolatedRule
|
var violatedRules []kyverno.ViolatedRule
|
||||||
for _, rule := range er.PolicyResponse.Rules {
|
for _, rule := range er.PolicyResponse.Rules {
|
||||||
if rule.Success && !rule.PathNotPresent {
|
if rule.Success {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
vrule := kyverno.ViolatedRule{
|
vrule := kyverno.ViolatedRule{
|
||||||
|
|
|
@ -19,17 +19,15 @@ func Test_GeneratePVsFromEngineResponse_PathNotExist(t *testing.T) {
|
||||||
},
|
},
|
||||||
Rules: []response.RuleResponse{
|
Rules: []response.RuleResponse{
|
||||||
{
|
{
|
||||||
Name: "test-path-not-exist",
|
Name: "test-path-not-exist",
|
||||||
Type: "Mutation",
|
Type: "Mutation",
|
||||||
Message: "referenced paths are not present: request.object.metadata.name1",
|
Message: "referenced paths are not present: request.object.metadata.name1",
|
||||||
Success: true,
|
Success: false,
|
||||||
PathNotPresent: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "test-path-exist",
|
Name: "test-path-exist",
|
||||||
Type: "Mutation",
|
Type: "Mutation",
|
||||||
Success: true,
|
Success: true,
|
||||||
PathNotPresent: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -44,11 +42,10 @@ func Test_GeneratePVsFromEngineResponse_PathNotExist(t *testing.T) {
|
||||||
},
|
},
|
||||||
Rules: []response.RuleResponse{
|
Rules: []response.RuleResponse{
|
||||||
{
|
{
|
||||||
Name: "test-path-not-exist-across-policy",
|
Name: "test-path-not-exist-across-policy",
|
||||||
Type: "Mutation",
|
Type: "Mutation",
|
||||||
Message: "referenced paths are not present: request.object.metadata.name1",
|
Message: "referenced paths are not present: request.object.metadata.name1",
|
||||||
Success: true,
|
Success: true,
|
||||||
PathNotPresent: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -56,5 +53,5 @@ func Test_GeneratePVsFromEngineResponse_PathNotExist(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pvInfos := GeneratePVsFromEngineResponse(ers)
|
pvInfos := GeneratePVsFromEngineResponse(ers)
|
||||||
assert.Assert(t, len(pvInfos) == 2)
|
assert.Assert(t, len(pvInfos) == 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/nirmata/kyverno/pkg/engine/rbac"
|
|
||||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||||
authenticationv1 "k8s.io/api/authentication/v1"
|
authenticationv1 "k8s.io/api/authentication/v1"
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
|
@ -16,6 +15,7 @@ import (
|
||||||
const (
|
const (
|
||||||
clusterrolekind = "ClusterRole"
|
clusterrolekind = "ClusterRole"
|
||||||
rolekind = "Role"
|
rolekind = "Role"
|
||||||
|
SaPrefix = "system:serviceaccount:"
|
||||||
)
|
)
|
||||||
|
|
||||||
//GetRoleRef gets the list of roles and cluster roles for the incoming api-request
|
//GetRoleRef gets the list of roles and cluster roles for the incoming api-request
|
||||||
|
@ -88,27 +88,20 @@ func getRoleRefByClusterRoleBindings(clusterroleBindings []*rbacv1.ClusterRoleBi
|
||||||
// subject.kind can only be ServiceAccount, User and Group
|
// subject.kind can only be ServiceAccount, User and Group
|
||||||
func matchSubjectsMap(subject rbacv1.Subject, userInfo authenticationv1.UserInfo) bool {
|
func matchSubjectsMap(subject rbacv1.Subject, userInfo authenticationv1.UserInfo) bool {
|
||||||
// ServiceAccount
|
// ServiceAccount
|
||||||
if isServiceaccountUserInfo(userInfo.Username) {
|
if strings.Contains(userInfo.Username, SaPrefix) {
|
||||||
return matchServiceAccount(subject, userInfo)
|
return matchServiceAccount(subject, userInfo)
|
||||||
|
} else {
|
||||||
|
// User or Group
|
||||||
|
return matchUserOrGroup(subject, userInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// User or Group
|
|
||||||
return matchUserOrGroup(subject, userInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isServiceaccountUserInfo(username string) bool {
|
|
||||||
if strings.Contains(username, rbac.SaPrefix) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// matchServiceAccount checks if userInfo sa matche the subject sa
|
// matchServiceAccount checks if userInfo sa matche the subject sa
|
||||||
// serviceaccount represents as saPrefix:namespace:name in userInfo
|
// serviceaccount represents as saPrefix:namespace:name in userInfo
|
||||||
func matchServiceAccount(subject rbacv1.Subject, userInfo authenticationv1.UserInfo) bool {
|
func matchServiceAccount(subject rbacv1.Subject, userInfo authenticationv1.UserInfo) bool {
|
||||||
subjectServiceAccount := subject.Namespace + ":" + subject.Name
|
subjectServiceAccount := subject.Namespace + ":" + subject.Name
|
||||||
if userInfo.Username[len(rbac.SaPrefix):] != subjectServiceAccount {
|
if userInfo.Username[len(SaPrefix):] != subjectServiceAccount {
|
||||||
glog.V(3).Infof("service account not match, expect %s, got %s", subjectServiceAccount, userInfo.Username[len(rbac.SaPrefix):])
|
glog.V(3).Infof("service account not match, expect %s, got %s", subjectServiceAccount, userInfo.Username[len(SaPrefix):])
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,27 +11,6 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_isServiceaccountUserInfo(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
username string
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
username: "system:serviceaccount:default:saconfig",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
username: "serviceaccount:default:saconfig",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
res := isServiceaccountUserInfo(test.username)
|
|
||||||
assert.Assert(t, test.expected == res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_matchServiceAccount_subject_variants(t *testing.T) {
|
func Test_matchServiceAccount_subject_variants(t *testing.T) {
|
||||||
userInfo := authenticationv1.UserInfo{
|
userInfo := authenticationv1.UserInfo{
|
||||||
Username: "system:serviceaccount:default:saconfig",
|
Username: "system:serviceaccount:default:saconfig",
|
||||||
|
|
|
@ -114,7 +114,7 @@ func processResourceWithPatches(patch []byte, resource []byte) []byte {
|
||||||
func containRBACinfo(policies []kyverno.ClusterPolicy) bool {
|
func containRBACinfo(policies []kyverno.ClusterPolicy) bool {
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
for _, rule := range policy.Spec.Rules {
|
for _, rule := range policy.Spec.Rules {
|
||||||
if len(rule.MatchResources.Roles) > 0 || len(rule.MatchResources.ClusterRoles) > 0 {
|
if len(rule.MatchResources.Roles) > 0 || len(rule.MatchResources.ClusterRoles) > 0 || len(rule.ExcludeResources.Roles) > 0 || len(rule.ExcludeResources.ClusterRoles) > 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ func (ws *WebhookServer) handlePolicyMutation(request *v1beta1.AdmissionRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Generate JSON Patches for defaults
|
// Generate JSON Patches for defaults
|
||||||
patches, updateMsgs := generateJSONPatchesForDefaults(policy, request.Operation)
|
patches, updateMsgs := generateJSONPatchesForDefaults(policy)
|
||||||
if patches != nil {
|
if patches != nil {
|
||||||
patchType := v1beta1.PatchTypeJSONPatch
|
patchType := v1beta1.PatchTypeJSONPatch
|
||||||
glog.V(4).Infof("defaulted values %v policy %s", updateMsgs, policy.Name)
|
glog.V(4).Infof("defaulted values %v policy %s", updateMsgs, policy.Name)
|
||||||
|
@ -50,7 +50,7 @@ func (ws *WebhookServer) handlePolicyMutation(request *v1beta1.AdmissionRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy, operation v1beta1.Operation) ([]byte, []string) {
|
func generateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy) ([]byte, []string) {
|
||||||
var patches [][]byte
|
var patches [][]byte
|
||||||
var updateMsgs []string
|
var updateMsgs []string
|
||||||
|
|
||||||
|
@ -66,20 +66,18 @@ func generateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy, operation v1b
|
||||||
updateMsgs = append(updateMsgs, updateMsg)
|
updateMsgs = append(updateMsgs, updateMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(shuting): enable this feature on policy UPDATE
|
patch, errs := generatePodControllerRule(*policy)
|
||||||
if operation == v1beta1.Create {
|
if len(errs) > 0 {
|
||||||
patch, errs := generatePodControllerRule(*policy)
|
var errMsgs []string
|
||||||
if len(errs) > 0 {
|
for _, err := range errs {
|
||||||
var errMsgs []string
|
errMsgs = append(errMsgs, err.Error())
|
||||||
for _, err := range errs {
|
|
||||||
errMsgs = append(errMsgs, err.Error())
|
|
||||||
}
|
|
||||||
glog.Errorf("failed auto generatig rule for pod controllers: %s", errMsgs)
|
|
||||||
updateMsgs = append(updateMsgs, strings.Join(errMsgs, ";"))
|
|
||||||
}
|
}
|
||||||
|
glog.Errorf("failed auto generating rule for pod controllers: %s", errMsgs)
|
||||||
patches = append(patches, patch...)
|
updateMsgs = append(updateMsgs, strings.Join(errMsgs, ";"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patches = append(patches, patch...)
|
||||||
|
|
||||||
return utils.JoinPatches(patches), updateMsgs
|
return utils.JoinPatches(patches), updateMsgs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,25 +168,73 @@ func generatePodControllerRule(policy kyverno.ClusterPolicy) (patches [][]byte,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createRuleMap(rules []kyverno.Rule) map[string]kyvernoRule {
|
||||||
|
var ruleMap = make(map[string]kyvernoRule)
|
||||||
|
for _, rule := range rules {
|
||||||
|
var jsonFriendlyStruct kyvernoRule
|
||||||
|
|
||||||
|
jsonFriendlyStruct.Name = rule.Name
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(rule.MatchResources, kyverno.MatchResources{}) {
|
||||||
|
jsonFriendlyStruct.MatchResources = rule.MatchResources.DeepCopy()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(rule.ExcludeResources, kyverno.ExcludeResources{}) {
|
||||||
|
jsonFriendlyStruct.ExcludeResources = rule.ExcludeResources.DeepCopy()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) {
|
||||||
|
jsonFriendlyStruct.Mutation = rule.Mutation.DeepCopy()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(rule.Validation, kyverno.Validation{}) {
|
||||||
|
jsonFriendlyStruct.Validation = rule.Validation.DeepCopy()
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleMap[rule.Name] = jsonFriendlyStruct
|
||||||
|
}
|
||||||
|
return ruleMap
|
||||||
|
}
|
||||||
|
|
||||||
// generateRulePatches generates rule for podControllers based on scenario A and C
|
// generateRulePatches generates rule for podControllers based on scenario A and C
|
||||||
func generateRulePatches(policy kyverno.ClusterPolicy, controllers string) (rulePatches [][]byte, errs []error) {
|
func generateRulePatches(policy kyverno.ClusterPolicy, controllers string) (rulePatches [][]byte, errs []error) {
|
||||||
var genRule kyvernoRule
|
var genRule kyvernoRule
|
||||||
insertIdx := len(policy.Spec.Rules)
|
insertIdx := len(policy.Spec.Rules)
|
||||||
|
|
||||||
|
ruleMap := createRuleMap(policy.Spec.Rules)
|
||||||
|
var ruleIndex = make(map[string]int)
|
||||||
|
for index, rule := range policy.Spec.Rules {
|
||||||
|
ruleIndex[rule.Name] = index
|
||||||
|
}
|
||||||
|
|
||||||
for _, rule := range policy.Spec.Rules {
|
for _, rule := range policy.Spec.Rules {
|
||||||
|
patchPostion := insertIdx
|
||||||
|
|
||||||
genRule = generateRuleForControllers(rule, controllers)
|
genRule = generateRuleForControllers(rule, controllers)
|
||||||
if reflect.DeepEqual(genRule, kyvernoRule{}) {
|
if reflect.DeepEqual(genRule, kyvernoRule{}) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
operation := "add"
|
||||||
|
if existingAutoGenRule, alreadyExists := ruleMap[genRule.Name]; alreadyExists {
|
||||||
|
existingAutoGenRuleRaw, _ := json.Marshal(existingAutoGenRule)
|
||||||
|
genRuleRaw, _ := json.Marshal(genRule)
|
||||||
|
|
||||||
|
if string(existingAutoGenRuleRaw) == string(genRuleRaw) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
operation = "replace"
|
||||||
|
patchPostion = ruleIndex[genRule.Name]
|
||||||
|
}
|
||||||
|
|
||||||
// generate patch bytes
|
// generate patch bytes
|
||||||
jsonPatch := struct {
|
jsonPatch := struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Op string `json:"op"`
|
Op string `json:"op"`
|
||||||
Value interface{} `json:"value"`
|
Value interface{} `json:"value"`
|
||||||
}{
|
}{
|
||||||
fmt.Sprintf("/spec/rules/%s", strconv.Itoa(insertIdx)),
|
fmt.Sprintf("/spec/rules/%s", strconv.Itoa(patchPostion)),
|
||||||
"add",
|
operation,
|
||||||
genRule,
|
genRule,
|
||||||
}
|
}
|
||||||
pbytes, err := json.Marshal(jsonPatch)
|
pbytes, err := json.Marshal(jsonPatch)
|
||||||
|
@ -227,6 +273,10 @@ type kyvernoRule struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateRuleForControllers(rule kyverno.Rule, controllers string) kyvernoRule {
|
func generateRuleForControllers(rule kyverno.Rule, controllers string) kyvernoRule {
|
||||||
|
if strings.HasPrefix(rule.Name, "autogen-") {
|
||||||
|
return kyvernoRule{}
|
||||||
|
}
|
||||||
|
|
||||||
match := rule.MatchResources
|
match := rule.MatchResources
|
||||||
exclude := rule.ExcludeResources
|
exclude := rule.ExcludeResources
|
||||||
if !utils.ContainsString(match.ResourceDescription.Kinds, "Pod") ||
|
if !utils.ContainsString(match.ResourceDescription.Kinds, "Pod") ||
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||||
policyvalidate "github.com/nirmata/kyverno/pkg/engine/policy"
|
policyvalidate "github.com/nirmata/kyverno/pkg/policy"
|
||||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
@ -35,7 +35,6 @@ func (ws *WebhookServer) handlePolicyValidation(request *v1beta1.AdmissionReques
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if admissionResp.Allowed {
|
if admissionResp.Allowed {
|
||||||
// if the policy contains mutating & validation rules and it config does not exist we create one
|
// if the policy contains mutating & validation rules and it config does not exist we create one
|
||||||
// queue the request
|
// queue the request
|
||||||
|
|
|
@ -4,9 +4,13 @@ metadata:
|
||||||
name: myapp-pod
|
name: myapp-pod
|
||||||
labels:
|
labels:
|
||||||
app: myapp
|
app: myapp
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: nginx
|
- name: goproxy
|
||||||
image: nginx
|
image: k8s.gcr.io/goproxy:0.1
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
periodSeconds: 5
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
periodSeconds: 10
|
Loading…
Add table
Reference in a new issue