mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-06 07:57:07 +00:00
Merge branch 'main' into test_cli
This commit is contained in:
commit
27f9b4747a
29 changed files with 875 additions and 119 deletions
|
@ -68,9 +68,11 @@ Parameter | Description | Default
|
|||
`createSelfSignedCert` | generate a self signed cert and certificate authority. Kyverno defaults to using kube-controller-manager CA-signed certificate or existing cert secret if false. | `false`
|
||||
`config.existingConfig` | existing Kubernetes configmap to use for the resource filters configuration | `nil`
|
||||
`config.resourceFilters` | list of filter of resource types to be skipped by kyverno policy engine. See [documentation](https://github.com/kyverno/kyverno/blob/master/documentation/installation.md#filter-kubernetes-resources-that-admission-webhook-should-not-process) for details | `["[Event,*,*]","[*,kube-system,*]","[*,kube-public,*]","[*,kube-node-lease,*]","[Node,*,*]","[APIService,*,*]","[TokenReview,*,*]","[SubjectAccessReview,*,*]","[*,kyverno,*]"]`
|
||||
`dnsPolicy` | Sets the DNS Policy which determines the manner in which DNS resolution happens across the cluster. For further reference, see [the official docs](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy) | `ClusterFirst`
|
||||
`extraArgs` | list of extra arguments to give the binary | `[]`
|
||||
`fullnameOverride` | override the expanded name of the chart | `nil`
|
||||
`generatecontrollerExtraResources` | extra resource type Kyverno is allowed to generate | `[]`
|
||||
`hostNetwork` | Use the host network's namespace. Set it to `true` when dealing with a custom CNI over Amazon EKS | `false`
|
||||
`image.pullPolicy` | Image pull policy | `IfNotPresent`
|
||||
`image.pullSecrets` | Specify image pull secrets | `[]` (does not add image pull secrets to deployed pods)
|
||||
`image.repository` | Image repository | `ghcr.io/kyverno/kyverno`
|
||||
|
|
|
@ -50,17 +50,34 @@ spec:
|
|||
context:
|
||||
description: Context defines variables and data sources that can be used during rule execution.
|
||||
items:
|
||||
description: ContextEntry adds variables and data sources to a rule Context
|
||||
description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided.
|
||||
properties:
|
||||
apiCall:
|
||||
description: APICall is an API server request to retrieve data
|
||||
properties:
|
||||
jmesPath:
|
||||
description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response from the API server.
|
||||
type: string
|
||||
urlPath:
|
||||
description: URLPath is the URL path to be used in the HTTP GET request
|
||||
type: string
|
||||
required:
|
||||
- urlPath
|
||||
type: object
|
||||
configMap:
|
||||
description: ConfigMapReference refers to a ConfigMap
|
||||
description: ConfigMap is the ConfigMap reference.
|
||||
properties:
|
||||
name:
|
||||
description: Name is the ConfigMap name.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace is the ConfigMap namespace.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name is the variable name.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
|
@ -1152,17 +1169,34 @@ spec:
|
|||
context:
|
||||
description: Context defines variables and data sources that can be used during rule execution.
|
||||
items:
|
||||
description: ContextEntry adds variables and data sources to a rule Context
|
||||
description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided.
|
||||
properties:
|
||||
apiCall:
|
||||
description: APICall is an API server request to retrieve data
|
||||
properties:
|
||||
jmesPath:
|
||||
description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response from the API server.
|
||||
type: string
|
||||
urlPath:
|
||||
description: URLPath is the URL path to be used in the HTTP GET request
|
||||
type: string
|
||||
required:
|
||||
- urlPath
|
||||
type: object
|
||||
configMap:
|
||||
description: ConfigMapReference refers to a ConfigMap
|
||||
description: ConfigMap is the ConfigMap reference.
|
||||
properties:
|
||||
name:
|
||||
description: Name is the ConfigMap name.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace is the ConfigMap namespace.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name is the variable name.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
|
|
|
@ -37,6 +37,12 @@ spec:
|
|||
{{- if .Values.priorityClassName }}
|
||||
priorityClassName: {{ .Values.priorityClassName | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.hostNetwork }}
|
||||
hostNetwork: {{ .Values.hostNetwork }}
|
||||
{{- end }}
|
||||
{{- if .Values.dnsPolicy }}
|
||||
dnsPolicy: {{ .Values.dnsPolicy }}
|
||||
{{- end }}
|
||||
initContainers:
|
||||
- name: kyverno-pre
|
||||
image: {{ .Values.initImage.repository }}:{{ default .Chart.AppVersion (default .Values.image.tag .Values.initImage.tag) }}
|
||||
|
|
|
@ -42,6 +42,16 @@ affinity: {}
|
|||
nodeSelector: {}
|
||||
tolerations: []
|
||||
|
||||
# change hostNetwork to true when you want the kyverno's pod to share its host's network namespace
|
||||
# useful for situations like when you end up dealing with a custom CNI over Amazon EKS
|
||||
# update the 'dnsPolicy' accordingly as well to suit the host network mode
|
||||
hostNetwork: false
|
||||
|
||||
# dnsPolicy determines the manner in which DNS resolution happens in the cluster
|
||||
# in case of hostNetwork: true, usually, the dnsPolicy is suitable to be "ClusterFirstWithHostNet"
|
||||
# for further reference: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
|
||||
dnsPolicy: "ClusterFirst"
|
||||
|
||||
extraArgs: []
|
||||
# - --webhooktimeout=4
|
||||
|
||||
|
|
|
@ -277,6 +277,7 @@ func main() {
|
|||
log.Log.WithName("ValidateAuditHandler"),
|
||||
configData,
|
||||
rCache,
|
||||
client,
|
||||
)
|
||||
|
||||
// Configure certificates
|
||||
|
|
|
@ -67,17 +67,39 @@ spec:
|
|||
can be used during rule execution.
|
||||
items:
|
||||
description: ContextEntry adds variables and data sources
|
||||
to a rule Context
|
||||
to a rule Context. Either a ConfigMap reference or a APILookup
|
||||
must be provided.
|
||||
properties:
|
||||
apiCall:
|
||||
description: APICall is an API server request to retrieve
|
||||
data
|
||||
properties:
|
||||
jmesPath:
|
||||
description: JMESPath is an optional JSON Match Expression
|
||||
that can be used to transform the JSON response
|
||||
from the API server.
|
||||
type: string
|
||||
urlPath:
|
||||
description: URLPath is the URL path to be used in
|
||||
the HTTP GET request
|
||||
type: string
|
||||
required:
|
||||
- urlPath
|
||||
type: object
|
||||
configMap:
|
||||
description: ConfigMapReference refers to a ConfigMap
|
||||
description: ConfigMap is the ConfigMap reference.
|
||||
properties:
|
||||
name:
|
||||
description: Name is the ConfigMap name.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace is the ConfigMap namespace.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name is the variable name.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
|
|
|
@ -68,17 +68,39 @@ spec:
|
|||
can be used during rule execution.
|
||||
items:
|
||||
description: ContextEntry adds variables and data sources
|
||||
to a rule Context
|
||||
to a rule Context. Either a ConfigMap reference or a APILookup
|
||||
must be provided.
|
||||
properties:
|
||||
apiCall:
|
||||
description: APICall is an API server request to retrieve
|
||||
data
|
||||
properties:
|
||||
jmesPath:
|
||||
description: JMESPath is an optional JSON Match Expression
|
||||
that can be used to transform the JSON response
|
||||
from the API server.
|
||||
type: string
|
||||
urlPath:
|
||||
description: URLPath is the URL path to be used in
|
||||
the HTTP GET request
|
||||
type: string
|
||||
required:
|
||||
- urlPath
|
||||
type: object
|
||||
configMap:
|
||||
description: ConfigMapReference refers to a ConfigMap
|
||||
description: ConfigMap is the ConfigMap reference.
|
||||
properties:
|
||||
name:
|
||||
description: Name is the ConfigMap name.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace is the ConfigMap namespace.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name is the variable name.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
|
|
|
@ -55,17 +55,34 @@ spec:
|
|||
context:
|
||||
description: Context defines variables and data sources that can be used during rule execution.
|
||||
items:
|
||||
description: ContextEntry adds variables and data sources to a rule Context
|
||||
description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided.
|
||||
properties:
|
||||
apiCall:
|
||||
description: APICall is an API server request to retrieve data
|
||||
properties:
|
||||
jmesPath:
|
||||
description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response from the API server.
|
||||
type: string
|
||||
urlPath:
|
||||
description: URLPath is the URL path to be used in the HTTP GET request
|
||||
type: string
|
||||
required:
|
||||
- urlPath
|
||||
type: object
|
||||
configMap:
|
||||
description: ConfigMapReference refers to a ConfigMap
|
||||
description: ConfigMap is the ConfigMap reference.
|
||||
properties:
|
||||
name:
|
||||
description: Name is the ConfigMap name.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace is the ConfigMap namespace.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name is the variable name.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
|
@ -1157,17 +1174,34 @@ spec:
|
|||
context:
|
||||
description: Context defines variables and data sources that can be used during rule execution.
|
||||
items:
|
||||
description: ContextEntry adds variables and data sources to a rule Context
|
||||
description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided.
|
||||
properties:
|
||||
apiCall:
|
||||
description: APICall is an API server request to retrieve data
|
||||
properties:
|
||||
jmesPath:
|
||||
description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response from the API server.
|
||||
type: string
|
||||
urlPath:
|
||||
description: URLPath is the URL path to be used in the HTTP GET request
|
||||
type: string
|
||||
required:
|
||||
- urlPath
|
||||
type: object
|
||||
configMap:
|
||||
description: ConfigMapReference refers to a ConfigMap
|
||||
description: ConfigMap is the ConfigMap reference.
|
||||
properties:
|
||||
name:
|
||||
description: Name is the ConfigMap name.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace is the ConfigMap namespace.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name is the variable name.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
|
|
|
@ -55,17 +55,34 @@ spec:
|
|||
context:
|
||||
description: Context defines variables and data sources that can be used during rule execution.
|
||||
items:
|
||||
description: ContextEntry adds variables and data sources to a rule Context
|
||||
description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided.
|
||||
properties:
|
||||
apiCall:
|
||||
description: APICall is an API server request to retrieve data
|
||||
properties:
|
||||
jmesPath:
|
||||
description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response from the API server.
|
||||
type: string
|
||||
urlPath:
|
||||
description: URLPath is the URL path to be used in the HTTP GET request
|
||||
type: string
|
||||
required:
|
||||
- urlPath
|
||||
type: object
|
||||
configMap:
|
||||
description: ConfigMapReference refers to a ConfigMap
|
||||
description: ConfigMap is the ConfigMap reference.
|
||||
properties:
|
||||
name:
|
||||
description: Name is the ConfigMap name.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace is the ConfigMap namespace.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name is the variable name.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
|
@ -1157,17 +1174,34 @@ spec:
|
|||
context:
|
||||
description: Context defines variables and data sources that can be used during rule execution.
|
||||
items:
|
||||
description: ContextEntry adds variables and data sources to a rule Context
|
||||
description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided.
|
||||
properties:
|
||||
apiCall:
|
||||
description: APICall is an API server request to retrieve data
|
||||
properties:
|
||||
jmesPath:
|
||||
description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response from the API server.
|
||||
type: string
|
||||
urlPath:
|
||||
description: URLPath is the URL path to be used in the HTTP GET request
|
||||
type: string
|
||||
required:
|
||||
- urlPath
|
||||
type: object
|
||||
configMap:
|
||||
description: ConfigMapReference refers to a ConfigMap
|
||||
description: ConfigMap is the ConfigMap reference.
|
||||
properties:
|
||||
name:
|
||||
description: Name is the ConfigMap name.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace is the ConfigMap namespace.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
name:
|
||||
description: Name is the variable name.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
|
|
|
@ -98,18 +98,43 @@ type Rule struct {
|
|||
Generation Generation `json:"generate,omitempty" yaml:"generate,omitempty"`
|
||||
}
|
||||
|
||||
// ContextEntry adds variables and data sources to a rule Context
|
||||
// ContextEntry adds variables and data sources to a rule Context. Either a
|
||||
// ConfigMap reference or a APILookup must be provided.
|
||||
type ContextEntry struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
|
||||
// Name is the variable name.
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
|
||||
// ConfigMap is the ConfigMap reference.
|
||||
ConfigMap *ConfigMapReference `json:"configMap,omitempty" yaml:"configMap,omitempty"`
|
||||
|
||||
// APICall is an API server request to retrieve data
|
||||
APICall *APICall `json:"apiCall,omitempty" yaml:"apiCall,omitempty"`
|
||||
}
|
||||
|
||||
// ConfigMapReference refers to a ConfigMap
|
||||
type ConfigMapReference struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
|
||||
// Name is the ConfigMap name.
|
||||
Name string `json:"name" yaml:"name"`
|
||||
|
||||
// Namespace is the ConfigMap namespace.
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// APICall contains an API server URL path used to perform an HTTP GET request
|
||||
// and an optional JMESPath to transform the retrieved data.
|
||||
type APICall struct {
|
||||
|
||||
// URLPath is the URL path to be used in the HTTP GET request
|
||||
URLPath string `json:"urlPath" yaml:"urlPath"`
|
||||
|
||||
// JMESPath is an optional JSON Match Expression that can be used to
|
||||
// transform the JSON response from the API server.
|
||||
// +optional
|
||||
JMESPath string `json:"jmesPath,omitempty" yaml:"jmesPath,omitempty"`
|
||||
}
|
||||
|
||||
// Condition defines variable-based conditional criteria for rule execution.
|
||||
type Condition struct {
|
||||
// Key is the context entry (using JMESPath) for conditional rule evaluation.
|
||||
|
|
|
@ -55,8 +55,13 @@ func NewClient(config *rest.Config, resync time.Duration, stopCh <-chan struct{}
|
|||
kclient: kclient,
|
||||
log: log.WithName("dclient"),
|
||||
}
|
||||
|
||||
// Set discovery client
|
||||
discoveryClient := ServerPreferredResources{cachedClient: memory.NewMemCacheClient(kclient.Discovery()), log: client.log}
|
||||
discoveryClient := &ServerPreferredResources{
|
||||
cachedClient: memory.NewMemCacheClient(kclient.Discovery()),
|
||||
log: client.log,
|
||||
}
|
||||
|
||||
// client will invalidate registered resources cache every x seconds,
|
||||
// As there is no way to identify if the registered resource is available or not
|
||||
// we will be invalidating the local cache, so the next request get a fresh cache
|
||||
|
@ -121,8 +126,8 @@ func (c *Client) getGroupVersionMapper(apiVersion string, kind string) schema.Gr
|
|||
gvr, _ := c.DiscoveryClient.GetGVRFromKind(kind)
|
||||
return gvr
|
||||
}
|
||||
return c.DiscoveryClient.GetGVRFromAPIVersionKind(apiVersion, kind)
|
||||
|
||||
return c.DiscoveryClient.GetGVRFromAPIVersionKind(apiVersion, kind)
|
||||
}
|
||||
|
||||
// GetResource returns the resource in unstructured/json format
|
||||
|
@ -347,15 +352,19 @@ func (c ServerPreferredResources) findResource(apiVersion string, kind string) (
|
|||
}
|
||||
|
||||
for _, serverResource := range serverResources {
|
||||
|
||||
if apiVersion != "" && serverResource.GroupVersion != apiVersion {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, resource := range serverResource.APIResources {
|
||||
if strings.Contains(resource.Name, "/") {
|
||||
// skip the sub-resources like deployment/status
|
||||
continue
|
||||
}
|
||||
|
||||
// skip the resource names with "/", to avoid comparison with subresources
|
||||
if resource.Kind == kind && !strings.Contains(resource.Name, "/") {
|
||||
// match kind or names (e.g. Namespace, namespaces, namespace)
|
||||
// to allow matching API paths (e.g. /api/v1/namespaces).
|
||||
if resource.Kind == kind || resource.Name == kind || resource.SingularName == kind {
|
||||
gv, err := schema.ParseGroupVersion(serverResource.GroupVersion)
|
||||
if err != nil {
|
||||
c.log.Error(err, "failed to parse groupVersion", "groupVersion", serverResource.GroupVersion)
|
||||
|
|
123
pkg/engine/apiPath.go
Normal file
123
pkg/engine/apiPath.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type APIPath struct {
|
||||
Root string
|
||||
Group string
|
||||
Version string
|
||||
ResourceType string
|
||||
Name string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// NewAPIPath validates and parses an API path.
|
||||
// See: https://kubernetes.io/docs/reference/using-api/api-concepts/
|
||||
func NewAPIPath(path string) (*APIPath, error) {
|
||||
trimmedPath := strings.Trim(path, "/ ")
|
||||
paths := strings.Split(trimmedPath, "/")
|
||||
|
||||
if len(paths) < 3 || len(paths) > 7 {
|
||||
return nil, fmt.Errorf("invalid path length %s", path)
|
||||
}
|
||||
|
||||
if paths[0] != "api" && paths[0] != "apis" {
|
||||
return nil, fmt.Errorf("urlPath must start with /api or /apis")
|
||||
}
|
||||
|
||||
if paths[0] == "api" && paths[1] != "v1" {
|
||||
return nil, fmt.Errorf("expected urlPath to start with /api/v1/")
|
||||
}
|
||||
|
||||
if paths[0] == "api" {
|
||||
if len(paths) == 3 {
|
||||
return &APIPath{
|
||||
Root: paths[0],
|
||||
Group: paths[1],
|
||||
ResourceType: paths[2],
|
||||
}, nil
|
||||
}
|
||||
|
||||
if len(paths) == 4 {
|
||||
return &APIPath{
|
||||
Root: paths[0],
|
||||
Group: paths[1],
|
||||
ResourceType: paths[2],
|
||||
Name: paths[3],
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid /api/v1 path %s", path)
|
||||
}
|
||||
|
||||
// /apis/GROUP/VERSION/RESOURCETYPE/
|
||||
if len(paths) == 4 {
|
||||
return &APIPath{
|
||||
Root: paths[0],
|
||||
Group: paths[1],
|
||||
Version: paths[2],
|
||||
ResourceType: paths[3],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// /apis/GROUP/VERSION/RESOURCETYPE/NAME
|
||||
if len(paths) == 5 {
|
||||
return &APIPath{
|
||||
Root: paths[0],
|
||||
Group: paths[1],
|
||||
Version: paths[2],
|
||||
ResourceType: paths[3],
|
||||
Name: paths[4],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE
|
||||
if len(paths) == 6 {
|
||||
return &APIPath{
|
||||
Root: paths[0],
|
||||
Group: paths[1],
|
||||
Version: paths[2],
|
||||
Namespace: paths[4],
|
||||
ResourceType: paths[5],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME
|
||||
if len(paths) == 7 {
|
||||
return &APIPath{
|
||||
Root: paths[0],
|
||||
Group: paths[1],
|
||||
Version: paths[2],
|
||||
Namespace: paths[4],
|
||||
ResourceType: paths[5],
|
||||
Name: paths[6],
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid /apis path %s", path)
|
||||
}
|
||||
|
||||
func (a *APIPath) String() string {
|
||||
var paths []string
|
||||
if a.Namespace != "" {
|
||||
if a.Name == "" {
|
||||
paths = []string{a.Root, a.Group, a.Version, a.ResourceType, "namespaces", a.Namespace}
|
||||
} else {
|
||||
paths = []string{a.Root, a.Group, a.Version, a.ResourceType, "namespaces", a.Namespace, a.Name}
|
||||
}
|
||||
} else {
|
||||
if a.Name != "" {
|
||||
paths = []string{a.Root, a.Group, a.Version, a.ResourceType, a.Name}
|
||||
} else {
|
||||
paths = []string{a.Root, a.Group, a.Version, a.ResourceType}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
result := "/" + strings.Join(paths, "/")
|
||||
result = strings.ReplaceAll(result, "//", "/")
|
||||
return result
|
||||
}
|
24
pkg/engine/apiPath_test.go
Normal file
24
pkg/engine/apiPath_test.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Paths(t *testing.T) {
|
||||
f := func(path, expected string) {
|
||||
p, err := NewAPIPath(path)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if p.String() != expected {
|
||||
t.Errorf("expected %s got %s", expected, p.String())
|
||||
}
|
||||
}
|
||||
|
||||
f("/api/v1/namespace/{{ request.namespace }}", "/api/v1/namespace/{{ request.namespace }}")
|
||||
f("/api/v1/namespace/{{ request.namespace }}/", "/api/v1/namespace/{{ request.namespace }}")
|
||||
f("/api/v1/namespace/{{ request.namespace }}/ ", "/api/v1/namespace/{{ request.namespace }}")
|
||||
f(" /api/v1/namespace/{{ request.namespace }}", "/api/v1/namespace/{{ request.namespace }}")
|
||||
}
|
|
@ -12,11 +12,11 @@ import (
|
|||
// 1. validate variables to be substitute in the general ruleInfo (match,exclude,condition)
|
||||
// - the caller has to check the ruleResponse to determine whether the path exist
|
||||
// 2. returns the list of rules that are applicable on this policy and resource, if 1 succeed
|
||||
func Generate(policyContext PolicyContext) (resp *response.EngineResponse) {
|
||||
func Generate(policyContext *PolicyContext) (resp *response.EngineResponse) {
|
||||
return filterRules(policyContext)
|
||||
}
|
||||
|
||||
func filterRules(policyContext PolicyContext) *response.EngineResponse {
|
||||
func filterRules(policyContext *PolicyContext) *response.EngineResponse {
|
||||
kind := policyContext.NewResource.GetKind()
|
||||
name := policyContext.NewResource.GetName()
|
||||
namespace := policyContext.NewResource.GetNamespace()
|
||||
|
@ -46,7 +46,7 @@ func filterRules(policyContext PolicyContext) *response.EngineResponse {
|
|||
return resp
|
||||
}
|
||||
|
||||
func filterRule(rule kyverno.Rule, policyContext PolicyContext) *response.RuleResponse {
|
||||
func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleResponse {
|
||||
if !rule.HasGenerate() {
|
||||
return nil
|
||||
}
|
||||
|
@ -59,7 +59,6 @@ func filterRule(rule kyverno.Rule, policyContext PolicyContext) *response.RuleRe
|
|||
admissionInfo := policyContext.AdmissionInfo
|
||||
ctx := policyContext.JSONContext
|
||||
resCache := policyContext.ResourceCache
|
||||
jsonContext := policyContext.JSONContext
|
||||
excludeGroupRole := policyContext.ExcludeGroupRole
|
||||
|
||||
logger := log.Log.WithName("Generate").WithValues("policy", policy.Name,
|
||||
|
@ -83,7 +82,7 @@ func filterRule(rule kyverno.Rule, policyContext PolicyContext) *response.RuleRe
|
|||
}
|
||||
|
||||
// add configmap json data to context
|
||||
if err := AddResourceToContext(logger, rule.Context, resCache, jsonContext); err != nil {
|
||||
if err := LoadContext(logger, rule.Context, resCache, policyContext); err != nil {
|
||||
logger.V(4).Info("cannot add configmaps to context", "reason", err.Error())
|
||||
return nil
|
||||
}
|
||||
|
|
190
pkg/engine/jsonContext.go
Normal file
190
pkg/engine/jsonContext.go
Normal file
|
@ -0,0 +1,190 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/jmespath/go-jmespath"
|
||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/resourcecache"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/dynamic/dynamiclister"
|
||||
)
|
||||
|
||||
// LoadContext - Fetches and adds external data to the Context.
|
||||
func LoadContext(logger logr.Logger, contextEntries []kyverno.ContextEntry, resCache resourcecache.ResourceCache, ctx *PolicyContext) error {
|
||||
if len(contextEntries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// get GVR Cache for "configmaps"
|
||||
// can get cache for other resources if the informers are enabled in resource cache
|
||||
gvrC, ok := resCache.GetGVRCache("ConfigMap")
|
||||
if !ok {
|
||||
return errors.New("configmaps GVR Cache not found")
|
||||
}
|
||||
|
||||
lister := gvrC.Lister()
|
||||
|
||||
for _, entry := range contextEntries {
|
||||
if entry.ConfigMap != nil {
|
||||
if err := loadConfigMap(entry, lister, ctx.JSONContext); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if entry.APICall != nil {
|
||||
if err := loadAPIData(logger, entry, ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadAPIData(logger logr.Logger, entry kyverno.ContextEntry, ctx *PolicyContext) error {
|
||||
jsonData, err := fetchAPIData(entry, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if entry.APICall.JMESPath == "" {
|
||||
err = ctx.JSONContext.AddJSON(jsonData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add resource data to context: contextEntry: %v, error: %v", entry, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
results, err := applyJMESPath(entry.APICall.JMESPath, jsonData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to apply JMESPath for context entry %v: %v", entry, err)
|
||||
}
|
||||
|
||||
contextNamedData := make(map[string]interface{})
|
||||
contextNamedData[entry.Name] = results
|
||||
contextData, err := json.Marshal(contextNamedData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshall data %v for context entry %v: %v", contextNamedData, entry, err)
|
||||
}
|
||||
|
||||
err = ctx.JSONContext.AddJSON(contextData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add JMESPath (%s) results to context, error: %v", entry.APICall.JMESPath, err)
|
||||
}
|
||||
|
||||
logger.Info("added APICall context entry", "data", contextNamedData)
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyJMESPath(jmesPath string, jsonData []byte) (interface{}, error) {
|
||||
jp, err := jmespath.Compile(jmesPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compile JMESPath: %s, error: %v", jmesPath, err)
|
||||
}
|
||||
|
||||
var data interface{}
|
||||
err = json.Unmarshal(jsonData, &data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshall JSON: %s, error: %v", string(jsonData), err)
|
||||
}
|
||||
|
||||
return jp.Search(data)
|
||||
}
|
||||
|
||||
func fetchAPIData(entry kyverno.ContextEntry, ctx *PolicyContext) ([]byte, error) {
|
||||
if entry.APICall == nil {
|
||||
return nil, fmt.Errorf("missing APICall in context entry %v", entry)
|
||||
}
|
||||
|
||||
p, err := NewAPIPath(entry.APICall.URLPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build API path for %v: %v", entry, err)
|
||||
}
|
||||
|
||||
var jsonData []byte
|
||||
if p.Name != "" {
|
||||
jsonData, err = loadResource(ctx, p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add resource with urlPath: %s: %v", p, err)
|
||||
}
|
||||
|
||||
} else {
|
||||
jsonData, err = loadResourceList(ctx, p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add resource list with urlPath: %s, error: %v", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
return jsonData, nil
|
||||
}
|
||||
|
||||
func loadResourceList(ctx *PolicyContext, p *APIPath) ([]byte, error) {
|
||||
l, err := ctx.Client.ListResource(p.Version, p.ResourceType, p.Namespace, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.MarshalJSON()
|
||||
}
|
||||
|
||||
func loadResource(ctx *PolicyContext, p *APIPath) ([]byte, error) {
|
||||
if ctx.Client == nil {
|
||||
return nil, fmt.Errorf("API client is not available")
|
||||
}
|
||||
|
||||
r, err := ctx.Client.GetResource(p.Version, p.ResourceType, p.Namespace, p.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.MarshalJSON()
|
||||
}
|
||||
|
||||
func loadConfigMap(entry kyverno.ContextEntry, lister dynamiclister.Lister, ctx *context.Context) error {
|
||||
data, err := fetchConfigMap(entry, lister)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve config map for context entry %v: %v", entry, err)
|
||||
}
|
||||
|
||||
err = ctx.AddJSON(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add config map for context entry %v: %v", entry, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchConfigMap(entry kyverno.ContextEntry, lister dynamiclister.Lister) ([]byte, error) {
|
||||
contextData := make(map[string]interface{})
|
||||
name := entry.ConfigMap.Name
|
||||
namespace := entry.ConfigMap.Namespace
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s/%s", namespace, name)
|
||||
obj, err := lister.Get(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read configmap %s/%s from cache: %v", namespace, name, err)
|
||||
}
|
||||
|
||||
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert configmap %s/%s: %v", namespace, name, err)
|
||||
}
|
||||
|
||||
// extract configmap data
|
||||
contextData["data"] = unstructuredObj["data"]
|
||||
contextData["metadata"] = unstructuredObj["metadata"]
|
||||
contextNamedData := make(map[string]interface{})
|
||||
contextNamedData[entry.Name] = contextData
|
||||
data, err := json.Marshal(contextNamedData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal configmap %s/%s: %v", namespace, name, err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
|
@ -30,7 +30,6 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
|
|||
ctx := policyContext.JSONContext
|
||||
|
||||
resCache := policyContext.ResourceCache
|
||||
jsonContext := policyContext.JSONContext
|
||||
logger := log.Log.WithName("EngineMutate").WithValues("policy", policy.Name, "kind", patchedResource.GetKind(),
|
||||
"namespace", patchedResource.GetNamespace(), "name", patchedResource.GetName())
|
||||
|
||||
|
@ -68,7 +67,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
|
|||
logger.V(3).Info("matched mutate rule")
|
||||
|
||||
// add configmap json data to context
|
||||
if err := AddResourceToContext(logger, rule.Context, resCache, jsonContext); err != nil {
|
||||
if err := LoadContext(logger, rule.Context, resCache, policyContext); err != nil {
|
||||
logger.V(2).Info("failed to add configmaps to context", "reason", err.Error())
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/wildcards"
|
||||
"github.com/kyverno/kyverno/pkg/resourcecache"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
"github.com/minio/minio/pkg/wildcard"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
|
@ -20,7 +16,6 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
|
@ -269,17 +264,18 @@ func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef k
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyConditions(original []kyverno.Condition) []kyverno.Condition {
|
||||
if original == nil || len(original) == 0 {
|
||||
return []kyverno.Condition{}
|
||||
}
|
||||
|
||||
var copy []kyverno.Condition
|
||||
var copies []kyverno.Condition
|
||||
for _, condition := range original {
|
||||
copy = append(copy, *condition.DeepCopy())
|
||||
copies = append(copies, *condition.DeepCopy())
|
||||
}
|
||||
|
||||
return copy
|
||||
return copies
|
||||
}
|
||||
|
||||
// excludeResource checks if the resource has ownerRef set
|
||||
|
@ -310,58 +306,3 @@ func ManagedPodResource(policy kyverno.ClusterPolicy, resource unstructured.Unst
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
// AddResourceToContext - Add the Configmap JSON to Context.
|
||||
// it will read configmaps (can be extended to get other type of resource like secrets, namespace etc)
|
||||
// from the informer cache and add the configmap data to context
|
||||
func AddResourceToContext(logger logr.Logger, contextEntries []kyverno.ContextEntry, resCache resourcecache.ResourceCache, ctx *context.Context) error {
|
||||
if len(contextEntries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
gvrC, ok := resCache.GetGVRCache("ConfigMap")
|
||||
if ok {
|
||||
lister := gvrC.Lister()
|
||||
for _, context := range contextEntries {
|
||||
contextData := make(map[string]interface{})
|
||||
name := context.ConfigMap.Name
|
||||
namespace := context.ConfigMap.Namespace
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s/%s", namespace, name)
|
||||
obj, err := lister.Get(key)
|
||||
if err != nil {
|
||||
logger.Error(err, fmt.Sprintf("failed to read configmap %s/%s from cache", namespace, name))
|
||||
continue
|
||||
}
|
||||
|
||||
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to convert context runtime object to unstructured")
|
||||
continue
|
||||
}
|
||||
|
||||
// extract configmap data
|
||||
contextData["data"] = unstructuredObj["data"]
|
||||
contextData["metadata"] = unstructuredObj["metadata"]
|
||||
contextNamedData := make(map[string]interface{})
|
||||
contextNamedData[context.Name] = contextData
|
||||
jdata, err := json.Marshal(contextNamedData)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to unmarshal context data")
|
||||
continue
|
||||
}
|
||||
|
||||
// add data to context
|
||||
err = ctx.AddJSON(jdata)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to load context json")
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New("configmaps GVR Cache not found")
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo
|
|||
log = log.WithValues("rule", rule.Name)
|
||||
|
||||
// add configmap json data to context
|
||||
if err := AddResourceToContext(log, rule.Context, ctx.ResourceCache, ctx.JSONContext); err != nil {
|
||||
if err := LoadContext(log, rule.Context, ctx.ResourceCache, ctx); err != nil {
|
||||
log.V(2).Info("failed to add configmaps to context", "reason", err.Error())
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
|
|||
return nil, err
|
||||
}
|
||||
|
||||
policyContext := engine.PolicyContext{
|
||||
policyContext := &engine.PolicyContext{
|
||||
NewResource: resource,
|
||||
Policy: *policyObj,
|
||||
AdmissionInfo: gr.Spec.Context.UserRequestInfo,
|
||||
|
@ -179,7 +179,7 @@ func updateStatus(statusControl StatusControlInterface, gr kyverno.GenerateReque
|
|||
return statusControl.Success(gr, genResources)
|
||||
}
|
||||
|
||||
func (c *Controller) applyGeneratePolicy(log logr.Logger, policyContext engine.PolicyContext, gr kyverno.GenerateRequest, applicableRules []string) (genResources []kyverno.ResourceSpec, err error) {
|
||||
func (c *Controller) applyGeneratePolicy(log logr.Logger, policyContext *engine.PolicyContext, gr kyverno.GenerateRequest, applicableRules []string) (genResources []kyverno.ResourceSpec, err error) {
|
||||
// Get the response as the actions to be performed on the resource
|
||||
// - - substitute values
|
||||
policy := policyContext.Policy
|
||||
|
@ -212,7 +212,7 @@ func (c *Controller) applyGeneratePolicy(log logr.Logger, policyContext engine.P
|
|||
}
|
||||
|
||||
// add configmap json data to context
|
||||
if err := engine.AddResourceToContext(log, rule.Context, resCache, jsonContext); err != nil {
|
||||
if err := engine.LoadContext(log, rule.Context, resCache, policyContext); err != nil {
|
||||
log.Info("cannot add configmaps to context", "reason", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/go-logr/logr"
|
||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
client "github.com/kyverno/kyverno/pkg/dclient"
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||
|
@ -20,7 +21,9 @@ import (
|
|||
|
||||
// applyPolicy applies policy on a resource
|
||||
func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructured,
|
||||
logger logr.Logger, excludeGroupRole []string, resCache resourcecache.ResourceCache) (responses []*response.EngineResponse) {
|
||||
logger logr.Logger, excludeGroupRole []string, resCache resourcecache.ResourceCache,
|
||||
client *client.Client) (responses []*response.EngineResponse) {
|
||||
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
name := resource.GetKind() + "/" + resource.GetName()
|
||||
|
@ -47,7 +50,16 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
|
|||
logger.Error(err, "failed to process mutation rule")
|
||||
}
|
||||
|
||||
engineResponseValidation = engine.Validate(&engine.PolicyContext{Policy: policy, NewResource: resource, ExcludeGroupRole: excludeGroupRole, ResourceCache: resCache, JSONContext: ctx})
|
||||
policyCtx := &engine.PolicyContext{
|
||||
Policy: policy,
|
||||
NewResource: resource,
|
||||
ExcludeGroupRole: excludeGroupRole,
|
||||
ResourceCache: resCache,
|
||||
JSONContext: ctx,
|
||||
Client: client,
|
||||
}
|
||||
|
||||
engineResponseValidation = engine.Validate(policyCtx)
|
||||
engineResponses = append(engineResponses, mergeRuleRespose(engineResponseMutation, engineResponseValidation))
|
||||
|
||||
return engineResponses
|
||||
|
|
|
@ -84,7 +84,7 @@ func (pc *PolicyController) applyPolicy(policy *kyverno.ClusterPolicy, resource
|
|||
logger.V(4).Info("policy and resource already processed", "policyResourceVersion", policy.ResourceVersion, "resourceResourceVersion", resource.GetResourceVersion(), "kind", resource.GetKind(), "namespace", resource.GetNamespace(), "name", resource.GetName())
|
||||
}
|
||||
|
||||
engineResponse := applyPolicy(*policy, resource, logger, pc.configHandler.GetExcludeGroupRole(), pc.resCache)
|
||||
engineResponse := applyPolicy(*policy, resource, logger, pc.configHandler.GetExcludeGroupRole(), pc.resCache, pc.client)
|
||||
engineResponses = append(engineResponses, engineResponse...)
|
||||
|
||||
// post-processing, register the resource as processed
|
||||
|
|
|
@ -4,12 +4,14 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/jmespath/go-jmespath"
|
||||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
"github.com/kyverno/kyverno/pkg/kyverno/common"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
dclient "github.com/kyverno/kyverno/pkg/dclient"
|
||||
"github.com/kyverno/kyverno/pkg/kyverno/common"
|
||||
"github.com/kyverno/kyverno/pkg/openapi"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
"github.com/minio/minio/pkg/wildcard"
|
||||
|
@ -427,16 +429,84 @@ func validateResources(rule kyverno.Rule) (string, error) {
|
|||
|
||||
// matched resources
|
||||
if path, err := validateMatchedResourceDescription(rule.MatchResources.ResourceDescription); err != nil {
|
||||
return fmt.Sprintf("resources.%s", path), err
|
||||
return fmt.Sprintf("match.resources.%s", path), err
|
||||
}
|
||||
// exclude resources
|
||||
if path, err := validateExcludeResourceDescription(rule.ExcludeResources.ResourceDescription); err != nil {
|
||||
return fmt.Sprintf("resources.%s", path), err
|
||||
return fmt.Sprintf("exclude.resources.%s", path), err
|
||||
}
|
||||
|
||||
//validating the values present under validation.preconditions, if they exist
|
||||
if rule.Conditions != nil {
|
||||
if path, err := validateConditions(rule.Conditions, "preconditions"); err != nil {
|
||||
return fmt.Sprintf("validate.%s", path), err
|
||||
}
|
||||
}
|
||||
// validating the values present under validation.deny.conditions, if they exist
|
||||
if rule.Validation.Deny != nil {
|
||||
if path, err := validateConditions(rule.Validation.Deny.Conditions, "conditions"); err != nil {
|
||||
return fmt.Sprintf("validate.deny.%s", path), err
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// validateConditions validates all the 'conditions' or 'preconditions' of a rule depending on the corresponding 'condition.key'.
|
||||
// As of now, it is validating the 'value' field whether it contains the only allowed set of values or not when 'condition.key' is {{request.operation}}
|
||||
func validateConditions(conditions []kyverno.Condition, schemaKey string) (string, error) {
|
||||
// []kyverno.Condition can only exist under either 'conditions' or 'preconditions' key of the policy schema
|
||||
if schemaKey != "conditions" && schemaKey != "preconditions" {
|
||||
return fmt.Sprintf(schemaKey), fmt.Errorf("wrong schema key found for validating the conditions. Conditions can only occur under 'preconditions' or 'conditions' key in the policy schema")
|
||||
}
|
||||
for i, condition := range conditions {
|
||||
if path, err := validateConditionValues(condition); err != nil {
|
||||
return fmt.Sprintf("%s[%d].%s", schemaKey, i, path), err
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// ValidateUniqueRuleName checks if the rule names are unique across a policy
|
||||
// validateConditionValues validates whether all the values under the 'value' field of a 'conditions' field
|
||||
// are apt with respect to the provided 'condition.key'
|
||||
func validateConditionValues(c kyverno.Condition) (string, error) {
|
||||
switch strings.ReplaceAll(c.Key.(string), " ", "") {
|
||||
case "{{request.operation}}":
|
||||
return validateConditionValuesKeyRequestOperation(c)
|
||||
default:
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// validateConditionValuesKeyRequestOperation validates whether all the values under the 'value' field of a 'conditions' field
|
||||
// are one of ["CREATE", "UPDATE", "DELETE", "CONNECT"] when 'condition.key' is {{request.operation}}
|
||||
func validateConditionValuesKeyRequestOperation(c kyverno.Condition) (string, error) {
|
||||
valuesAllowed := map[string]bool{
|
||||
"CREATE": true,
|
||||
"UPDATE": true,
|
||||
"DELETE": true,
|
||||
"CONNECT": true,
|
||||
}
|
||||
switch reflect.TypeOf(c.Value).Kind() {
|
||||
case reflect.String:
|
||||
if !valuesAllowed[c.Value.(string)] {
|
||||
return fmt.Sprintf("value: %s", c.Value.(string)), fmt.Errorf("unknown value '%s' found under the 'value' field. Only the following values are allowed: [CREATE, UPDATE, DELETE, CONNECT]", c.Value.(string))
|
||||
}
|
||||
case reflect.Slice:
|
||||
values := reflect.ValueOf(c.Value)
|
||||
for i := 0; i < values.Len(); i++ {
|
||||
value := values.Index(i).Interface().(string)
|
||||
if !valuesAllowed[value] {
|
||||
return fmt.Sprintf("value[%d]", i), fmt.Errorf("unknown value '%s' found under the 'value' field. Only the following values are allowed: [CREATE, UPDATE, DELETE, CONNECT]", value)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Sprintf("value"), fmt.Errorf("'value' field found to be of the type %v. The provided value/values are expected to be either in the form of a string or list", reflect.TypeOf(c.Value).Kind())
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// validateUniqueRuleName checks if the rule names are unique across a policy
|
||||
func validateUniqueRuleName(p kyverno.ClusterPolicy) (string, error) {
|
||||
var ruleNames []string
|
||||
|
||||
|
@ -481,14 +551,59 @@ func validateRuleContext(rule kyverno.Rule) error {
|
|||
return fmt.Errorf("a name is required for context entries")
|
||||
}
|
||||
|
||||
var err error
|
||||
if entry.ConfigMap != nil {
|
||||
if entry.ConfigMap.Name == "" {
|
||||
return fmt.Errorf("a name is required for configMap context entry")
|
||||
}
|
||||
err = validateConfigMap(entry)
|
||||
} else if entry.APICall != nil {
|
||||
err = validateAPICall(entry)
|
||||
} else {
|
||||
return fmt.Errorf("a configMap or apiCall is required for context entries")
|
||||
}
|
||||
|
||||
if entry.ConfigMap.Namespace == "" {
|
||||
return fmt.Errorf("a namespace is required for configMap context entry")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateConfigMap(entry kyverno.ContextEntry) error {
|
||||
if entry.ConfigMap == nil {
|
||||
return fmt.Errorf("configMap is empty")
|
||||
}
|
||||
|
||||
if entry.APICall != nil {
|
||||
return fmt.Errorf("both configMap and apiCall are not allowed in a context entry")
|
||||
}
|
||||
|
||||
if entry.ConfigMap.Name == "" {
|
||||
return fmt.Errorf("a name is required for configMap context entry")
|
||||
}
|
||||
|
||||
if entry.ConfigMap.Namespace == "" {
|
||||
return fmt.Errorf("a namespace is required for configMap context entry")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAPICall(entry kyverno.ContextEntry) error {
|
||||
if entry.APICall == nil {
|
||||
return fmt.Errorf("apiCall is empty")
|
||||
}
|
||||
|
||||
if entry.ConfigMap != nil {
|
||||
return fmt.Errorf("both configMap and apiCall are not allowed in a context entry")
|
||||
}
|
||||
|
||||
if _, err := engine.NewAPIPath(entry.APICall.URLPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if entry.APICall.JMESPath != "" {
|
||||
if _, err := jmespath.NewParser().Parse(entry.APICall.JMESPath); err != nil {
|
||||
return fmt.Errorf("failed to parse JMESPath %s: %v", entry.APICall.JMESPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -251,6 +251,155 @@ func Test_Validate_ResourceDescription_MatchedValid(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func Test_Validate_DenyConditions_KeyRequestOperation_Empty(t *testing.T) {
|
||||
denyConditions := []byte(`[]`)
|
||||
|
||||
var dcs []kyverno.Condition
|
||||
err := json.Unmarshal(denyConditions, &dcs)
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = validateConditions(dcs, "conditions")
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func Test_Validate_Preconditions_KeyRequestOperation_Empty(t *testing.T) {
|
||||
preConditions := []byte(`[]`)
|
||||
|
||||
var pcs []kyverno.Condition
|
||||
err := json.Unmarshal(preConditions, &pcs)
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = validateConditions(pcs, "preconditions")
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func Test_Validate_DenyConditionsValuesString_KeyRequestOperation_ExpectedValue(t *testing.T) {
|
||||
denyConditions := []byte(`
|
||||
[
|
||||
{
|
||||
"key":"{{request.operation}}",
|
||||
"operator":"Equals",
|
||||
"value":"DELETE"
|
||||
},
|
||||
{
|
||||
"key":"{{request.operation}}",
|
||||
"operator":"NotEquals",
|
||||
"value":"CREATE"
|
||||
},
|
||||
{
|
||||
"key":"{{request.operation}}",
|
||||
"operator":"NotEquals",
|
||||
"value":"CONNECT"
|
||||
},
|
||||
{
|
||||
"key":"{{ request.operation }}",
|
||||
"operator":"NotEquals",
|
||||
"value":"UPDATE"
|
||||
},
|
||||
{
|
||||
"key":"{{lbServiceCount}}",
|
||||
"operator":"Equals",
|
||||
"value":"2"
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
var dcs []kyverno.Condition
|
||||
err := json.Unmarshal(denyConditions, &dcs)
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = validateConditions(dcs, "conditions")
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func Test_Validate_PreconditionsValuesString_KeyRequestOperation_UnknownValue(t *testing.T) {
|
||||
preConditions := []byte(`
|
||||
[
|
||||
{
|
||||
"key":"{{request.operation}}",
|
||||
"operator":"Equals",
|
||||
"value":"foobar"
|
||||
},
|
||||
{
|
||||
"key": "{{request.operation}}",
|
||||
"operator": "NotEquals",
|
||||
"value": "CREATE"
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
var pcs []kyverno.Condition
|
||||
err := json.Unmarshal(preConditions, &pcs)
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = validateConditions(pcs, "preconditions")
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
||||
func Test_Validate_DenyConditionsValuesList_KeyRequestOperation_ExpectedItem(t *testing.T) {
|
||||
denyConditions := []byte(`
|
||||
[
|
||||
{
|
||||
"key":"{{request.operation}}",
|
||||
"operator":"Equals",
|
||||
"value": [
|
||||
"CREATE",
|
||||
"DELETE",
|
||||
"CONNECT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key":"{{request.operation}}",
|
||||
"operator":"NotEquals",
|
||||
"value": [
|
||||
"UPDATE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "{{lbServiceCount}}",
|
||||
"operator": "Equals",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
var dcs []kyverno.Condition
|
||||
err := json.Unmarshal(denyConditions, &dcs)
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = validateConditions(dcs, "conditions")
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func Test_Validate_PreconditionsValuesList_KeyRequestOperation_UnknownItem(t *testing.T) {
|
||||
preConditions := []byte(`
|
||||
[
|
||||
{
|
||||
"key":"{{request.operation}}",
|
||||
"operator":"Equals",
|
||||
"value": [
|
||||
"foobar",
|
||||
"CREATE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key":"{{request.operation}}",
|
||||
"operator":"NotEquals",
|
||||
"value": [
|
||||
"foobar"
|
||||
]
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
var pcs []kyverno.Condition
|
||||
err := json.Unmarshal(preConditions, &pcs)
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = validateConditions(pcs, "preconditions")
|
||||
assert.Assert(t, err != nil)
|
||||
}
|
||||
|
||||
func Test_Validate_ResourceDescription_MissingKindsOnExclude(t *testing.T) {
|
||||
var err error
|
||||
excludeResourcedescirption := []byte(`
|
||||
|
|
|
@ -150,7 +150,7 @@ func runTestCase(t *testing.T, tc scaseT) bool {
|
|||
if err := createNamespace(client, resource); err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
policyContext := engine.PolicyContext{
|
||||
policyContext := &engine.PolicyContext{
|
||||
NewResource: *resource,
|
||||
Policy: *policy,
|
||||
Client: client,
|
||||
|
|
|
@ -42,7 +42,7 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic
|
|||
logger.Error(err, "failed to extract resource")
|
||||
}
|
||||
|
||||
policyContext := engine.PolicyContext{
|
||||
policyContext := &engine.PolicyContext{
|
||||
NewResource: new,
|
||||
OldResource: old,
|
||||
AdmissionInfo: userRequestInfo,
|
||||
|
|
|
@ -458,7 +458,7 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
|
|||
logger.Error(err, "failed to load service account in context")
|
||||
}
|
||||
|
||||
ok, msg := HandleValidation(request, policies, nil, ctx, userRequestInfo, ws.statusListener, ws.eventGen, ws.prGenerator, ws.log, ws.configHandler, ws.resCache)
|
||||
ok, msg := HandleValidation(request, policies, nil, ctx, userRequestInfo, ws.statusListener, ws.eventGen, ws.prGenerator, ws.log, ws.configHandler, ws.resCache, ws.client)
|
||||
if !ok {
|
||||
logger.Info("admission request denied")
|
||||
return &v1beta1.AdmissionResponse{
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package webhooks
|
||||
|
||||
import (
|
||||
client "github.com/kyverno/kyverno/pkg/dclient"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/event"
|
||||
|
@ -41,7 +41,7 @@ type AuditHandler interface {
|
|||
}
|
||||
|
||||
type auditHandler struct {
|
||||
client *kyvernoclient.Clientset
|
||||
client *client.Client
|
||||
queue workqueue.RateLimitingInterface
|
||||
pCache policycache.Interface
|
||||
eventGen event.Interface
|
||||
|
@ -67,7 +67,8 @@ func NewValidateAuditHandler(pCache policycache.Interface,
|
|||
crbInformer rbacinformer.ClusterRoleBindingInformer,
|
||||
log logr.Logger,
|
||||
dynamicConfig config.Interface,
|
||||
resCache resourcecache.ResourceCache) AuditHandler {
|
||||
resCache resourcecache.ResourceCache,
|
||||
client *client.Client) AuditHandler {
|
||||
|
||||
return &auditHandler{
|
||||
pCache: pCache,
|
||||
|
@ -82,6 +83,7 @@ func NewValidateAuditHandler(pCache policycache.Interface,
|
|||
prGenerator: prGenerator,
|
||||
configHandler: dynamicConfig,
|
||||
resCache: resCache,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,7 +175,7 @@ func (h *auditHandler) process(request *v1beta1.AdmissionRequest) error {
|
|||
return errors.Wrap(err, "failed to load service account in context")
|
||||
}
|
||||
|
||||
HandleValidation(request, policies, nil, ctx, userRequestInfo, h.statusListener, h.eventGen, h.prGenerator, logger, h.configHandler, h.resCache)
|
||||
HandleValidation(request, policies, nil, ctx, userRequestInfo, h.statusListener, h.eventGen, h.prGenerator, logger, h.configHandler, h.resCache, h.client)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package webhooks
|
||||
|
||||
import (
|
||||
client "github.com/kyverno/kyverno/pkg/dclient"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
|
@ -36,7 +37,8 @@ func HandleValidation(
|
|||
prGenerator policyreport.GeneratorInterface,
|
||||
log logr.Logger,
|
||||
dynamicConfig config.Interface,
|
||||
resCache resourcecache.ResourceCache) (bool, string) {
|
||||
resCache resourcecache.ResourceCache,
|
||||
client *client.Client) (bool, string) {
|
||||
|
||||
if len(policies) == 0 {
|
||||
return true, ""
|
||||
|
@ -76,6 +78,7 @@ func HandleValidation(
|
|||
ExcludeResourceFunc: dynamicConfig.ToFilter,
|
||||
ResourceCache: resCache,
|
||||
JSONContext: ctx,
|
||||
Client: client,
|
||||
}
|
||||
|
||||
var engineResponses []*response.EngineResponse
|
||||
|
|
Loading…
Add table
Reference in a new issue