1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

Merge branch 'master' into update_doc

This commit is contained in:
shivkumar dudhani 2020-02-07 11:22:50 -08:00
commit 4e1b11c1d4
21 changed files with 450 additions and 290 deletions

View file

@ -32,6 +32,8 @@ kind: ClusterPolicy
metadata:
name: check-cpu-memory
spec:
# `enforce` blocks the request. `audit` reports violations
validationFailureAction: enforce
rules:
- name: check-pod-resources
match:
@ -71,17 +73,15 @@ spec:
match:
resources:
kinds:
- Deployment
- Pod
mutate:
overlay:
spec:
template:
spec:
containers:
# match images which end with :latest
- (image): "*:latest"
# set the imagePullPolicy to "Always"
imagePullPolicy: "Always"
containers:
# match images which end with :latest
- (image): "*:latest"
# set the imagePullPolicy to "Always"
imagePullPolicy: "Always"
````
### 3. Generating resources
@ -100,13 +100,10 @@ spec:
resources:
kinds:
- Namespace
selector:
matchExpressions:
- {key: kafka, operator: Exists}
generate:
kind: ConfigMap
name: zk-kafka-address
# create the resource in the new namespace
# generate the resource in the new namespace
namespace: "{{request.object.metadata.name}}"
data:
kind: ConfigMap
@ -123,12 +120,14 @@ Refer to a list of curated of ***[sample policies](/samples/README.md)*** that c
* [Getting Started](documentation/installation.md)
* [Writing Policies](documentation/writing-policies.md)
* [Mutate](documentation/writing-policies-mutate.md)
* [Validate](documentation/writing-policies-validate.md)
* [Generate](documentation/writing-policies-generate.md)
* [Validate Resources](documentation/writing-policies-validate.md)
* [Mutate Resources](documentation/writing-policies-mutate.md)
* [Generate Resources](documentation/writing-policies-generate.md)
* [Variable Substitution](documentation/writing-policies-variables.md)
* [Preconditions](documentation/writing-policies-preconditions.md)
* [Auto-Generation of Pod Controller Policies](documentation/writing-policies-autogen.md)
* [Background Processing](documentation/writing-policies-background.md)
* [Testing Policies](documentation/testing-policies.md)
* [Using kubectl](documentation/testing-policies.md#Test-using-kubectl)
* [Using the Kyverno CLI](documentation/testing-policies.md#Test-using-the-Kyverno-CLI)
* [Sample Policies](/samples/README.md)
## License

View file

@ -46,6 +46,8 @@ func main() {
requests := []request{
// Resource
{validatingWebhookConfigKind, config.ValidatingWebhookConfigurationName},
{validatingWebhookConfigKind, config.ValidatingWebhookConfigurationDebugName},
{mutatingWebhookConfigKind, config.MutatingWebhookConfigurationName},
{mutatingWebhookConfigKind, config.MutatingWebhookConfigurationDebugName},
// Policy

View file

@ -27,9 +27,10 @@ import (
)
var (
kubeconfig string
serverIP string
webhookTimeout int
kubeconfig string
serverIP string
webhookTimeout int
runValidationInMutatingWebhook string
//TODO: this has been added to backward support command line arguments
// will be removed in future and the configuration will be set only via configmaps
filterK8Resources string
@ -40,7 +41,6 @@ var (
func main() {
defer glog.Flush()
version.PrintVersionInfo()
// cleanUp Channel
cleanUp := make(chan struct{})
// handle os signals
@ -103,7 +103,9 @@ func main() {
rWebhookWatcher := webhookconfig.NewResourceWebhookRegister(
lastReqTime,
kubeInformer.Admissionregistration().V1beta1().MutatingWebhookConfigurations(),
kubeInformer.Admissionregistration().V1beta1().ValidatingWebhookConfigurations(),
webhookRegistrationClient,
runValidationInMutatingWebhook,
)
// KYVERNO CRD INFORMER
@ -265,6 +267,8 @@ func init() {
flag.IntVar(&webhookTimeout, "webhooktimeout", 3, "timeout for webhook configurations")
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
flag.StringVar(&serverIP, "serverIP", "", "IP address where Kyverno controller runs. Only required if out-of-cluster.")
flag.StringVar(&runValidationInMutatingWebhook, "runValidationInMutatingWebhook", "", "Validation will also be done using the mutation webhook, set to 'true' to enable. Older kubernetes versions do not work properly when a validation webhook is registered.")
// Generate CSR with CN as FQDN due to https://github.com/nirmata/kyverno/issues/542
flag.BoolVar(&fqdncn, "fqdn-as-cn", false, "use FQDN as Common Name in CSR")
config.LogDefaultFlags()

View file

@ -2,9 +2,11 @@
# Testing Policies
The resources definitions for testing are located in [/test](/test) directory. Each test contains a pair of files: one is the resource definition, and the second is the kyverno policy for this definition.
## Test using kubectl
To do this you should [install kyverno to the cluster](/documentation/installation.md).
For example, to test the simplest kyverno policy for ConfigMap, create the policy and then the resource itself via kubectl:
@ -19,52 +21,3 @@ Then compare the original resource definition in CM.yaml with the actual one:
````bash
kubectl get -f CM.yaml -o yaml
````
## Test using the Kyverno CLI
The Kyverno Command Line Interface (CLI) tool allows writing and testing policies without having to apply local policy changes to a cluster. You can also test policies without a Kubernetes clusters, but results may vary as default values will not be filled in.
### Building the CLI
You will need a [Go environment](https://golang.org/doc/install) setup.
1. Clone the Kyverno repo
````bash
git clone https://github.com/nirmata/kyverno/
````
2. Build the CLI
````bash
cd kyverno/cmd/kyverno
go build
````
Or, you can directly build and install the CLI using `go get`:
````bash
go get -u https://github.com/nirmata/kyverno/cmd/kyverno
````
### Using the CLI
The CLI loads default kubeconfig ($HOME/.kube/config) to test policies in Kubernetes cluster. If no kubeconfig is found, the CLI will test policies on raw resources.
To test a policy using the CLI type:
`kyverno apply @<policy> @<resource YAML file or folder>`
For example:
```bash
kyverno apply @../../examples/cli/policy-deployment.yaml @../../examples/cli/resources
```
To test a policy with the specific kubeconfig:
```bash
kyverno apply @../../examples/cli/policy-deployment.yaml @../../examples/cli/resources --kubeconfig $PATH_TO_KUBECONFIG_FILE
```
In future releases, the CLI will support complete validation and generation of policies.

View file

@ -0,0 +1,20 @@
<small>*[documentation](/README.md#documentation) / [Writing Policies](/documentation/writing-policies.md) / Auto-Generation for Pod Controllers*</small>
# Auto Generating Rules for Pod Controllers
Writing policies on pods helps address all pod creation flows. However, when pod cotrollers are used pod level policies result in errors not being reported when the pod controller object is created.
Kyverno solves this issue by supporting automatic generation of policy rules for pod controllers from a rule written for a pod.
This auto-generation behavior is controlled by the `pod-policies.kyverno.io/autogen-controllers` annotation.
By default, Kyverno inserts an annotation `pod-policies.kyverno.io/autogen-controllers=all`, to generate an additional rule that is applied to pod controllers: DaemonSet, Deployment, Job, StatefulSet.
You can change the annotation `pod-policies.kyverno.io/autogen-controllers` to customize the target pod controllers for the auto-generated rules. For example, Kyverno generates a rule for a `Deployment` if the annotation of policy is defined as `pod-policies.kyverno.io/autogen-controllers=Deployment`.
When a `name` or `labelSelector` is specified in the match / exclude block, Kyverno skips generating pod controllers rule as these filters may not be applicable to pod controllers.
To disable auto-generating rules for pod controllers set `pod-policies.kyverno.io/autogen-controllers` to the value `none`.
<small>*Read Next >> [Background Processing](/documentation/writing-policies-background.md)*</small>

View file

@ -0,0 +1,20 @@
<small>*[documentation](/README.md#documentation) / [Writing Policies](/documentation/writing-policies.md) / Background Processing*</small>
# Background processing
Kyverno applies policies during admission control and to existing resources in the cluster that may have been created before a policy was created. The application of policies to existing resources is referred to as `background` processing.
Note, that Kyverno does not mutate existing resources, and will only report policy violation for existing resources that do not match mutation, validation, or generation rules.
A policy is always enabled for processing during admission control. However, policy rules that rely on request information (e.g. `{{request.userInfo}}`) cannot be applied to existing resource in the `background` mode as the user information is not available outside of the admission controller. Hence, these rules must use the boolean flag `{spec.background}` to disable `background` processing.
```
spec:
background: true
rules:
- name: default-deny-ingress
```
The default value of `background` is `true`. When a policy is created or modified, the policy validation logic will report an error if a rule uses `userInfo` and does not set `background` to `false`.
<small>*Read Next >> [Testing Policies](/documentation/testing-policies.md)*</small>

View file

@ -1,12 +1,11 @@
<small>*[documentation](/README.md#documentation) / [Writing Policies](/documentation/writing-policies.md) / Generate*</small>
<small>*[documentation](/README.md#documentation) / [Writing Policies](/documentation/writing-policies.md) / Generate Resources*</small>
# Generate Configurations
# Generate Resources
```generate``` is used to create additional resources when a resource is created. This is useful to create supporting resources, such as role bindings for a new namespace.
## Example 1
- rule
Creates a ConfigMap with name `default-config` for all
````yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
@ -19,28 +18,22 @@ spec:
resources:
kinds:
- Namespace
selector:
matchLabels:
LabelForSelector : "namespace2"
generate:
kind: ConfigMap # Kind of resource
name: default-config # Name of the new Resource
namespace: "{{request.object.metadata.name}}" # Create in the namespace that triggers this rule
namespace: "{{request.object.metadata.name}}" # namespace that triggers this rule
clone:
namespace: default
name: config-template
- name: "Generate Secret"
- name: "Generate Secret (insecure)"
match:
resources:
kinds:
- Namespace
selector:
matchLabels:
LabelForSelector : "namespace2"
generate:
kind: Secret
name: mongo-creds
namespace: "{{request.object.metadata.name}}" # Create in the namespace that triggers this rule
namespace: "{{request.object.metadata.name}}" # namespace that triggers this rule
data:
data:
DB_USER: YWJyYWthZGFicmE=
@ -50,9 +43,9 @@ spec:
purpose: mongo
````
In this example, when this policy is applied, any new namespace that satisfies the label selector will receive 2 new resources after its creation:
* ConfigMap copied from default/config-template.
* Secret with values DB_USER and DB_PASSWORD, and label ```purpose: mongo```.
In this example new namespaces will receive 2 new resources after its creation:
* A ConfigMap cloned from default/config-template.
* A Secret with values DB_USER and DB_PASSWORD, and label ```purpose: mongo```.
## Example 2
@ -72,7 +65,7 @@ spec:
generate:
kind: NetworkPolicy
name: deny-all-traffic
namespace: "{{request.object.metadata.name}}" # Create in the namespace that triggers this rule
namespace: "{{request.object.metadata.name}}" # namespace that triggers this rule
data:
spec:
podSelector:
@ -84,8 +77,9 @@ spec:
policyname: "default"
````
In this example, when the policy is applied, any new namespace will receive a NetworkPolicy based on the specified template that by default denies all inbound and outbound traffic.
In this example new namespaces will receive a NetworkPolicy that default denies all inbound and outbound traffic.
---
<small>*Read Next >> [Testing Policies](/documentation/testing-policies.md)*</small>
<small>*Read Next >> [Variables](/documentation/writing-policies-variables.md)*</small>

View file

@ -1,8 +1,8 @@
<small>*[documentation](/README.md#documentation) / [Writing Policies](/documentation/writing-policies.md) / Mutate*</small>
<small>*[documentation](/README.md#documentation) / [Writing Policies](/documentation/writing-policies.md) / Mutate Resources*</small>
# Mutate Configurations
# Mutate Resources
The ```mutate``` rule contains actions that will be applied to matching resources. A mutate rule can be written as a JSON Patch or as an overlay.
The ```mutate``` rule can be used to add, replace, or delete elements in matching resources. A mutate rule can be written as a JSON Patch or as an overlay.
By using a ```patch``` in the (JSONPatch - RFC 6902)[http://jsonpatch.com/] format, you can make precise changes to the resource being created. Using an ```overlay``` is convenient for describing the desired state of the resource.
@ -213,4 +213,4 @@ The anchor processing behavior for mutate conditions is as follows:
Additional details on mutation overlay behaviors are available on the wiki: [Mutation Overlay](https://github.com/nirmata/kyverno/wiki/Mutation-Overlay)
---
<small>*Read Next >> [Generate](/documentation/writing-policies-generate.md)*</small>
<small>*Read Next >> [Generate Resources](/documentation/writing-policies-generate.md)*</small>

View file

@ -0,0 +1,30 @@
<small>*[documentation](/README.md#documentation) / [Writing Policies](/documentation/writing-policies.md) / Preconditions*</small>
# Preconditions
Preconditions allow controlling policy rule execution based on variable values.
While `match` & `exclude` conditions allow filtering requests based on resource and user information, `preconditions` can be used to define custom filters for more granular control.
The following operators are currently supported for preconditon evaluation:
- Equal
- NotEqual
## Example
```yaml
- name: generate-owner-role
match:
resources:
kinds:
- Namespace
preconditions:
- key: "{{serviceAccountName}}"
operator: NotEqual
value: ""
```
In the above example, the rule is only applied to requests from service accounts i.e. when the `{{serviceAccountName}}` is not empty.
<small>*Read Next >> [Auto-Generation for Pod Controllers](/documentation/writing-policies-autogen.md)*</small>

View file

@ -1,7 +1,7 @@
<small>*[documentation](/README.md#documentation) / [Writing Policies](/documentation/writing-policies.md) / Validate*</small>
<small>*[documentation](/README.md#documentation) / [Writing Policies](/documentation/writing-policies.md) / Validate Resources*</small>
# Validate Configurations
# Validate Resources
A validation rule is expressed as an overlay pattern that expresses the desired configuration. Resource configurations must match fields and expressions defined in the pattern to pass the validation rule. The following rules are followed when processing the overlay pattern:
@ -191,4 +191,4 @@ Additional examples are available in [samples](/samples/README.md)
The `validationFailureAction` attribute controls processing behaviors when the resource is not compliant with the policy. If the value is set to `enforce` resource creation or updates are blocked when the resource does not comply, and when the value is set to `audit` a policy violation is reported but the resource creation or update is allowed.
---
<small>*Read Next >> [Generate](/documentation/writing-policies-mutate.md)*</small>
<small>*Read Next >> [Mutate Resources](/documentation/writing-policies-mutate.md)*</small>

View file

@ -0,0 +1,35 @@
<small>*[documentation](/README.md#documentation) / [Writing Policies](/documentation/writing-policies.md) / Variables*</small>
# Variables
Sometimes it is necessary to vary the contents of a mutated or generated resource based on request data. To achieve this, variables can be used to reference attributes that are loaded in the rule processing context using a [JMESPATH](http://jmespath.org/) notation.
The policy engine will substitute any values with the format `{{<JMESPATH>}}` with the variable value before processing the rule.
The following data is available for use in context:
- Resource: `{{request.object}}`
- UserInfo: `{{request.userInfo}}`
## Pre-defined Variables
Kyverno automatically creates a few useful variables:
- `serviceAccountName` : the last part of a service account i.e. without the suffix `system:serviceaccount:<namespace>:` and stores the userName. For example, when processing a request from `system:serviceaccount:nirmata:user1` Kyverno will store the value `user1` in the variable `serviceAccountName`.
- `serviceAccountNamespace` : the `namespace` portion of the serviceAccount. For example, when processing a request from `system:serviceaccount:nirmata:user1` Kyverno will store `nirmata` in the variable `serviceAccountNamespace`.
## Examples
1. Reference a resource name (type string)
`{{request.object.metadata.name}}`
2. Build name from multiple variables (type string)
`"ns-owner-{{request.object.metadata.namespace}}-{{request.userInfo.username}}-binding"`
3. Reference the metadata (type object)
`{{request.object.metadata}}`
<small>*Read Next >> [Preconditions](/documentation/writing-policies-preconditions.md)*</small>

View file

@ -6,11 +6,17 @@ The following picture shows the structure of a Kyverno Policy:
![KyvernoPolicy](images/Kyverno-Policy-Structure.png)
Each Kyverno policy contains one or more rules. Each rule has a match clause, an optional excludes clause, and a mutate, validate, or generate clause.
Each Kyverno policy contains one or more rules. Each rule has a `match` clause, an optional `exclude` clause, and one of a `mutate`, `validate`, or `generate` clause.
The match / exclude clauses have the same structure, and can contain the following elements:
* resources: select resources by name, namespaces, kinds, and label selectors.
* subjects: select users, user groups, and service accounts
* roles: select namespaced roles
* clusterroles: select cluster wide roles
When Kyverno receives an admission controller request, i.e. a validation or mutation webhook, it first checks to see if the resource and user information matches or should be excluded from processing. If both checks pass, then the rule logic to mutate, validate, or generate resources is applied.
The following YAML provides an example for the match and validate clauses.
The following YAML provides an example for a match clause.
````yaml
apiVersion : kyverno.io/v1
@ -31,11 +37,11 @@ spec :
kinds: # Required, list of kinds
- Deployment
- StatefulSet
name: "mongo*" # Optional, a resource name is optional. Name supports wildcards * and ?
namespaces: # Optional, list of namespaces. Supports wilcards * and ?
name: "mongo*" # Optional, a resource name is optional. Name supports wildcards (* and ?)
namespaces: # Optional, list of namespaces. Supports wildcards (* and ?)
- "dev*"
- test
selector: # Optional, a resource selector is optional. Selector values support wildcards * and ?
selector: # Optional, a resource selector is optional. Values support wildcards (* and ?)
matchLabels:
app: mongodb
matchExpressions:
@ -47,116 +53,14 @@ spec :
# Optional, roles to be matched
roles:
# Optional, clusterroles to be matched
clusterroles:
# Resources that need to be excluded
exclude: # Optional, resources to be excluded from evaulation
resources:
kinds:
- Daemonsets
name: "*"
namespaces:
- prod
- "kube*"
selector:
matchLabels:
app: mongodb
matchExpressions:
- {key: tier, operator: In, values: [database]}
# Optional, subjects to be excluded
subjects:
# Optional, roles to be excluded
roles:
# Optional, clusterroles to be excluded
clusterroles:
- cluster-admin
- admin
# rule is evaluated if the preconditions are satisfied
# all preconditions are AND/&& operation
preconditions:
- key: name # compares (key operator value)
operator: Equal
value: name # constant "name" == "name"
- key: "{{serviceAccountName}}" # refer to a pre-defined variable serviceAccountName
operator: NotEqual
value: "user1" # if service
# Each rule can contain a single validate, mutate, or generate directive
...
clusterroles: cluster-admin
...
````
Each rule can validate, mutate, or generate configurations of matching resources. A rule definition can contain only a single **mutate**, **validate**, or **generate** child node. These actions are applied to the resource in described order: mutation, validation and then generation.
# Variables:
Variables can be used to reference attributes that are loaded in the context using a [JMESPATH](http://jmespath.org/) search path.
Format: `{{<JMESPATH>}}`
Resources available in context:
- Resource: `{{request.object}}`
- UserInfo: `{{request.userInfo}}`
## Pre-defined Variables
- `serviceAccountName` : the variable removes the suffix system:serviceaccount:<namespace>: and stores the userName.
Example userName=`system:serviceaccount:nirmata:user1` will store variable value as `user1`.
- `serviceAccountNamespace` : extracts the `namespace` of the serviceAccount.
Example userName=`system:serviceaccount:nirmata:user1` will store variable value as `nirmata`.
Examples:
1. Refer to resource name(type string)
`{{request.object.metadata.name}}`
2. Build name from multiple variables(type string)
`"ns-owner-{{request.object.metadata.namespace}}-{{request.userInfo.username}}-binding"`
3. Refer to metadata struct/object(type object)
`{{request.object.metadata}}`
# PreConditions:
Apart from using `match` & `exclude` conditions on resource to filter which resources to apply the rule on, `preconditions` can be used to define custom filters.
```yaml
- name: generate-owner-role
match:
resources:
kinds:
- Namespace
preconditions:
- key: "{{request.userInfo.username}}"
operator: NotEqual
value: ""
```
In the above example, if the variable `{{request.userInfo.username}}` is blank then we dont apply the rule on resource.
Operators supported:
- Equal
- NotEqual
# Background processing
Kyverno applies policies in foreground and background mode.
- `foreground`: leverages admission control webhooks to intercept, and apply policies on, API requests for resource changes.
- `background`: policy-controller applies policies on the existing resoruces after configured re-conciliation time.
A policy is always enabled for `foreground` processing, but `background` processing is configurable using a boolean flag at `{spec.background}`.
```
spec:
background: true
rules:
- name: default-deny-ingress
```
- Unless specified the default value is `true`
- As the userInformation is only avaiable in the incoming api-request, a policy using userInfo filters and variables reffering to `{{request.userInfo}}` can only be processed in foreground mode.
- When a new policy is created, the policy validation will throw an error if using `userInfo` with a policy defined in background mode.
# Auto generating rules for pod controllers
Writing policies on pods helps address all pod creation flows, but results in errors not being reported when a pod controller object is created. Kyverno solves this issue, by automatically generating rules for pod controllers from a rule written for a pod.
This behavior is controlled by the pod-policies.kyverno.io/autogen-controllers annotation. By default, Kyverno inserts an annotation `pod-policies.kyverno.io/autogen-controllers=all`, to generate an additional rule that is applied to pod controllers: DaemonSet, Deployment, Job, StatefulSet.
Change the annotation `pod-policies.kyverno.io/autogen-controllers` to customize the applicable pod controllers of the auto-gen rule. For example, Kyverno generates the rule for `Deployment` if the annotation of policy is defined as `pod-policies.kyverno.io/autogen-controllers=Deployment`. If `name` or `labelSelector` is specified in the match / exclude block, Kyverno skips generating pod controllers rule as these filters may not be applicable to pod controllers.
To disable auto-generating rules for pod controllers, set `pod-policies.kyverno.io/autogen-controllers=none`.
---
<small>*Read Next >> [Validate](/documentation/writing-policies-validate.md)*</small>
<small>*Read Next >> [Validate Resources](/documentation/writing-policies-validate.md)*</small>

View file

@ -23,9 +23,9 @@ const (
//MutatingWebhookName default resource mutating webhook name
MutatingWebhookName = "nirmata.kyverno.resource.mutating-webhook"
// ValidatingWebhookConfigurationName = "kyverno-validating-webhook-cfg"
// ValidatingWebhookConfigurationDebug = "kyverno-validating-webhook-cfg-debug"
// ValidatingWebhookName = "nirmata.kyverno.policy-validating-webhook"
ValidatingWebhookConfigurationName = "kyverno-resource-validating-webhook-cfg"
ValidatingWebhookConfigurationDebugName = "kyverno-resource-validating-webhook-cfg-debug"
ValidatingWebhookName = "nirmata.kyverno.resource.validating-webhook"
//VerifyMutatingWebhookConfigurationName default verify mutating webhook configuration name
VerifyMutatingWebhookConfigurationName = "kyverno-verify-mutating-webhook-cfg"

View file

@ -109,8 +109,9 @@ func (nspv *namespacedPV) updatePV(newPv, oldPv *kyverno.PolicyViolation) error
// update resource
_, err = nspv.kyvernoInterface.PolicyViolations(newPv.GetNamespace()).Update(newPv)
if err != nil {
return fmt.Errorf("failed to update namespaced polciy violation: %v", err)
return fmt.Errorf("failed to update namespaced policy violation: %v", err)
}
glog.Infof("namespaced policy violation updated for resource %v", newPv.Spec.ResourceSpec)
return nil
}

View file

@ -86,7 +86,7 @@ func (wrc *WebhookRegistrationClient) RemoveWebhookConfigurations(cleanUp chan<-
//CreateResourceMutatingWebhookConfiguration create a Mutatingwebhookconfiguration resource for all resource type
// used to forward request to kyverno webhooks to apply policeis
// Mutationg webhook is be used for Mutating & Validating purpose
// Mutationg webhook is be used for Mutating purpose
func (wrc *WebhookRegistrationClient) CreateResourceMutatingWebhookConfiguration() error {
var caData []byte
var config *admregapi.MutatingWebhookConfiguration
@ -101,7 +101,7 @@ func (wrc *WebhookRegistrationClient) CreateResourceMutatingWebhookConfiguration
if wrc.serverIP != "" {
// debug mode
// clientConfig - URL
config = wrc.contructDebugMutatingWebhookConfig(caData)
config = wrc.constructDebugMutatingWebhookConfig(caData)
} else {
// clientConfig - service
config = wrc.constructMutatingWebhookConfig(caData)
@ -118,6 +118,35 @@ func (wrc *WebhookRegistrationClient) CreateResourceMutatingWebhookConfiguration
return nil
}
func (wrc *WebhookRegistrationClient) CreateResourceValidatingWebhookConfiguration() error {
var caData []byte
var config *admregapi.ValidatingWebhookConfiguration
if caData = wrc.readCaData(); caData == nil {
return errors.New("Unable to extract CA data from configuration")
}
// if serverIP is specified we assume its debug mode
if wrc.serverIP != "" {
// debug mode
// clientConfig - URL
config = wrc.constructDebugValidatingWebhookConfig(caData)
} else {
// clientConfig - service
config = wrc.constructValidatingWebhookConfig(caData)
}
_, err := wrc.client.CreateResource(ValidatingWebhookConfigurationKind, "", *config, false)
if errorsapi.IsAlreadyExists(err) {
glog.V(4).Infof("resource validating webhook configuration %s, already exists. not creating one", config.Name)
return nil
}
if err != nil {
glog.V(4).Infof("failed to create resource validating webhook configuration %s: %v", config.Name, err)
return err
}
return nil
}
//registerPolicyValidatingWebhookConfiguration create a Validating webhook configuration for Policy CRD
func (wrc *WebhookRegistrationClient) createPolicyValidatingWebhookConfiguration() error {
var caData []byte
@ -221,9 +250,10 @@ func (wrc *WebhookRegistrationClient) removeWebhookConfigurations() {
var wg sync.WaitGroup
wg.Add(4)
wg.Add(5)
// mutating and validating webhook configuration for Kubernetes resources
go wrc.removeResourceMutatingWebhookConfiguration(&wg)
go wrc.removeResourceValidatingWebhookConfiguration(&wg)
// mutating and validating webhook configurtion for Policy CRD resource
go wrc.removePolicyMutatingWebhookConfiguration(&wg)
go wrc.removePolicyValidatingWebhookConfiguration(&wg)
@ -242,6 +272,12 @@ func (wrc *WebhookRegistrationClient) removeResourceMutatingWebhookConfiguration
glog.Error(err)
}
}
func (wrc *WebhookRegistrationClient) removeResourceValidatingWebhookConfiguration(wg *sync.WaitGroup) {
defer wg.Done()
if err := wrc.RemoveResourceValidatingWebhookConfiguration(); err != nil {
glog.Error(err)
}
}
// delete policy mutating webhookconfigurations
// handle wait group

View file

@ -10,7 +10,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (wrc *WebhookRegistrationClient) contructDebugMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
func (wrc *WebhookRegistrationClient) constructDebugMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.MutatingWebhookServicePath)
glog.V(4).Infof("Debug MutatingWebhookConfig is registered with url %s\n", url)
@ -83,3 +83,73 @@ func (wrc *WebhookRegistrationClient) RemoveResourceMutatingWebhookConfiguration
glog.V(4).Infof("deleted resource webhook configuration %s", configName)
return nil
}
func (wrc *WebhookRegistrationClient) constructDebugValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.ValidatingWebhookServicePath)
glog.V(4).Infof("Debug ValidatingWebhookConfig is registered with url %s\n", url)
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.ValidatingWebhookConfigurationDebugName,
},
Webhooks: []admregapi.Webhook{
generateDebugWebhook(
config.ValidatingWebhookName,
url,
caData,
true,
wrc.timeoutSeconds,
"*/*",
"*",
"*",
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
),
},
}
}
func (wrc *WebhookRegistrationClient) constructValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.ValidatingWebhookConfigurationName,
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
Webhooks: []admregapi.Webhook{
generateWebhook(
config.ValidatingWebhookName,
config.ValidatingWebhookServicePath,
caData,
false,
wrc.timeoutSeconds,
"*/*",
"*",
"*",
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
),
},
}
}
func (wrc *WebhookRegistrationClient) GetResourceValidatingWebhookConfigName() string {
if wrc.serverIP != "" {
return config.ValidatingWebhookConfigurationDebugName
}
return config.ValidatingWebhookConfigurationName
}
func (wrc *WebhookRegistrationClient) RemoveResourceValidatingWebhookConfiguration() error {
configName := wrc.GetResourceValidatingWebhookConfigName()
err := wrc.client.DeleteResource(ValidatingWebhookConfigurationKind, "", configName, false)
if errors.IsNotFound(err) {
glog.V(4).Infof("resource webhook configuration %s does not exits, so not deleting", configName)
return nil
}
if err != nil {
glog.V(4).Infof("failed to delete resource webhook configuration %s: %v", configName, err)
return err
}
glog.V(4).Infof("deleted resource webhook configuration %s", configName)
return nil
}

View file

@ -17,23 +17,31 @@ type ResourceWebhookRegister struct {
pendingCreation *abool.AtomicBool
LastReqTime *checker.LastReqTime
mwebhookconfigSynced cache.InformerSynced
vwebhookconfigSynced cache.InformerSynced
// list/get mutatingwebhookconfigurations
mWebhookConfigLister mconfiglister.MutatingWebhookConfigurationLister
webhookRegistrationClient *WebhookRegistrationClient
mWebhookConfigLister mconfiglister.MutatingWebhookConfigurationLister
vWebhookConfigLister mconfiglister.ValidatingWebhookConfigurationLister
webhookRegistrationClient *WebhookRegistrationClient
RunValidationInMutatingWebhook string
}
// NewResourceWebhookRegister returns a new instance of ResourceWebhookRegister manager
func NewResourceWebhookRegister(
lastReqTime *checker.LastReqTime,
mconfigwebhookinformer mconfiginformer.MutatingWebhookConfigurationInformer,
vconfigwebhookinformer mconfiginformer.ValidatingWebhookConfigurationInformer,
webhookRegistrationClient *WebhookRegistrationClient,
runValidationInMutatingWebhook string,
) *ResourceWebhookRegister {
return &ResourceWebhookRegister{
pendingCreation: abool.New(),
LastReqTime: lastReqTime,
mwebhookconfigSynced: mconfigwebhookinformer.Informer().HasSynced,
mWebhookConfigLister: mconfigwebhookinformer.Lister(),
webhookRegistrationClient: webhookRegistrationClient,
pendingCreation: abool.New(),
LastReqTime: lastReqTime,
mwebhookconfigSynced: mconfigwebhookinformer.Informer().HasSynced,
mWebhookConfigLister: mconfigwebhookinformer.Lister(),
vwebhookconfigSynced: vconfigwebhookinformer.Informer().HasSynced,
vWebhookConfigLister: vconfigwebhookinformer.Lister(),
webhookRegistrationClient: webhookRegistrationClient,
RunValidationInMutatingWebhook: runValidationInMutatingWebhook,
}
}
@ -45,62 +53,86 @@ func (rww *ResourceWebhookRegister) RegisterResourceWebhook() {
return
}
// check cache
configName := rww.webhookRegistrationClient.GetResourceMutatingWebhookConfigName()
// exsitence of config is all that matters; if error occurs, creates webhook anyway
// errors of webhook creation are handled separately
config, _ := rww.mWebhookConfigLister.Get(configName)
if config != nil {
glog.V(4).Info("mutating webhoook configuration already exists, skip the request")
return
}
createWebhook := func() {
rww.pendingCreation.Set()
err := rww.webhookRegistrationClient.CreateResourceMutatingWebhookConfiguration()
rww.pendingCreation.UnSet()
if err != nil {
glog.Errorf("failed to create resource mutating webhook configuration: %v, re-queue creation request", err)
rww.RegisterResourceWebhook()
return
}
glog.V(3).Info("Successfully created mutating webhook configuration for resources")
}
timeDiff := time.Since(rww.LastReqTime.Time())
if timeDiff < checker.DefaultDeadline {
glog.V(3).Info("Verified webhook status, creating webhook configuration")
go createWebhook()
go func() {
mutatingConfigName := rww.webhookRegistrationClient.GetResourceMutatingWebhookConfigName()
mutatingConfig, _ := rww.mWebhookConfigLister.Get(mutatingConfigName)
if mutatingConfig != nil {
glog.V(4).Info("mutating webhoook configuration already exists")
} else {
rww.pendingCreation.Set()
err1 := rww.webhookRegistrationClient.CreateResourceMutatingWebhookConfiguration()
rww.pendingCreation.UnSet()
if err1 != nil {
glog.Errorf("failed to create resource mutating webhook configuration: %v, re-queue creation request", err1)
rww.RegisterResourceWebhook()
return
}
glog.V(3).Info("Successfully created mutating webhook configuration for resources")
}
if rww.RunValidationInMutatingWebhook != "true" {
validatingConfigName := rww.webhookRegistrationClient.GetResourceValidatingWebhookConfigName()
validatingConfig, _ := rww.vWebhookConfigLister.Get(validatingConfigName)
if validatingConfig != nil {
glog.V(4).Info("validating webhoook configuration already exists")
} else {
rww.pendingCreation.Set()
err2 := rww.webhookRegistrationClient.CreateResourceValidatingWebhookConfiguration()
rww.pendingCreation.UnSet()
if err2 != nil {
glog.Errorf("failed to create resource validating webhook configuration: %v, re-queue creation request", err2)
rww.RegisterResourceWebhook()
return
}
glog.V(3).Info("Successfully created validating webhook configuration for resources")
}
}
}()
}
}
//Run starts the ResourceWebhookRegister manager
func (rww *ResourceWebhookRegister) Run(stopCh <-chan struct{}) {
// wait for cache to populate first time
if !cache.WaitForCacheSync(stopCh, rww.mwebhookconfigSynced) {
if !cache.WaitForCacheSync(stopCh, rww.mwebhookconfigSynced, rww.vwebhookconfigSynced) {
glog.Error("configuration: failed to sync webhook informer cache")
}
}
// RemoveResourceWebhookConfiguration removes the resource webhook configurations
func (rww *ResourceWebhookRegister) RemoveResourceWebhookConfiguration() error {
var err error
// check informer cache
configName := rww.webhookRegistrationClient.GetResourceMutatingWebhookConfigName()
config, err := rww.mWebhookConfigLister.Get(configName)
mutatingConfigName := rww.webhookRegistrationClient.GetResourceMutatingWebhookConfigName()
mutatingConfig, err := rww.mWebhookConfigLister.Get(mutatingConfigName)
if err != nil {
glog.V(4).Infof("failed to list mutating webhook config: %v", err)
return err
}
if config == nil {
// as no resource is found
return nil
if mutatingConfig != nil {
err = rww.webhookRegistrationClient.RemoveResourceMutatingWebhookConfiguration()
if err != nil {
return err
}
glog.V(3).Info("removed mutating resource webhook configuration")
}
err = rww.webhookRegistrationClient.RemoveResourceMutatingWebhookConfiguration()
if err != nil {
return err
if rww.RunValidationInMutatingWebhook != "true" {
validatingConfigName := rww.webhookRegistrationClient.GetResourceValidatingWebhookConfigName()
validatingConfig, err := rww.vWebhookConfigLister.Get(validatingConfigName)
if err != nil {
glog.V(4).Infof("failed to list validating webhook config: %v", err)
return err
}
if validatingConfig != nil {
err = rww.webhookRegistrationClient.RemoveResourceValidatingWebhookConfiguration()
if err != nil {
return err
}
glog.V(3).Info("removed validating resource webhook configuration")
}
}
glog.V(3).Info("removed resource webhook configuration")
return nil
}

View file

@ -2,6 +2,9 @@ package webhooks
import (
"encoding/json"
"strings"
yamlv2 "gopkg.in/yaml.v2"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
@ -11,14 +14,9 @@ import (
)
const (
policyAnnotation = "policies.kyverno.patches"
policyAnnotation = "policies.kyverno.io~1patches"
)
type policyPatch struct {
PolicyName string `json:"policyname"`
RulePatches interface{} `json:"patches"`
}
type rulePatch struct {
RuleName string `json:"rulename"`
Op string `json:"op"`
@ -31,6 +29,15 @@ type annresponse struct {
Value interface{} `json:"value"`
}
var operationToPastTense = map[string]string{
"add": "added",
"remove": "removed",
"replace": "replaced",
"move": "moved",
"copy": "copied",
"test": "tested",
}
func generateAnnotationPatches(engineResponses []response.EngineResponse) []byte {
var annotations map[string]string
@ -52,7 +59,7 @@ func generateAnnotationPatches(engineResponses []response.EngineResponse) []byte
return nil
}
if _, ok := annotations[policyAnnotation]; ok {
if _, ok := annotations[strings.ReplaceAll(policyAnnotation, "~1", "/")]; ok {
// create update patch string
patchResponse = annresponse{
Op: "replace",
@ -69,7 +76,7 @@ func generateAnnotationPatches(engineResponses []response.EngineResponse) []byte
}
} else {
// insert 'policies.kyverno.patches' entry in annotation map
annotations[policyAnnotation] = string(value)
annotations[strings.ReplaceAll(policyAnnotation, "~1", "/")] = string(value)
patchResponse = annresponse{
Op: "add",
Path: "/metadata/annotations",
@ -90,31 +97,31 @@ func generateAnnotationPatches(engineResponses []response.EngineResponse) []byte
}
func annotationFromEngineResponses(engineResponses []response.EngineResponse) []byte {
var policyPatches []policyPatch
var annotationContent = make(map[string]string)
for _, engineResponse := range engineResponses {
if !engineResponse.IsSuccesful() {
glog.V(3).Infof("Policy %s failed, skip preparing annotation\n", engineResponse.PolicyResponse.Policy)
continue
}
var pp policyPatch
rulePatches := annotationFromPolicyResponse(engineResponse.PolicyResponse)
if rulePatches == nil {
continue
}
pp.RulePatches = rulePatches
pp.PolicyName = engineResponse.PolicyResponse.Policy
policyPatches = append(policyPatches, pp)
policyName := engineResponse.PolicyResponse.Policy
for _, rulePatch := range rulePatches {
annotationContent[rulePatch.RuleName+"."+policyName+".kyverno.io"] = operationToPastTense[rulePatch.Op] + " " + rulePatch.Path
}
}
// return nil if there's no patches
// otherwise result = null, len(result) = 4
if len(policyPatches) == 0 {
if len(annotationContent) == 0 {
return nil
}
result, _ := json.Marshal(policyPatches)
result, _ := yamlv2.Marshal(annotationContent)
return result
}

View file

@ -43,7 +43,7 @@ func Test_empty_annotation(t *testing.T) {
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true, nil)
annPatches := generateAnnotationPatches([]response.EngineResponse{engineResponse})
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.io/patches":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}}`
assert.Assert(t, string(annPatches) == expectedPatches)
}
@ -56,7 +56,7 @@ func Test_exist_annotation(t *testing.T) {
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true, annotation)
annPatches := generateAnnotationPatches([]response.EngineResponse{engineResponse})
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.io/patches":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}}`
assert.Assert(t, string(annPatches) == expectedPatches)
}
@ -69,7 +69,7 @@ func Test_exist_kyverno_annotation(t *testing.T) {
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true, annotation)
annPatches := generateAnnotationPatches([]response.EngineResponse{engineResponse})
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.patches":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.io/patches":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}}`
assert.Assert(t, string(annPatches) == expectedPatches)
}

View file

@ -127,6 +127,9 @@ func extractResources(newRaw []byte, request *v1beta1.AdmissionRequest) (unstruc
var emptyResource unstructured.Unstructured
// New Resource
if newRaw == nil {
newRaw = request.Object.Raw
}
if newRaw == nil {
return emptyResource, emptyResource, fmt.Errorf("new resource is not defined")
}

View file

@ -123,6 +123,7 @@ func NewWebhookServer(
}
mux := http.NewServeMux()
mux.HandleFunc(config.MutatingWebhookServicePath, ws.serve)
mux.HandleFunc(config.ValidatingWebhookServicePath, ws.serve)
mux.HandleFunc(config.VerifyMutatingWebhookServicePath, ws.serve)
mux.HandleFunc(config.PolicyValidatingWebhookServicePath, ws.serve)
mux.HandleFunc(config.PolicyMutatingWebhookServicePath, ws.serve)
@ -164,7 +165,11 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
admissionReview.Response = ws.handleVerifyRequest(request)
case config.MutatingWebhookServicePath:
if !ws.configHandler.ToFilter(request.Kind.Kind, request.Namespace, request.Name) {
admissionReview.Response = ws.handleAdmissionRequest(request)
admissionReview.Response = ws.handleMutateAdmissionRequest(request)
}
case config.ValidatingWebhookServicePath:
if !ws.configHandler.ToFilter(request.Kind.Kind, request.Namespace, request.Name) {
admissionReview.Response = ws.handleValidateAdmissionRequest(request)
}
case config.PolicyValidatingWebhookServicePath:
if !ws.configHandler.ToFilter(request.Kind.Kind, request.Namespace, request.Name) {
@ -189,7 +194,7 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
}
}
func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
policies, err := ws.pMetaStore.LookUp(request.Kind.Kind, request.Namespace)
if err != nil {
// Unable to connect to policy Lister to access policies
@ -242,16 +247,18 @@ func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionReques
// patch the resource with patches before handling validation rules
patchedResource := processResourceWithPatches(patches, request.Object.Raw)
// VALIDATION
ok, msg := ws.HandleValidation(request, policies, patchedResource, roles, clusterRoles)
if !ok {
glog.V(4).Infof("Deny admission request: %v/%s/%s", request.Kind, request.Namespace, request.Name)
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: msg,
},
if ws.resourceWebhookWatcher != nil && ws.resourceWebhookWatcher.RunValidationInMutatingWebhook == "true" {
// VALIDATION
ok, msg := ws.HandleValidation(request, policies, patchedResource, roles, clusterRoles)
if !ok {
glog.V(4).Infof("Deny admission request: %v/%s/%s", request.Kind, request.Namespace, request.Name)
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: msg,
},
}
}
}
@ -260,7 +267,7 @@ func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionReques
// Success -> Generate Request CR created successsfully
// Failed -> Failed to create Generate Request CR
if request.Operation == v1beta1.Create {
ok, msg = ws.HandleGenerate(request, policies, patchedResource, roles, clusterRoles)
ok, msg := ws.HandleGenerate(request, policies, patchedResource, roles, clusterRoles)
if !ok {
glog.V(4).Infof("Deny admission request: %v/%s/%s", request.Kind, request.Namespace, request.Name)
return &v1beta1.AdmissionResponse{
@ -284,6 +291,49 @@ func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionReques
}
}
func (ws *WebhookServer) handleValidateAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
policies, err := ws.pMetaStore.LookUp(request.Kind.Kind, request.Namespace)
if err != nil {
// Unable to connect to policy Lister to access policies
glog.Errorf("Unable to connect to policy controller to access policies. Policies are NOT being applied: %v", err)
return &v1beta1.AdmissionResponse{Allowed: true}
}
var roles, clusterRoles []string
// getRoleRef only if policy has roles/clusterroles defined
startTime := time.Now()
if containRBACinfo(policies) {
roles, clusterRoles, err = userinfo.GetRoleRef(ws.rbLister, ws.crbLister, request)
if err != nil {
// TODO(shuting): continue apply policy if error getting roleRef?
glog.Errorf("Unable to get rbac information for request Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s: %v",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation, err)
}
}
glog.V(4).Infof("Time: webhook GetRoleRef %v", time.Since(startTime))
// VALIDATION
ok, msg := ws.HandleValidation(request, policies, nil, roles, clusterRoles)
if !ok {
glog.V(4).Infof("Deny admission request: %v/%s/%s", request.Kind, request.Namespace, request.Name)
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: msg,
},
}
}
return &v1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{
Status: "Success",
},
}
}
// RunAsync TLS server in separate thread and returns control immediately
func (ws *WebhookServer) RunAsync(stopCh <-chan struct{}) {
if !cache.WaitForCacheSync(stopCh, ws.pSynced, ws.rbSynced, ws.crbSynced) {