mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
api server lookups (#1514)
* initial commit for api server lookups Signed-off-by: Jim Bugwadia <jim@nirmata.com> * initial commit for API server lookups Signed-off-by: Jim Bugwadia <jim@nirmata.com> * Enhancing dockerfiles (multi-stage) of kyverno components and adding non-root user to the docker images (#1495) * Dockerfile refactored Signed-off-by: Raj Babu Das <mail.rajdas@gmail.com> * Adding non-root commands to docker images and enhanced the dockerfiles Signed-off-by: Raj Babu Das <mail.rajdas@gmail.com> * changing base image to scratch Signed-off-by: Raj Babu Das <mail.rajdas@gmail.com> * Minor typo fix Signed-off-by: Raj Babu Das <mail.rajdas@gmail.com> * changing dockerfiles to use /etc/passwd to use non-root user' Signed-off-by: Raj Babu Das <mail.rajdas@gmail.com> * minor typo Signed-off-by: Raj Babu Das <mail.rajdas@gmail.com> * minor typo Signed-off-by: Raj Babu Das <mail.rajdas@gmail.com> Signed-off-by: Jim Bugwadia <jim@nirmata.com> * revert cli image name (#1507) Signed-off-by: Raj Babu Das <mail.rajdas@gmail.com> Signed-off-by: Jim Bugwadia <jim@nirmata.com> * Refactor resourceCache; Reduce throttling requests (background controller) (#1500) * skip sending API request for filtered resource * fix PR comment Signed-off-by: Shuting Zhao <shutting06@gmail.com> * fixes https://github.com/kyverno/kyverno/issues/1490 Signed-off-by: Shuting Zhao <shutting06@gmail.com> * fix bug - namespace is not returned properly Signed-off-by: Shuting Zhao <shutting06@gmail.com> * reduce throttling - list resource using lister * refactor resource cache * fix test Signed-off-by: Shuting Zhao <shutting06@gmail.com> * fix label selector Signed-off-by: Shuting Zhao <shutting06@gmail.com> * fix build failure Signed-off-by: Shuting Zhao <shutting06@gmail.com> Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix merge issues Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix unit test Signed-off-by: Jim Bugwadia <jim@nirmata.com> * add nil check for API client Signed-off-by: Jim Bugwadia <jim@nirmata.com> Co-authored-by: Raj Babu Das <mail.rajdas@gmail.com> Co-authored-by: shuting <shutting06@gmail.com>
This commit is contained in:
parent
c692263177
commit
e8e3b93a5f
26 changed files with 639 additions and 117 deletions
|
@ -50,17 +50,34 @@ spec:
|
||||||
context:
|
context:
|
||||||
description: Context defines variables and data sources that can be used during rule execution.
|
description: Context defines variables and data sources that can be used during rule execution.
|
||||||
items:
|
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:
|
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:
|
configMap:
|
||||||
description: ConfigMapReference refers to a ConfigMap
|
description: ConfigMap is the ConfigMap reference.
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
description: Name is the ConfigMap name.
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
namespace:
|
||||||
|
description: Namespace is the ConfigMap namespace.
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
type: object
|
type: object
|
||||||
name:
|
name:
|
||||||
|
description: Name is the variable name.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
@ -1152,17 +1169,34 @@ spec:
|
||||||
context:
|
context:
|
||||||
description: Context defines variables and data sources that can be used during rule execution.
|
description: Context defines variables and data sources that can be used during rule execution.
|
||||||
items:
|
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:
|
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:
|
configMap:
|
||||||
description: ConfigMapReference refers to a ConfigMap
|
description: ConfigMap is the ConfigMap reference.
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
description: Name is the ConfigMap name.
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
namespace:
|
||||||
|
description: Namespace is the ConfigMap namespace.
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
type: object
|
type: object
|
||||||
name:
|
name:
|
||||||
|
description: Name is the variable name.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
|
|
@ -277,6 +277,7 @@ func main() {
|
||||||
log.Log.WithName("ValidateAuditHandler"),
|
log.Log.WithName("ValidateAuditHandler"),
|
||||||
configData,
|
configData,
|
||||||
rCache,
|
rCache,
|
||||||
|
client,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configure certificates
|
// Configure certificates
|
||||||
|
|
|
@ -67,17 +67,39 @@ spec:
|
||||||
can be used during rule execution.
|
can be used during rule execution.
|
||||||
items:
|
items:
|
||||||
description: ContextEntry adds variables and data sources
|
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:
|
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:
|
configMap:
|
||||||
description: ConfigMapReference refers to a ConfigMap
|
description: ConfigMap is the ConfigMap reference.
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
description: Name is the ConfigMap name.
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
namespace:
|
||||||
|
description: Namespace is the ConfigMap namespace.
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
type: object
|
type: object
|
||||||
name:
|
name:
|
||||||
|
description: Name is the variable name.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
|
|
@ -68,17 +68,39 @@ spec:
|
||||||
can be used during rule execution.
|
can be used during rule execution.
|
||||||
items:
|
items:
|
||||||
description: ContextEntry adds variables and data sources
|
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:
|
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:
|
configMap:
|
||||||
description: ConfigMapReference refers to a ConfigMap
|
description: ConfigMap is the ConfigMap reference.
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
description: Name is the ConfigMap name.
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
namespace:
|
||||||
|
description: Namespace is the ConfigMap namespace.
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
type: object
|
type: object
|
||||||
name:
|
name:
|
||||||
|
description: Name is the variable name.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
|
|
@ -55,17 +55,34 @@ spec:
|
||||||
context:
|
context:
|
||||||
description: Context defines variables and data sources that can be used during rule execution.
|
description: Context defines variables and data sources that can be used during rule execution.
|
||||||
items:
|
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:
|
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:
|
configMap:
|
||||||
description: ConfigMapReference refers to a ConfigMap
|
description: ConfigMap is the ConfigMap reference.
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
description: Name is the ConfigMap name.
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
namespace:
|
||||||
|
description: Namespace is the ConfigMap namespace.
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
type: object
|
type: object
|
||||||
name:
|
name:
|
||||||
|
description: Name is the variable name.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
@ -1157,17 +1174,34 @@ spec:
|
||||||
context:
|
context:
|
||||||
description: Context defines variables and data sources that can be used during rule execution.
|
description: Context defines variables and data sources that can be used during rule execution.
|
||||||
items:
|
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:
|
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:
|
configMap:
|
||||||
description: ConfigMapReference refers to a ConfigMap
|
description: ConfigMap is the ConfigMap reference.
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
description: Name is the ConfigMap name.
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
namespace:
|
||||||
|
description: Namespace is the ConfigMap namespace.
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
type: object
|
type: object
|
||||||
name:
|
name:
|
||||||
|
description: Name is the variable name.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
|
|
@ -55,17 +55,34 @@ spec:
|
||||||
context:
|
context:
|
||||||
description: Context defines variables and data sources that can be used during rule execution.
|
description: Context defines variables and data sources that can be used during rule execution.
|
||||||
items:
|
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:
|
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:
|
configMap:
|
||||||
description: ConfigMapReference refers to a ConfigMap
|
description: ConfigMap is the ConfigMap reference.
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
description: Name is the ConfigMap name.
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
namespace:
|
||||||
|
description: Namespace is the ConfigMap namespace.
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
type: object
|
type: object
|
||||||
name:
|
name:
|
||||||
|
description: Name is the variable name.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
@ -1157,17 +1174,34 @@ spec:
|
||||||
context:
|
context:
|
||||||
description: Context defines variables and data sources that can be used during rule execution.
|
description: Context defines variables and data sources that can be used during rule execution.
|
||||||
items:
|
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:
|
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:
|
configMap:
|
||||||
description: ConfigMapReference refers to a ConfigMap
|
description: ConfigMap is the ConfigMap reference.
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
description: Name is the ConfigMap name.
|
||||||
type: string
|
type: string
|
||||||
namespace:
|
namespace:
|
||||||
|
description: Namespace is the ConfigMap namespace.
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
type: object
|
type: object
|
||||||
name:
|
name:
|
||||||
|
description: Name is the variable name.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
|
|
@ -98,18 +98,43 @@ type Rule struct {
|
||||||
Generation Generation `json:"generate,omitempty" yaml:"generate,omitempty"`
|
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 {
|
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"`
|
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
|
// ConfigMapReference refers to a ConfigMap
|
||||||
type ConfigMapReference struct {
|
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"`
|
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.
|
// Condition defines variable-based conditional criteria for rule execution.
|
||||||
type Condition struct {
|
type Condition struct {
|
||||||
// Key is the context entry (using JMESPath) for conditional rule evaluation.
|
// 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,
|
kclient: kclient,
|
||||||
log: log.WithName("dclient"),
|
log: log.WithName("dclient"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set discovery client
|
// 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,
|
// client will invalidate registered resources cache every x seconds,
|
||||||
// As there is no way to identify if the registered resource is available or not
|
// 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
|
// 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)
|
gvr, _ := c.DiscoveryClient.GetGVRFromKind(kind)
|
||||||
return gvr
|
return gvr
|
||||||
}
|
}
|
||||||
return c.DiscoveryClient.GetGVRFromAPIVersionKind(apiVersion, kind)
|
|
||||||
|
|
||||||
|
return c.DiscoveryClient.GetGVRFromAPIVersionKind(apiVersion, kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetResource returns the resource in unstructured/json format
|
// GetResource returns the resource in unstructured/json format
|
||||||
|
@ -347,15 +352,19 @@ func (c ServerPreferredResources) findResource(apiVersion string, kind string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, serverResource := range serverResources {
|
for _, serverResource := range serverResources {
|
||||||
|
|
||||||
if apiVersion != "" && serverResource.GroupVersion != apiVersion {
|
if apiVersion != "" && serverResource.GroupVersion != apiVersion {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, resource := range serverResource.APIResources {
|
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
|
// match kind or names (e.g. Namespace, namespaces, namespace)
|
||||||
if resource.Kind == kind && !strings.Contains(resource.Name, "/") {
|
// 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)
|
gv, err := schema.ParseGroupVersion(serverResource.GroupVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Error(err, "failed to parse groupVersion", "groupVersion", serverResource.GroupVersion)
|
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)
|
// 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
|
// - 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
|
// 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)
|
return filterRules(policyContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterRules(policyContext PolicyContext) *response.EngineResponse {
|
func filterRules(policyContext *PolicyContext) *response.EngineResponse {
|
||||||
kind := policyContext.NewResource.GetKind()
|
kind := policyContext.NewResource.GetKind()
|
||||||
name := policyContext.NewResource.GetName()
|
name := policyContext.NewResource.GetName()
|
||||||
namespace := policyContext.NewResource.GetNamespace()
|
namespace := policyContext.NewResource.GetNamespace()
|
||||||
|
@ -46,7 +46,7 @@ func filterRules(policyContext PolicyContext) *response.EngineResponse {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterRule(rule kyverno.Rule, policyContext PolicyContext) *response.RuleResponse {
|
func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleResponse {
|
||||||
if !rule.HasGenerate() {
|
if !rule.HasGenerate() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,6 @@ func filterRule(rule kyverno.Rule, policyContext PolicyContext) *response.RuleRe
|
||||||
admissionInfo := policyContext.AdmissionInfo
|
admissionInfo := policyContext.AdmissionInfo
|
||||||
ctx := policyContext.JSONContext
|
ctx := policyContext.JSONContext
|
||||||
resCache := policyContext.ResourceCache
|
resCache := policyContext.ResourceCache
|
||||||
jsonContext := policyContext.JSONContext
|
|
||||||
excludeGroupRole := policyContext.ExcludeGroupRole
|
excludeGroupRole := policyContext.ExcludeGroupRole
|
||||||
|
|
||||||
logger := log.Log.WithName("Generate").WithValues("policy", policy.Name,
|
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
|
// 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())
|
logger.V(4).Info("cannot add configmaps to context", "reason", err.Error())
|
||||||
return nil
|
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
|
ctx := policyContext.JSONContext
|
||||||
|
|
||||||
resCache := policyContext.ResourceCache
|
resCache := policyContext.ResourceCache
|
||||||
jsonContext := policyContext.JSONContext
|
|
||||||
logger := log.Log.WithName("EngineMutate").WithValues("policy", policy.Name, "kind", patchedResource.GetKind(),
|
logger := log.Log.WithName("EngineMutate").WithValues("policy", policy.Name, "kind", patchedResource.GetKind(),
|
||||||
"namespace", patchedResource.GetNamespace(), "name", patchedResource.GetName())
|
"namespace", patchedResource.GetNamespace(), "name", patchedResource.GetName())
|
||||||
|
|
||||||
|
@ -68,7 +67,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
|
||||||
logger.V(3).Info("matched mutate rule")
|
logger.V(3).Info("matched mutate rule")
|
||||||
|
|
||||||
// add configmap json data to context
|
// 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())
|
logger.V(2).Info("failed to add configmaps to context", "reason", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
|
||||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
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/engine/wildcards"
|
||||||
"github.com/kyverno/kyverno/pkg/resourcecache"
|
|
||||||
"github.com/kyverno/kyverno/pkg/utils"
|
"github.com/kyverno/kyverno/pkg/utils"
|
||||||
"github.com/minio/minio/pkg/wildcard"
|
"github.com/minio/minio/pkg/wildcard"
|
||||||
authenticationv1 "k8s.io/api/authentication/v1"
|
authenticationv1 "k8s.io/api/authentication/v1"
|
||||||
|
@ -20,7 +16,6 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -269,17 +264,18 @@ func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef k
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyConditions(original []kyverno.Condition) []kyverno.Condition {
|
func copyConditions(original []kyverno.Condition) []kyverno.Condition {
|
||||||
if original == nil || len(original) == 0 {
|
if original == nil || len(original) == 0 {
|
||||||
return []kyverno.Condition{}
|
return []kyverno.Condition{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var copy []kyverno.Condition
|
var copies []kyverno.Condition
|
||||||
for _, condition := range original {
|
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
|
// excludeResource checks if the resource has ownerRef set
|
||||||
|
@ -310,58 +306,3 @@ func ManagedPodResource(policy kyverno.ClusterPolicy, resource unstructured.Unst
|
||||||
|
|
||||||
return false
|
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)
|
log = log.WithValues("rule", rule.Name)
|
||||||
|
|
||||||
// add configmap json data to context
|
// 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())
|
log.V(2).Info("failed to add configmaps to context", "reason", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,7 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
policyContext := engine.PolicyContext{
|
policyContext := &engine.PolicyContext{
|
||||||
NewResource: resource,
|
NewResource: resource,
|
||||||
Policy: *policyObj,
|
Policy: *policyObj,
|
||||||
AdmissionInfo: gr.Spec.Context.UserRequestInfo,
|
AdmissionInfo: gr.Spec.Context.UserRequestInfo,
|
||||||
|
@ -179,7 +179,7 @@ func updateStatus(statusControl StatusControlInterface, gr kyverno.GenerateReque
|
||||||
return statusControl.Success(gr, genResources)
|
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
|
// Get the response as the actions to be performed on the resource
|
||||||
// - - substitute values
|
// - - substitute values
|
||||||
policy := policyContext.Policy
|
policy := policyContext.Policy
|
||||||
|
@ -212,7 +212,7 @@ func (c *Controller) applyGeneratePolicy(log logr.Logger, policyContext engine.P
|
||||||
}
|
}
|
||||||
|
|
||||||
// add configmap json data to context
|
// 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())
|
log.Info("cannot add configmaps to context", "reason", err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -528,7 +528,8 @@ func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
|
||||||
}
|
}
|
||||||
|
|
||||||
if policyHasGenerate {
|
if policyHasGenerate {
|
||||||
generateResponse := engine.Generate(engine.PolicyContext{Policy: *policy, NewResource: *resource})
|
ctx := &engine.PolicyContext{Policy: *policy, NewResource: *resource}
|
||||||
|
generateResponse := engine.Generate(ctx)
|
||||||
engineResponses = append(engineResponses, generateResponse)
|
engineResponses = append(engineResponses, generateResponse)
|
||||||
if len(generateResponse.PolicyResponse.Rules) > 0 {
|
if len(generateResponse.PolicyResponse.Rules) > 0 {
|
||||||
log.Log.V(3).Info("generate resource is valid", "policy", policy.Name, "resource", resPath)
|
log.Log.V(3).Info("generate resource is valid", "policy", policy.Name, "resource", resPath)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
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"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/response"
|
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||||
|
@ -20,7 +21,9 @@ import (
|
||||||
|
|
||||||
// applyPolicy applies policy on a resource
|
// applyPolicy applies policy on a resource
|
||||||
func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructured,
|
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()
|
startTime := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
name := resource.GetKind() + "/" + resource.GetName()
|
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")
|
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))
|
engineResponses = append(engineResponses, mergeRuleRespose(engineResponseMutation, engineResponseValidation))
|
||||||
|
|
||||||
return engineResponses
|
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())
|
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...)
|
engineResponses = append(engineResponses, engineResponse...)
|
||||||
|
|
||||||
// post-processing, register the resource as processed
|
// post-processing, register the resource as processed
|
||||||
|
|
|
@ -4,12 +4,14 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/jmespath/go-jmespath"
|
||||||
|
"github.com/kyverno/kyverno/pkg/engine"
|
||||||
|
"github.com/kyverno/kyverno/pkg/kyverno/common"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||||
dclient "github.com/kyverno/kyverno/pkg/dclient"
|
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/openapi"
|
||||||
"github.com/kyverno/kyverno/pkg/utils"
|
"github.com/kyverno/kyverno/pkg/utils"
|
||||||
"github.com/minio/minio/pkg/wildcard"
|
"github.com/minio/minio/pkg/wildcard"
|
||||||
|
@ -481,14 +483,59 @@ func validateRuleContext(rule kyverno.Rule) error {
|
||||||
return fmt.Errorf("a name is required for context entries")
|
return fmt.Errorf("a name is required for context entries")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
if entry.ConfigMap != nil {
|
if entry.ConfigMap != nil {
|
||||||
if entry.ConfigMap.Name == "" {
|
err = validateConfigMap(entry)
|
||||||
return fmt.Errorf("a name is required for configMap context 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 == "" {
|
if err != nil {
|
||||||
return fmt.Errorf("a namespace is required for configMap context entry")
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,7 @@ func runTestCase(t *testing.T, tc scaseT) bool {
|
||||||
if err := createNamespace(client, resource); err != nil {
|
if err := createNamespace(client, resource); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
} else {
|
} else {
|
||||||
policyContext := engine.PolicyContext{
|
policyContext := &engine.PolicyContext{
|
||||||
NewResource: *resource,
|
NewResource: *resource,
|
||||||
Policy: *policy,
|
Policy: *policy,
|
||||||
Client: client,
|
Client: client,
|
||||||
|
|
|
@ -42,7 +42,7 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic
|
||||||
logger.Error(err, "failed to extract resource")
|
logger.Error(err, "failed to extract resource")
|
||||||
}
|
}
|
||||||
|
|
||||||
policyContext := engine.PolicyContext{
|
policyContext := &engine.PolicyContext{
|
||||||
NewResource: new,
|
NewResource: new,
|
||||||
OldResource: old,
|
OldResource: old,
|
||||||
AdmissionInfo: userRequestInfo,
|
AdmissionInfo: userRequestInfo,
|
||||||
|
|
|
@ -458,7 +458,7 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
|
||||||
logger.Error(err, "failed to load service account in context")
|
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 {
|
if !ok {
|
||||||
logger.Info("admission request denied")
|
logger.Info("admission request denied")
|
||||||
return &v1beta1.AdmissionResponse{
|
return &v1beta1.AdmissionResponse{
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package webhooks
|
package webhooks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
client "github.com/kyverno/kyverno/pkg/dclient"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||||
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
|
||||||
"github.com/kyverno/kyverno/pkg/config"
|
"github.com/kyverno/kyverno/pkg/config"
|
||||||
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
|
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
|
||||||
"github.com/kyverno/kyverno/pkg/event"
|
"github.com/kyverno/kyverno/pkg/event"
|
||||||
|
@ -41,7 +41,7 @@ type AuditHandler interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type auditHandler struct {
|
type auditHandler struct {
|
||||||
client *kyvernoclient.Clientset
|
client *client.Client
|
||||||
queue workqueue.RateLimitingInterface
|
queue workqueue.RateLimitingInterface
|
||||||
pCache policycache.Interface
|
pCache policycache.Interface
|
||||||
eventGen event.Interface
|
eventGen event.Interface
|
||||||
|
@ -67,7 +67,8 @@ func NewValidateAuditHandler(pCache policycache.Interface,
|
||||||
crbInformer rbacinformer.ClusterRoleBindingInformer,
|
crbInformer rbacinformer.ClusterRoleBindingInformer,
|
||||||
log logr.Logger,
|
log logr.Logger,
|
||||||
dynamicConfig config.Interface,
|
dynamicConfig config.Interface,
|
||||||
resCache resourcecache.ResourceCache) AuditHandler {
|
resCache resourcecache.ResourceCache,
|
||||||
|
client *client.Client) AuditHandler {
|
||||||
|
|
||||||
return &auditHandler{
|
return &auditHandler{
|
||||||
pCache: pCache,
|
pCache: pCache,
|
||||||
|
@ -82,6 +83,7 @@ func NewValidateAuditHandler(pCache policycache.Interface,
|
||||||
prGenerator: prGenerator,
|
prGenerator: prGenerator,
|
||||||
configHandler: dynamicConfig,
|
configHandler: dynamicConfig,
|
||||||
resCache: resCache,
|
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")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package webhooks
|
package webhooks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
client "github.com/kyverno/kyverno/pkg/dclient"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
@ -36,7 +37,8 @@ func HandleValidation(
|
||||||
prGenerator policyreport.GeneratorInterface,
|
prGenerator policyreport.GeneratorInterface,
|
||||||
log logr.Logger,
|
log logr.Logger,
|
||||||
dynamicConfig config.Interface,
|
dynamicConfig config.Interface,
|
||||||
resCache resourcecache.ResourceCache) (bool, string) {
|
resCache resourcecache.ResourceCache,
|
||||||
|
client *client.Client) (bool, string) {
|
||||||
|
|
||||||
if len(policies) == 0 {
|
if len(policies) == 0 {
|
||||||
return true, ""
|
return true, ""
|
||||||
|
@ -76,6 +78,7 @@ func HandleValidation(
|
||||||
ExcludeResourceFunc: dynamicConfig.ToFilter,
|
ExcludeResourceFunc: dynamicConfig.ToFilter,
|
||||||
ResourceCache: resCache,
|
ResourceCache: resCache,
|
||||||
JSONContext: ctx,
|
JSONContext: ctx,
|
||||||
|
Client: client,
|
||||||
}
|
}
|
||||||
|
|
||||||
var engineResponses []*response.EngineResponse
|
var engineResponses []*response.EngineResponse
|
||||||
|
|
Loading…
Add table
Reference in a new issue