1
0
Fork 0
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:
shravan 2020-02-24 20:19:28 +05:30
commit 36e775edb0
69 changed files with 99030 additions and 3080 deletions

View file

@ -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
################################## ##################################

View file

@ -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

View file

@ -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()
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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:

View file

@ -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`.

View 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>

View file

@ -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>

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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

View file

@ -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)
} }

View file

@ -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{}

View file

@ -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)

View file

@ -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"`
}{ }{

View file

@ -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
}

View file

@ -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)
} }

View file

@ -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

View file

@ -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

View file

@ -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"))
} }

View file

@ -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
}

View file

@ -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
}

View file

@ -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",
},
}
}

View file

@ -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
}

View file

@ -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
} }

View file

@ -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))
} }
} }

View file

@ -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}
}

View file

@ -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:

View file

@ -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{}
} }

View file

@ -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)
} }

View file

@ -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
} }

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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")
} }
} }

View 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}
}

View 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")
}
}

View file

@ -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
} }

View file

@ -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
View 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
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
View 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 ""
}

View file

@ -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
} }

View file

@ -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
} }

View file

@ -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")
} }

View file

@ -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{

View file

@ -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)
} }

View file

@ -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
} }

View file

@ -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",

View file

@ -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
} }
} }

View file

@ -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") ||

View file

@ -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

View file

@ -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