1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00

feat: add namespace support in CLI values (#11958)

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2025-01-20 12:43:13 +01:00 committed by GitHub
parent 83289c7a00
commit a30fc14d4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 306 additions and 21 deletions

View file

@ -1,5 +1,9 @@
package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
)
// ValuesSpec declares values to be loaded by the Kyverno CLI
type ValuesSpec struct {
// GlobalValues are the global values
@ -14,6 +18,9 @@ type ValuesSpec struct {
// NamespaceSelectors are the namespace labels
NamespaceSelectors []NamespaceSelector `json:"namespaceSelector,omitempty"`
// Namespaces are the namespaces
Namespaces []corev1.Namespace `json:"namespaces,omitempty"`
// Subresources are the subresource/parent resource mappings
Subresources []Subresource `json:"subresources,omitempty"`
}

View file

@ -36,6 +36,7 @@ import (
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
"github.com/spf13/cobra"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -261,7 +262,7 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
if err != nil {
return rc, resources1, skippedInvalidPolicies, responses1, err
}
responses3, err := c.applyValidatingPolicies(vps, resources1, variables.NamespaceSelectors(), rc, dClient)
responses3, err := c.applyValidatingPolicies(vps, resources1, variables.Namespace, rc, dClient)
if err != nil {
return rc, resources1, skippedInvalidPolicies, responses1, err
}
@ -315,7 +316,7 @@ func (c *ApplyCommandConfig) applyValidatingAdmissionPolicies(
func (c *ApplyCommandConfig) applyValidatingPolicies(
vps []kyvernov2alpha1.ValidatingPolicy,
resources []*unstructured.Unstructured,
namespaceSelectorMap map[string]map[string]string,
namespaceProvider func(string) *corev1.Namespace,
_ *processor.ResultCounts,
_ dclient.Interface,
) ([]engineapi.EngineResponse, error) {
@ -325,12 +326,11 @@ func (c *ApplyCommandConfig) applyValidatingPolicies(
if err != nil {
return nil, err
}
eng := engine.NewEngine(provider)
eng := engine.NewEngine(provider, namespaceProvider)
responses := make([]engineapi.EngineResponse, 0)
for _, resource := range resources {
request := engine.EngineRequest{
Resource: resource,
NamespaceLabels: namespaceSelectorMap,
Resource: resource,
}
response, err := eng.Handle(ctx, request)
if err != nil {

View file

@ -58,6 +58,97 @@ spec:
- name
type: object
type: array
namespaces:
description: Namespaces are the namespaces
items:
description: |-
Namespace provides a scope for Names.
Use of multiple namespaces is optional.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
description: |-
Standard object's metadata.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
type: object
spec:
description: |-
Spec defines the behavior of the Namespace.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
properties:
finalizers:
description: |-
Finalizers is an opaque list of values that must be empty to permanently remove object from storage.
More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/
items:
description: FinalizerName is the name identifying a finalizer
during namespace lifecycle.
type: string
type: array
x-kubernetes-list-type: atomic
type: object
status:
description: |-
Status describes the current status of a Namespace.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
properties:
conditions:
description: Represents the latest available observations of
a namespace's current state.
items:
description: NamespaceCondition contains details about state
of namespace.
properties:
lastTransitionTime:
description: Last time the condition transitioned from
one status to another.
format: date-time
type: string
message:
description: Human-readable message indicating details
about last transition.
type: string
reason:
description: Unique, one-word, CamelCase reason for the
condition's last transition.
type: string
status:
description: Status of the condition, one of True, False,
Unknown.
type: string
type:
description: Type of namespace controller condition.
type: string
required:
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
description: |-
Phase is the current lifecycle phase of the namespace.
More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/
type: string
type: object
type: object
type: array
policies:
description: Policies are the policy values
items:

View file

@ -58,6 +58,97 @@ spec:
- name
type: object
type: array
namespaces:
description: Namespaces are the namespaces
items:
description: |-
Namespace provides a scope for Names.
Use of multiple namespaces is optional.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
description: |-
Standard object's metadata.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
type: object
spec:
description: |-
Spec defines the behavior of the Namespace.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
properties:
finalizers:
description: |-
Finalizers is an opaque list of values that must be empty to permanently remove object from storage.
More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/
items:
description: FinalizerName is the name identifying a finalizer
during namespace lifecycle.
type: string
type: array
x-kubernetes-list-type: atomic
type: object
status:
description: |-
Status describes the current status of a Namespace.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
properties:
conditions:
description: Represents the latest available observations of
a namespace's current state.
items:
description: NamespaceCondition contains details about state
of namespace.
properties:
lastTransitionTime:
description: Last time the condition transitioned from
one status to another.
format: date-time
type: string
message:
description: Human-readable message indicating details
about last transition.
type: string
reason:
description: Unique, one-word, CamelCase reason for the
condition's last transition.
type: string
status:
description: Status of the condition, one of True, False,
Unknown.
type: string
type:
description: Type of namespace controller condition.
type: string
required:
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
description: |-
Phase is the current lifecycle phase of the namespace.
More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/
type: string
type: object
type: object
type: array
policies:
description: Policies are the policy values
items:

View file

@ -5,6 +5,8 @@ import (
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/v1alpha1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/store"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
)
@ -28,10 +30,12 @@ func (v Variables) NamespaceSelectors() map[string]Labels {
return nil
}
out := map[string]Labels{}
if v.values.NamespaceSelectors != nil {
for _, n := range v.values.NamespaceSelectors {
out[n.Name] = n.Labels
}
for _, n := range v.values.NamespaceSelectors {
out[n.Name] = n.Labels
}
// Give precedence to namespaces
for _, n := range v.values.Namespaces {
out[n.Name] = n.Labels
}
if len(out) == 0 {
return nil
@ -39,6 +43,33 @@ func (v Variables) NamespaceSelectors() map[string]Labels {
return out
}
func (v Variables) Namespace(name string) *corev1.Namespace {
if v.values == nil {
return nil
}
// Give precedence to namespaces
for _, n := range v.values.Namespaces {
if n.Name == name {
return &n
}
}
for _, n := range v.values.NamespaceSelectors {
if n.Name == name {
return &corev1.Namespace{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Namespace",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: n.Labels,
},
}
}
}
return nil
}
func (v Variables) ComputeVariables(s *store.Store, policy, resource, kind string, kindMap sets.Set[string], variables ...string) (map[string]interface{}, error) {
resourceValues := map[string]interface{}{}
// first apply global values

View file

@ -1046,6 +1046,19 @@ map[string]interface{}
</tr>
<tr>
<td>
<code>namespaces</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#namespace-v1-core">
[]Kubernetes core/v1.Namespace
</a>
</em>
</td>
<td>
<p>Namespaces are the namespaces</p>
</td>
</tr>
<tr>
<td>
<code>subresources</code><br/>
<em>
<a href="#cli.kyverno.io/v1alpha1.Subresource">

View file

@ -2080,6 +2080,35 @@ This is DEPRECATED, Use <code>patchedResources</code> instead.</p>
<tr>
<td><code>namespaces</code>
<span style="color:blue;"> *</span>
</br>
<span style="font-family: monospace">[]core/v1.Namespace</span>
</td>
<td>
<p>Namespaces are the namespaces</p>
</td>
</tr>
<tr>
<td><code>subresources</code>

View file

@ -8,14 +8,15 @@ import (
"github.com/kyverno/kyverno/pkg/cel/policy"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type EngineRequest struct {
Resource *unstructured.Unstructured
NamespaceLabels map[string]map[string]string
Resource *unstructured.Unstructured
}
type EngineResponse struct {
@ -86,13 +87,17 @@ type Engine interface {
Handle(context.Context, EngineRequest, ...policy.CompiledPolicy) (EngineResponse, error)
}
type NamespaceResolver = func(string) *corev1.Namespace
type engine struct {
provider Provider
nsResolver NamespaceResolver
provider Provider
}
func NewEngine(provider Provider) *engine {
func NewEngine(provider Provider, nsResolver NamespaceResolver) *engine {
return &engine{
provider: provider,
nsResolver: nsResolver,
provider: provider,
}
}
@ -104,15 +109,27 @@ func (e *engine) Handle(ctx context.Context, request EngineRequest) (EngineRespo
if err != nil {
return response, err
}
// resolve namespace
var namespace *unstructured.Unstructured
if ns := request.Resource.GetNamespace(); ns != "" {
coreNs := e.nsResolver(ns)
if coreNs != nil {
ns, err := kubeutils.ObjToUnstructured(coreNs)
if err != nil {
return response, err
}
namespace = ns
}
}
for _, policy := range policies {
response.Policies = append(response.Policies, e.handlePolicy(ctx, policy, request))
response.Policies = append(response.Policies, e.handlePolicy(ctx, policy, request.Resource, namespace))
}
return response, nil
}
func (e *engine) handlePolicy(ctx context.Context, policy policy.CompiledPolicy, request EngineRequest) PolicyResponse {
func (e *engine) handlePolicy(ctx context.Context, policy policy.CompiledPolicy, resource *unstructured.Unstructured, namespace *unstructured.Unstructured) PolicyResponse {
var rules []engineapi.RuleResponse
ok, err := policy.Evaluate(ctx, request.Resource, request.NamespaceLabels)
ok, err := policy.Evaluate(ctx, resource, namespace)
// TODO: evaluation should be per rule
if err != nil {
rules = handlers.WithResponses(engineapi.RuleError("todo", engineapi.Validation, "failed to load context", err, nil))

View file

@ -24,12 +24,17 @@ type CompiledPolicy struct {
func (p *CompiledPolicy) Evaluate(
ctx context.Context,
resource *unstructured.Unstructured,
namespaceLabels map[string]map[string]string,
namespace *unstructured.Unstructured,
) (bool, error) {
var nsData map[string]any
if namespace != nil {
nsData = namespace.UnstructuredContent()
}
matchConditions := func() (bool, error) {
var errs []error
data := map[string]any{
ObjectKey: resource.UnstructuredContent(),
NamespaceObjectKey: nsData,
ObjectKey: resource.UnstructuredContent(),
}
for _, matchCondition := range p.matchConditions {
// evaluate the condition
@ -63,8 +68,9 @@ func (p *CompiledPolicy) Evaluate(
variables := func() map[string]any {
vars := lazy.NewMapValue(VariablesType)
data := map[string]any{
ObjectKey: resource.UnstructuredContent(),
VariablesKey: vars,
NamespaceObjectKey: nsData,
ObjectKey: resource.UnstructuredContent(),
VariablesKey: vars,
}
for name, variable := range p.variables {
vars.Append(name, func(*lazy.MapValue) ref.Val {