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

View file

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

View file

@ -58,6 +58,97 @@ spec:
- name - name
type: object type: object
type: array 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: policies:
description: Policies are the policy values description: Policies are the policy values
items: items:

View file

@ -58,6 +58,97 @@ spec:
- name - name
type: object type: object
type: array 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: policies:
description: Policies are the policy values description: Policies are the policy values
items: 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/apis/v1alpha1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/store" "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" "k8s.io/apimachinery/pkg/util/sets"
) )
@ -28,10 +30,12 @@ func (v Variables) NamespaceSelectors() map[string]Labels {
return nil return nil
} }
out := map[string]Labels{} out := map[string]Labels{}
if v.values.NamespaceSelectors != nil { for _, n := range v.values.NamespaceSelectors {
for _, n := range v.values.NamespaceSelectors { out[n.Name] = n.Labels
out[n.Name] = n.Labels }
} // Give precedence to namespaces
for _, n := range v.values.Namespaces {
out[n.Name] = n.Labels
} }
if len(out) == 0 { if len(out) == 0 {
return nil return nil
@ -39,6 +43,33 @@ func (v Variables) NamespaceSelectors() map[string]Labels {
return out 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) { 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{}{} resourceValues := map[string]interface{}{}
// first apply global values // first apply global values

View file

@ -1046,6 +1046,19 @@ map[string]interface{}
</tr> </tr>
<tr> <tr>
<td> <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/> <code>subresources</code><br/>
<em> <em>
<a href="#cli.kyverno.io/v1alpha1.Subresource"> <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> <tr>
<td><code>subresources</code> <td><code>subresources</code>

View file

@ -8,14 +8,15 @@ import (
"github.com/kyverno/kyverno/pkg/cel/policy" "github.com/kyverno/kyverno/pkg/cel/policy"
engineapi "github.com/kyverno/kyverno/pkg/engine/api" engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/handlers" "github.com/kyverno/kyverno/pkg/engine/handlers"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
corev1 "k8s.io/api/core/v1"
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"
) )
type EngineRequest struct { type EngineRequest struct {
Resource *unstructured.Unstructured Resource *unstructured.Unstructured
NamespaceLabels map[string]map[string]string
} }
type EngineResponse struct { type EngineResponse struct {
@ -86,13 +87,17 @@ type Engine interface {
Handle(context.Context, EngineRequest, ...policy.CompiledPolicy) (EngineResponse, error) Handle(context.Context, EngineRequest, ...policy.CompiledPolicy) (EngineResponse, error)
} }
type NamespaceResolver = func(string) *corev1.Namespace
type engine struct { type engine struct {
provider Provider nsResolver NamespaceResolver
provider Provider
} }
func NewEngine(provider Provider) *engine { func NewEngine(provider Provider, nsResolver NamespaceResolver) *engine {
return &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 { if err != nil {
return response, err 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 { 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 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 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 // TODO: evaluation should be per rule
if err != nil { if err != nil {
rules = handlers.WithResponses(engineapi.RuleError("todo", engineapi.Validation, "failed to load context", 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( func (p *CompiledPolicy) Evaluate(
ctx context.Context, ctx context.Context,
resource *unstructured.Unstructured, resource *unstructured.Unstructured,
namespaceLabels map[string]map[string]string, namespace *unstructured.Unstructured,
) (bool, error) { ) (bool, error) {
var nsData map[string]any
if namespace != nil {
nsData = namespace.UnstructuredContent()
}
matchConditions := func() (bool, error) { matchConditions := func() (bool, error) {
var errs []error var errs []error
data := map[string]any{ data := map[string]any{
ObjectKey: resource.UnstructuredContent(), NamespaceObjectKey: nsData,
ObjectKey: resource.UnstructuredContent(),
} }
for _, matchCondition := range p.matchConditions { for _, matchCondition := range p.matchConditions {
// evaluate the condition // evaluate the condition
@ -63,8 +68,9 @@ func (p *CompiledPolicy) Evaluate(
variables := func() map[string]any { variables := func() map[string]any {
vars := lazy.NewMapValue(VariablesType) vars := lazy.NewMapValue(VariablesType)
data := map[string]any{ data := map[string]any{
ObjectKey: resource.UnstructuredContent(), NamespaceObjectKey: nsData,
VariablesKey: vars, ObjectKey: resource.UnstructuredContent(),
VariablesKey: vars,
} }
for name, variable := range p.variables { for name, variable := range p.variables {
vars.Append(name, func(*lazy.MapValue) ref.Val { vars.Append(name, func(*lazy.MapValue) ref.Val {