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

feat(cli,apply): load validating policies (#11933)

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2025-01-17 10:53:17 +01:00 committed by GitHub
parent 97ed53f6bb
commit 7351501ef6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 203 additions and 10 deletions

View file

@ -13,6 +13,7 @@ import (
"github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/memfs"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2" kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/command" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/command"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/deprecations" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/deprecations"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/exception" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/exception"
@ -26,6 +27,8 @@ import (
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/variables" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/variables"
"github.com/kyverno/kyverno/pkg/autogen" "github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/cel/engine"
celpolicy "github.com/kyverno/kyverno/pkg/cel/policy"
"github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api" engineapi "github.com/kyverno/kyverno/pkg/engine/api"
@ -196,7 +199,7 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
return nil, nil, skippedInvalidPolicies, nil, fmt.Errorf("failed to decode yaml (%w)", err) return nil, nil, skippedInvalidPolicies, nil, fmt.Errorf("failed to decode yaml (%w)", err)
} }
var store store.Store var store store.Store
policies, vaps, vapBindings, err := c.loadPolicies() policies, vaps, vapBindings, vps, err := c.loadPolicies()
if err != nil { if err != nil {
return nil, nil, skippedInvalidPolicies, nil, err return nil, nil, skippedInvalidPolicies, nil, err
} }
@ -229,14 +232,17 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
for _, policy := range policies { for _, policy := range policies {
policyRulesCount += len(autogen.Default.ComputeRules(policy, "")) policyRulesCount += len(autogen.Default.ComputeRules(policy, ""))
} }
// account for vaps
policyRulesCount += len(vaps) policyRulesCount += len(vaps)
// account for vps
policyRulesCount += len(vps)
if len(exceptions) > 0 { if len(exceptions) > 0 {
fmt.Fprintf(out, "\nApplying %d policy rule(s) to %d resource(s) with %d exception(s)...\n", policyRulesCount, len(resources), len(exceptions)) fmt.Fprintf(out, "\nApplying %d policy rule(s) to %d resource(s) with %d exception(s)...\n", policyRulesCount, len(resources), len(exceptions))
} else { } else {
fmt.Fprintf(out, "\nApplying %d policy rule(s) to %d resource(s)...\n", policyRulesCount, len(resources)) fmt.Fprintf(out, "\nApplying %d policy rule(s) to %d resource(s)...\n", policyRulesCount, len(resources))
} }
} }
rc, resources1, responses1, err := c.applyPolicytoResource( rc, resources1, responses1, err := c.applyPolicies(
out, out,
&store, &store,
variables, variables,
@ -251,13 +257,18 @@ 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
} }
responses2, err := c.applyValidatingAdmissionPolicytoResource(vaps, vapBindings, resources1, variables.NamespaceSelectors(), rc, dClient) responses2, err := c.applyValidatingAdmissionPolicies(vaps, vapBindings, resources1, variables.NamespaceSelectors(), rc, dClient)
if err != nil {
return rc, resources1, skippedInvalidPolicies, responses1, err
}
responses3, err := c.applyValidatingPolicies(vps, resources1, variables.NamespaceSelectors(), rc, dClient)
if err != nil { if err != nil {
return rc, resources1, skippedInvalidPolicies, responses1, err return rc, resources1, skippedInvalidPolicies, responses1, err
} }
var responses []engineapi.EngineResponse var responses []engineapi.EngineResponse
responses = append(responses, responses1...) responses = append(responses, responses1...)
responses = append(responses, responses2...) responses = append(responses, responses2...)
responses = append(responses, responses3...)
return rc, resources1, skippedInvalidPolicies, responses, nil return rc, resources1, skippedInvalidPolicies, responses, nil
} }
@ -269,7 +280,7 @@ func (c *ApplyCommandConfig) getMutateLogPathIsDir() (bool, error) {
return mutateLogPathIsDir, nil return mutateLogPathIsDir, nil
} }
func (c *ApplyCommandConfig) applyValidatingAdmissionPolicytoResource( func (c *ApplyCommandConfig) applyValidatingAdmissionPolicies(
vaps []admissionregistrationv1beta1.ValidatingAdmissionPolicy, vaps []admissionregistrationv1beta1.ValidatingAdmissionPolicy,
vapBindings []admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding, vapBindings []admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding,
resources []*unstructured.Unstructured, resources []*unstructured.Unstructured,
@ -301,7 +312,50 @@ func (c *ApplyCommandConfig) applyValidatingAdmissionPolicytoResource(
return responses, nil return responses, nil
} }
func (c *ApplyCommandConfig) applyPolicytoResource( func (c *ApplyCommandConfig) applyValidatingPolicies(
vps []kyvernov2alpha1.ValidatingPolicy,
resources []*unstructured.Unstructured,
namespaceSelectorMap map[string]map[string]string,
_ *processor.ResultCounts,
_ dclient.Interface,
) ([]engineapi.EngineResponse, error) {
ctx := context.TODO()
compiler := celpolicy.NewCompiler()
policies := make([]celpolicy.CompiledPolicy, 0, len(vps))
for _, vp := range vps {
policy, err := compiler.Compile(&vp)
if err != nil {
return nil, fmt.Errorf("failed to compile policy %s (%w)", vp.GetName(), err.ToAggregate())
}
policies = append(policies, *policy)
}
eng := engine.NewEngine()
var responses []engineapi.EngineResponse
for _, resource := range resources {
request := engine.EngineRequest{
Resource: resource,
NamespaceLabels: namespaceSelectorMap,
}
_, err := eng.Handle(ctx, request, policies...)
if err != nil {
if c.ContinueOnFail {
fmt.Printf("failed to apply validating policies on resource %s (%v)\n", resource.GetName(), err)
continue
}
return responses, fmt.Errorf("failed to apply validating policies on resource %s (%w)", resource.GetName(), err)
}
// TODO
// processor := processor.ValidatingAdmissionPolicyProcessor{
// PolicyReport: c.PolicyReport,
// Rc: rc,
// Client: dClient,
// }
// responses = append(responses, ers...)
}
return responses, nil
}
func (c *ApplyCommandConfig) applyPolicies(
out io.Writer, out io.Writer,
store *store.Store, store *store.Store,
vars *variables.Variables, vars *variables.Variables,
@ -388,24 +442,26 @@ func (c *ApplyCommandConfig) loadPolicies() (
[]kyvernov1.PolicyInterface, []kyvernov1.PolicyInterface,
[]admissionregistrationv1beta1.ValidatingAdmissionPolicy, []admissionregistrationv1beta1.ValidatingAdmissionPolicy,
[]admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding, []admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding,
[]kyvernov2alpha1.ValidatingPolicy,
error, error,
) { ) {
// load policies // load policies
var policies []kyvernov1.PolicyInterface var policies []kyvernov1.PolicyInterface
var vaps []admissionregistrationv1beta1.ValidatingAdmissionPolicy var vaps []admissionregistrationv1beta1.ValidatingAdmissionPolicy
var vapBindings []admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding var vapBindings []admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding
var vps []kyvernov2alpha1.ValidatingPolicy
for _, path := range c.PolicyPaths { for _, path := range c.PolicyPaths {
isGit := source.IsGit(path) isGit := source.IsGit(path)
if isGit { if isGit {
gitSourceURL, err := url.Parse(path) gitSourceURL, err := url.Parse(path)
if err != nil { if err != nil {
return nil, nil, nil, fmt.Errorf("failed to load policies (%w)", err) return nil, nil, nil, nil, fmt.Errorf("failed to load policies (%w)", err)
} }
pathElems := strings.Split(gitSourceURL.Path[1:], "/") pathElems := strings.Split(gitSourceURL.Path[1:], "/")
if len(pathElems) <= 1 { if len(pathElems) <= 1 {
err := fmt.Errorf("invalid URL path %s - expected https://<any_git_source_domain>/:owner/:repository/:branch (without --git-branch flag) OR https://<any_git_source_domain>/:owner/:repository/:directory (with --git-branch flag)", gitSourceURL.Path) err := fmt.Errorf("invalid URL path %s - expected https://<any_git_source_domain>/:owner/:repository/:branch (without --git-branch flag) OR https://<any_git_source_domain>/:owner/:repository/:directory (with --git-branch flag)", gitSourceURL.Path)
return nil, nil, nil, fmt.Errorf("failed to parse URL (%w)", err) return nil, nil, nil, nil, fmt.Errorf("failed to parse URL (%w)", err)
} }
gitSourceURL.Path = strings.Join([]string{pathElems[0], pathElems[1]}, "/") gitSourceURL.Path = strings.Join([]string{pathElems[0], pathElems[1]}, "/")
repoURL := gitSourceURL.String() repoURL := gitSourceURL.String()
@ -414,11 +470,11 @@ func (c *ApplyCommandConfig) loadPolicies() (
fs := memfs.New() fs := memfs.New()
if _, err := gitutils.Clone(repoURL, fs, c.GitBranch); err != nil { if _, err := gitutils.Clone(repoURL, fs, c.GitBranch); err != nil {
log.Log.V(3).Info(fmt.Sprintf("failed to clone repository %v as it is not valid", repoURL), "error", err) log.Log.V(3).Info(fmt.Sprintf("failed to clone repository %v as it is not valid", repoURL), "error", err)
return nil, nil, nil, fmt.Errorf("failed to clone repository (%w)", err) return nil, nil, nil, nil, fmt.Errorf("failed to clone repository (%w)", err)
} }
policyYamls, err := gitutils.ListYamls(fs, gitPathToYamls) policyYamls, err := gitutils.ListYamls(fs, gitPathToYamls)
if err != nil { if err != nil {
return nil, nil, nil, fmt.Errorf("failed to list YAMLs in repository (%w)", err) return nil, nil, nil, nil, fmt.Errorf("failed to list YAMLs in repository (%w)", err)
} }
for _, policyYaml := range policyYamls { for _, policyYaml := range policyYamls {
loaderResults, err := policy.Load(fs, "", policyYaml) loaderResults, err := policy.Load(fs, "", policyYaml)
@ -428,6 +484,7 @@ func (c *ApplyCommandConfig) loadPolicies() (
policies = append(policies, loaderResults.Policies...) policies = append(policies, loaderResults.Policies...)
vaps = append(vaps, loaderResults.VAPs...) vaps = append(vaps, loaderResults.VAPs...)
vapBindings = append(vapBindings, loaderResults.VAPBindings...) vapBindings = append(vapBindings, loaderResults.VAPBindings...)
vps = append(vps, loaderResults.ValidatingPolicies...)
} }
} else { } else {
loaderResults, err := policy.Load(nil, "", path) loaderResults, err := policy.Load(nil, "", path)
@ -437,6 +494,7 @@ func (c *ApplyCommandConfig) loadPolicies() (
policies = append(policies, loaderResults.Policies...) policies = append(policies, loaderResults.Policies...)
vaps = append(vaps, loaderResults.VAPs...) vaps = append(vaps, loaderResults.VAPs...)
vapBindings = append(vapBindings, loaderResults.VAPBindings...) vapBindings = append(vapBindings, loaderResults.VAPBindings...)
vps = append(vps, loaderResults.ValidatingPolicies...)
} }
} }
for _, policy := range policies { for _, policy := range policies {
@ -445,7 +503,7 @@ func (c *ApplyCommandConfig) loadPolicies() (
} }
} }
} }
return policies, vaps, vapBindings, nil return policies, vaps, vapBindings, vps, nil
} }
func (c *ApplyCommandConfig) initStoreAndClusterClient(store *store.Store, targetResources ...*unstructured.Unstructured) ( func (c *ApplyCommandConfig) initStoreAndClusterClient(store *store.Store, targetResources ...*unstructured.Unstructured) (

37
pkg/cel/engine/engine.go Normal file
View file

@ -0,0 +1,37 @@
package engine
import (
"context"
"github.com/kyverno/kyverno/pkg/cel/policy"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type EngineRequest struct {
Resource *unstructured.Unstructured
NamespaceLabels map[string]map[string]string
}
type EngineResponse struct{}
type Engine interface {
Handle(context.Context, EngineRequest, ...policy.CompiledPolicy) (EngineResponse, error)
}
type engine struct{}
func NewEngine() *engine {
return &engine{}
}
func (e *engine) Handle(ctx context.Context, request EngineRequest, policies ...policy.CompiledPolicy) (EngineResponse, error) {
var response EngineResponse
for _, policy := range policies {
// TODO
_, err := policy.Evaluate(ctx, request.Resource, request.NamespaceLabels)
if err != nil {
return response, nil
}
}
return response, nil
}

View file

@ -0,0 +1,11 @@
package engine
import (
"context"
"github.com/kyverno/kyverno/pkg/cel/policy"
)
type Provider interface {
CompiledPolicies(context.Context) ([]policy.CompiledPolicy, error)
}

View file

@ -1,8 +1,16 @@
package policy package policy
import ( import (
"context"
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/kyverno/kyverno/pkg/cel/utils"
"go.uber.org/multierr"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apiserver/pkg/cel/lazy"
) )
type CompiledPolicy struct { type CompiledPolicy struct {
@ -12,3 +20,82 @@ type CompiledPolicy struct {
validations []cel.Program validations []cel.Program
auditAnnotations map[string]cel.Program auditAnnotations map[string]cel.Program
} }
func (p *CompiledPolicy) Evaluate(
ctx context.Context,
resource *unstructured.Unstructured,
namespaceLabels map[string]map[string]string,
) (bool, error) {
matchConditions := func() (bool, error) {
var errs []error
data := map[string]any{
ObjectKey: resource.UnstructuredContent(),
}
for _, matchCondition := range p.matchConditions {
// evaluate the condition
out, _, err := matchCondition.ContextEval(ctx, data)
// check error
if err != nil {
errs = append(errs, err)
continue
}
// try to convert to a bool
result, err := utils.ConvertToNative[bool](out)
// check error
if err != nil {
errs = append(errs, err)
continue
}
// if condition is false, skip
if !result {
return false, nil
}
}
return true, multierr.Combine(errs...)
}
match, err := matchConditions()
if err != nil {
return false, err
}
if !match {
return true, nil
}
variables := func() map[string]any {
vars := lazy.NewMapValue(VariablesType)
data := map[string]any{
ObjectKey: resource.UnstructuredContent(),
VariablesKey: vars,
}
for name, variable := range p.variables {
vars.Append(name, func(*lazy.MapValue) ref.Val {
out, _, err := variable.Eval(data)
if out != nil {
return out
}
if err != nil {
return types.WrapErr(err)
}
return nil
})
}
return data
}
data := variables()
for _, rule := range p.validations {
out, _, err := rule.Eval(data)
// check error
if err != nil {
return false, err
}
response, err := utils.ConvertToNative[bool](out)
// check error
if err != nil {
return false, err
}
// if response is false, return
if !response {
return false, nil
}
}
return true, nil
}