mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-23 00:01:55 +00:00
feat: support json payload via CLI apply command (#12296)
* chore: remove unused code Signed-off-by: ShutingZhao <shuting@nirmata.com> * feat: support json in CLI apply command Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: remove not used validation expressions Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: update codegen docs Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: add unit tests Signed-off-by: ShutingZhao <shuting@nirmata.com> --------- Signed-off-by: ShutingZhao <shuting@nirmata.com>
This commit is contained in:
parent
0bcc850d77
commit
637f756994
9 changed files with 327 additions and 61 deletions
|
@ -11,6 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5/memfs"
|
"github.com/go-git/go-billy/v5/memfs"
|
||||||
|
"github.com/kyverno/kyverno-json/pkg/payload"
|
||||||
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"
|
||||||
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
||||||
|
@ -79,6 +80,7 @@ type ApplyCommandConfig struct {
|
||||||
inlineExceptions bool
|
inlineExceptions bool
|
||||||
GenerateExceptions bool
|
GenerateExceptions bool
|
||||||
GeneratedExceptionTTL time.Duration
|
GeneratedExceptionTTL time.Duration
|
||||||
|
JSONPaths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Command() *cobra.Command {
|
func Command() *cobra.Command {
|
||||||
|
@ -110,6 +112,9 @@ func Command() *cobra.Command {
|
||||||
for _, response := range responses {
|
for _, response := range responses {
|
||||||
var failedRules []engineapi.RuleResponse
|
var failedRules []engineapi.RuleResponse
|
||||||
resPath := fmt.Sprintf("%s/%s/%s", response.Resource.GetNamespace(), response.Resource.GetKind(), response.Resource.GetName())
|
resPath := fmt.Sprintf("%s/%s/%s", response.Resource.GetNamespace(), response.Resource.GetKind(), response.Resource.GetName())
|
||||||
|
if resPath == "//" {
|
||||||
|
resPath = "JSON payload"
|
||||||
|
}
|
||||||
for _, rule := range response.PolicyResponse.Rules {
|
for _, rule := range response.PolicyResponse.Rules {
|
||||||
if rule.Status() == engineapi.RuleStatusFail {
|
if rule.Status() == engineapi.RuleStatusFail {
|
||||||
failedRules = append(failedRules, rule)
|
failedRules = append(failedRules, rule)
|
||||||
|
@ -143,6 +148,8 @@ func Command() *cobra.Command {
|
||||||
return exit(out, rc, applyCommandConfig.warnExitCode, applyCommandConfig.warnNoPassed)
|
return exit(out, rc, applyCommandConfig.warnExitCode, applyCommandConfig.warnNoPassed)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringSliceVarP(&applyCommandConfig.JSONPaths, "json", "", []string{}, "Path to JSON payload files")
|
||||||
cmd.Flags().StringSliceVarP(&applyCommandConfig.ResourcePaths, "resource", "r", []string{}, "Path to resource files")
|
cmd.Flags().StringSliceVarP(&applyCommandConfig.ResourcePaths, "resource", "r", []string{}, "Path to resource files")
|
||||||
cmd.Flags().StringSliceVarP(&applyCommandConfig.ResourcePaths, "resources", "", []string{}, "Path to resource files")
|
cmd.Flags().StringSliceVarP(&applyCommandConfig.ResourcePaths, "resources", "", []string{}, "Path to resource files")
|
||||||
cmd.Flags().StringSliceVarP(&applyCommandConfig.TargetResourcePaths, "target-resource", "", []string{}, "Path to individual files containing target resources files for policies that have mutate existing")
|
cmd.Flags().StringSliceVarP(&applyCommandConfig.TargetResourcePaths, "target-resource", "", []string{}, "Path to individual files containing target resources files for policies that have mutate existing")
|
||||||
|
@ -209,7 +216,7 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
|
||||||
}
|
}
|
||||||
var targetResources []*unstructured.Unstructured
|
var targetResources []*unstructured.Unstructured
|
||||||
if len(c.TargetResourcePaths) > 0 {
|
if len(c.TargetResourcePaths) > 0 {
|
||||||
targetResources, err = c.loadResources(out, c.TargetResourcePaths, policies, vaps, nil)
|
targetResources, _, err = c.loadResources(out, c.TargetResourcePaths, policies, vaps, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, skippedInvalidPolicies, nil, err
|
return nil, nil, skippedInvalidPolicies, nil, err
|
||||||
}
|
}
|
||||||
|
@ -218,7 +225,7 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, skippedInvalidPolicies, nil, err
|
return nil, nil, skippedInvalidPolicies, nil, err
|
||||||
}
|
}
|
||||||
resources, err := c.loadResources(out, c.ResourcePaths, policies, vaps, dClient)
|
resources, jsonPayloads, err := c.loadResources(out, c.ResourcePaths, policies, vaps, dClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, skippedInvalidPolicies, nil, err
|
return nil, nil, skippedInvalidPolicies, nil, err
|
||||||
}
|
}
|
||||||
|
@ -247,10 +254,11 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
|
||||||
policyRulesCount += len(vps)
|
policyRulesCount += len(vps)
|
||||||
exceptionsCount := len(exceptions)
|
exceptionsCount := len(exceptions)
|
||||||
exceptionsCount += len(celexceptions)
|
exceptionsCount += len(celexceptions)
|
||||||
|
resourceCount := len(resources) + len(jsonPayloads)
|
||||||
if exceptionsCount > 0 {
|
if exceptionsCount > 0 {
|
||||||
fmt.Fprintf(out, "\nApplying %d policy rule(s) to %d resource(s) with %d exception(s)...\n", policyRulesCount, len(resources), exceptionsCount)
|
fmt.Fprintf(out, "\nApplying %d policy rule(s) to %d resource(s) with %d exception(s)...\n", policyRulesCount, resourceCount, exceptionsCount)
|
||||||
} 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, resourceCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rc, resources1, responses1, err := c.applyPolicies(
|
rc, resources1, responses1, err := c.applyPolicies(
|
||||||
|
@ -272,7 +280,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, celexceptions, resources1, variables.Namespace, rc, dClient)
|
responses3, err := c.applyValidatingPolicies(vps, jsonPayloads, celexceptions, resources1, variables.Namespace, rc, dClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rc, resources1, skippedInvalidPolicies, responses1, err
|
return rc, resources1, skippedInvalidPolicies, responses1, err
|
||||||
}
|
}
|
||||||
|
@ -325,6 +333,7 @@ func (c *ApplyCommandConfig) applyValidatingAdmissionPolicies(
|
||||||
|
|
||||||
func (c *ApplyCommandConfig) applyValidatingPolicies(
|
func (c *ApplyCommandConfig) applyValidatingPolicies(
|
||||||
vps []policiesv1alpha1.ValidatingPolicy,
|
vps []policiesv1alpha1.ValidatingPolicy,
|
||||||
|
jsonPayloads []*unstructured.Unstructured,
|
||||||
exceptions []*policiesv1alpha1.CELPolicyException,
|
exceptions []*policiesv1alpha1.CELPolicyException,
|
||||||
resources []*unstructured.Unstructured,
|
resources []*unstructured.Unstructured,
|
||||||
namespaceProvider func(string) *corev1.Namespace,
|
namespaceProvider func(string) *corev1.Namespace,
|
||||||
|
@ -355,6 +364,7 @@ func (c *ApplyCommandConfig) applyValidatingPolicies(
|
||||||
}
|
}
|
||||||
restMapper := restmapper.NewDiscoveryRESTMapper(apiGroupResources)
|
restMapper := restmapper.NewDiscoveryRESTMapper(apiGroupResources)
|
||||||
responses := make([]engineapi.EngineResponse, 0)
|
responses := make([]engineapi.EngineResponse, 0)
|
||||||
|
responsesTemp := make([]engine.EngineResponse, 0)
|
||||||
for _, resource := range resources {
|
for _, resource := range resources {
|
||||||
// get gvk from resource
|
// get gvk from resource
|
||||||
gvk := resource.GroupVersionKind()
|
gvk := resource.GroupVersionKind()
|
||||||
|
@ -384,7 +394,7 @@ func (c *ApplyCommandConfig) applyValidatingPolicies(
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
response, err := eng.Handle(ctx, request)
|
reps, err := eng.Handle(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.ContinueOnFail {
|
if c.ContinueOnFail {
|
||||||
fmt.Printf("failed to apply validating policies on resource %s (%v)\n", resource.GetName(), err)
|
fmt.Printf("failed to apply validating policies on resource %s (%v)\n", resource.GetName(), err)
|
||||||
|
@ -392,7 +402,25 @@ func (c *ApplyCommandConfig) applyValidatingPolicies(
|
||||||
}
|
}
|
||||||
return responses, fmt.Errorf("failed to apply validating policies on resource %s (%w)", resource.GetName(), err)
|
return responses, fmt.Errorf("failed to apply validating policies on resource %s (%w)", resource.GetName(), err)
|
||||||
}
|
}
|
||||||
// transform response into legacy engine responses
|
responsesTemp = append(responsesTemp, reps)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, json := range jsonPayloads {
|
||||||
|
eng = engine.NewEngine(provider, nil, nil)
|
||||||
|
request := engine.RequestFromJSON(contextProvider, json)
|
||||||
|
reps, err := eng.Handle(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
if c.ContinueOnFail {
|
||||||
|
fmt.Printf("failed to apply validating policies on JSON payloads (%v)\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return responses, fmt.Errorf("failed to apply validating policies on JSON payloads (%w)", err)
|
||||||
|
}
|
||||||
|
responsesTemp = append(responsesTemp, reps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// transform response into legacy engine responses
|
||||||
|
for _, response := range responsesTemp {
|
||||||
for _, r := range response.Policies {
|
for _, r := range response.Policies {
|
||||||
engineResponse := engineapi.EngineResponse{
|
engineResponse := engineapi.EngineResponse{
|
||||||
Resource: *response.Resource,
|
Resource: *response.Resource,
|
||||||
|
@ -482,12 +510,24 @@ func (c *ApplyCommandConfig) applyPolicies(
|
||||||
return &rc, resources, responses, nil
|
return &rc, resources, responses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApplyCommandConfig) loadResources(out io.Writer, paths []string, policies []kyvernov1.PolicyInterface, vap []admissionregistrationv1.ValidatingAdmissionPolicy, dClient dclient.Interface) ([]*unstructured.Unstructured, error) {
|
func (c *ApplyCommandConfig) loadResources(out io.Writer, paths []string, policies []kyvernov1.PolicyInterface, vap []admissionregistrationv1.ValidatingAdmissionPolicy, dClient dclient.Interface) ([]*unstructured.Unstructured, []*unstructured.Unstructured, error) {
|
||||||
resources, err := common.GetResourceAccordingToResourcePath(out, nil, paths, c.Cluster, policies, vap, dClient, c.Namespace, c.PolicyReport, "")
|
resources, err := common.GetResourceAccordingToResourcePath(out, nil, paths, c.Cluster, policies, vap, dClient, c.Namespace, c.PolicyReport, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resources, fmt.Errorf("failed to load resources (%w)", err)
|
return resources, nil, fmt.Errorf("failed to load resources (%w)", err)
|
||||||
}
|
}
|
||||||
return resources, nil
|
|
||||||
|
var jsonPayloads []*unstructured.Unstructured
|
||||||
|
if len(c.JSONPaths) > 0 {
|
||||||
|
for _, path := range c.JSONPaths {
|
||||||
|
payload, err := payload.Load(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to load JSON payload (%w)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonPayloads = append(jsonPayloads, &unstructured.Unstructured{Object: payload.(map[string]interface{})})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resources, jsonPayloads, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApplyCommandConfig) loadPolicies() (
|
func (c *ApplyCommandConfig) loadPolicies() (
|
||||||
|
@ -632,7 +672,7 @@ func (c *ApplyCommandConfig) checkArguments() error {
|
||||||
if (len(c.PolicyPaths) > 0 && c.PolicyPaths[0] == "-") && len(c.ResourcePaths) > 0 && c.ResourcePaths[0] == "-" {
|
if (len(c.PolicyPaths) > 0 && c.PolicyPaths[0] == "-") && len(c.ResourcePaths) > 0 && c.ResourcePaths[0] == "-" {
|
||||||
return fmt.Errorf("a stdin pipe can be used for either policies or resources, not both")
|
return fmt.Errorf("a stdin pipe can be used for either policies or resources, not both")
|
||||||
}
|
}
|
||||||
if len(c.ResourcePaths) == 0 && !c.Cluster {
|
if len(c.ResourcePaths) == 0 && !c.Cluster && len(c.JSONPaths) == 0 {
|
||||||
return fmt.Errorf("resource file(s) or cluster required")
|
return fmt.Errorf("resource file(s) or cluster required")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -145,24 +145,6 @@ func Test_Apply(t *testing.T) {
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// // TODO
|
|
||||||
// config: ApplyCommandConfig{
|
|
||||||
// PolicyPaths: []string{"https://github.com/kyverno/policies/openshift/team-validate-ns-name/"},
|
|
||||||
// ResourcePaths: []string{"../../../../../test/openshift/team-validate-ns-name.yaml"},
|
|
||||||
// GitBranch: "main",
|
|
||||||
// PolicyReport: true,
|
|
||||||
// },
|
|
||||||
// expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{
|
|
||||||
// Summary: policyreportv1alpha2.PolicyReportSummary{
|
|
||||||
// Pass: 2,
|
|
||||||
// Fail: 0,
|
|
||||||
// Skip: 0,
|
|
||||||
// Error: 0,
|
|
||||||
// Warn: 0,
|
|
||||||
// },
|
|
||||||
// }},
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
config: ApplyCommandConfig{
|
config: ApplyCommandConfig{
|
||||||
PolicyPaths: []string{"../../../../../test/cli/apply/policies-set"},
|
PolicyPaths: []string{"../../../../../test/cli/apply/policies-set"},
|
||||||
|
@ -537,6 +519,22 @@ func Test_Apply_ValidatingPolicies(t *testing.T) {
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
config: ApplyCommandConfig{
|
||||||
|
PolicyPaths: []string{"../../../../../test/cli/test-validating-policy/json-check-dockerfile/policy.yaml"},
|
||||||
|
JSONPaths: []string{"../../../../../test/cli/test-validating-policy/json-check-dockerfile/payload.json"},
|
||||||
|
PolicyReport: true,
|
||||||
|
},
|
||||||
|
expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{
|
||||||
|
Summary: policyreportv1alpha2.PolicyReportSummary{
|
||||||
|
Pass: 1,
|
||||||
|
Fail: 1,
|
||||||
|
Skip: 0,
|
||||||
|
Error: 0,
|
||||||
|
Warn: 0,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
config: ApplyCommandConfig{
|
config: ApplyCommandConfig{
|
||||||
PolicyPaths: []string{"../../../../../test/cli/test-cel-exceptions/check-deployment-labels/policy.yaml"},
|
PolicyPaths: []string{"../../../../../test/cli/test-cel-exceptions/check-deployment-labels/policy.yaml"},
|
||||||
|
@ -610,7 +608,7 @@ func Test_Apply_ValidatingPolicies(t *testing.T) {
|
||||||
_ = input.Close()
|
_ = input.Close()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
desc := fmt.Sprintf("Policies: [%s], / Resources: [%s]", strings.Join(tc.config.PolicyPaths, ","), strings.Join(tc.config.ResourcePaths, ","))
|
desc := fmt.Sprintf("Policies: [%s], / Resources: [%s], JSON payload: [%s]", strings.Join(tc.config.PolicyPaths, ","), strings.Join(tc.config.ResourcePaths, ","), strings.Join(tc.config.JSONPaths, ","))
|
||||||
|
|
||||||
_, _, _, responses, err := tc.config.applyCommandHelper(os.Stdout)
|
_, _, _, responses, err := tc.config.applyCommandHelper(os.Stdout)
|
||||||
assert.NoError(t, err, desc)
|
assert.NoError(t, err, desc)
|
||||||
|
|
|
@ -37,7 +37,7 @@ func GetResourceAccordingToResourcePath(
|
||||||
policyResourcePath string,
|
policyResourcePath string,
|
||||||
) (resources []*unstructured.Unstructured, err error) {
|
) (resources []*unstructured.Unstructured, err error) {
|
||||||
if fs != nil {
|
if fs != nil {
|
||||||
resources, err = GetResourcesWithTest(out, fs, policies, resourcePaths, policyResourcePath)
|
resources, err = GetResourcesWithTest(out, fs, resourcePaths, policyResourcePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to extract the resources (%w)", err)
|
return nil, fmt.Errorf("failed to extract the resources (%w)", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log"
|
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log"
|
||||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource"
|
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource"
|
||||||
"github.com/kyverno/kyverno/pkg/admissionpolicy"
|
"github.com/kyverno/kyverno/pkg/admissionpolicy"
|
||||||
"github.com/kyverno/kyverno/pkg/autogen"
|
|
||||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||||
|
@ -126,16 +125,8 @@ func whenClusterIsFalse(out io.Writer, resourcePaths []string, policyReport bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetResourcesWithTest with gets matched resources by the given policies
|
// GetResourcesWithTest with gets matched resources by the given policies
|
||||||
func GetResourcesWithTest(out io.Writer, fs billy.Filesystem, policies []kyvernov1.PolicyInterface, resourcePaths []string, policyResourcePath string) ([]*unstructured.Unstructured, error) {
|
func GetResourcesWithTest(out io.Writer, fs billy.Filesystem, resourcePaths []string, policyResourcePath string) ([]*unstructured.Unstructured, error) {
|
||||||
resources := make([]*unstructured.Unstructured, 0)
|
resources := make([]*unstructured.Unstructured, 0)
|
||||||
resourceTypesMap := make(map[string]bool)
|
|
||||||
for _, policy := range policies {
|
|
||||||
for _, rule := range autogen.Default.ComputeRules(policy, "") {
|
|
||||||
for _, kind := range rule.MatchResources.Kinds {
|
|
||||||
resourceTypesMap[kind] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(resourcePaths) > 0 {
|
if len(resourcePaths) > 0 {
|
||||||
for _, resourcePath := range resourcePaths {
|
for _, resourcePath := range resourcePaths {
|
||||||
var resourceBytes []byte
|
var resourceBytes []byte
|
||||||
|
|
|
@ -49,6 +49,7 @@ kyverno apply [flags]
|
||||||
--generated-exception-ttl duration Default TTL for generated exceptions (default 720h0m0s)
|
--generated-exception-ttl duration Default TTL for generated exceptions (default 720h0m0s)
|
||||||
-b, --git-branch string test git repository branch
|
-b, --git-branch string test git repository branch
|
||||||
-h, --help help for apply
|
-h, --help help for apply
|
||||||
|
--json strings Path to JSON payload files
|
||||||
--kubeconfig string path to kubeconfig file with authorization and master location information
|
--kubeconfig string path to kubeconfig file with authorization and master location information
|
||||||
-n, --namespace string Optional Policy parameter passed with cluster flag
|
-n, --namespace string Optional Policy parameter passed with cluster flag
|
||||||
-o, --output string Prints the mutated/generated resources in provided file/directory
|
-o, --output string Prints the mutated/generated resources in provided file/directory
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
vpolautogen "github.com/kyverno/kyverno/pkg/cel/autogen"
|
vpolautogen "github.com/kyverno/kyverno/pkg/cel/autogen"
|
||||||
contextlib "github.com/kyverno/kyverno/pkg/cel/libs/context"
|
contextlib "github.com/kyverno/kyverno/pkg/cel/libs/context"
|
||||||
"github.com/kyverno/kyverno/pkg/cel/matching"
|
"github.com/kyverno/kyverno/pkg/cel/matching"
|
||||||
|
celpolicy "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"
|
||||||
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
||||||
|
@ -25,8 +26,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type EngineRequest struct {
|
type EngineRequest struct {
|
||||||
request admissionv1.AdmissionRequest
|
jsonPayload *unstructured.Unstructured
|
||||||
context contextlib.ContextInterface
|
request admissionv1.AdmissionRequest
|
||||||
|
context contextlib.ContextInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func RequestFromAdmission(context contextlib.ContextInterface, request admissionv1.AdmissionRequest) EngineRequest {
|
func RequestFromAdmission(context contextlib.ContextInterface, request admissionv1.AdmissionRequest) EngineRequest {
|
||||||
|
@ -36,6 +38,13 @@ func RequestFromAdmission(context contextlib.ContextInterface, request admission
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RequestFromJSON(context contextlib.ContextInterface, jsonPayload *unstructured.Unstructured) EngineRequest {
|
||||||
|
return EngineRequest{
|
||||||
|
jsonPayload: jsonPayload,
|
||||||
|
context: context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Request(
|
func Request(
|
||||||
context contextlib.ContextInterface,
|
context contextlib.ContextInterface,
|
||||||
gvk schema.GroupVersionKind,
|
gvk schema.GroupVersionKind,
|
||||||
|
@ -111,6 +120,15 @@ func (e *engine) Handle(ctx context.Context, request EngineRequest) (EngineRespo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if request.jsonPayload != nil {
|
||||||
|
response.Resource = request.jsonPayload
|
||||||
|
for _, policy := range policies {
|
||||||
|
response.Policies = append(response.Policies, e.handlePolicy(ctx, policy, request.jsonPayload.Object, nil, nil, nil, request.context))
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
// load objects
|
// load objects
|
||||||
object, oldObject, err := admissionutils.ExtractResources(nil, request.request)
|
object, oldObject, err := admissionutils.ExtractResources(nil, request.request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -147,7 +165,7 @@ func (e *engine) Handle(ctx context.Context, request EngineRequest) (EngineRespo
|
||||||
}
|
}
|
||||||
// evaluate policies
|
// evaluate policies
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
response.Policies = append(response.Policies, e.handlePolicy(ctx, policy, attr, &request.request, namespace, request.context))
|
response.Policies = append(response.Policies, e.handlePolicy(ctx, policy, nil, attr, &request.request, namespace, request.context))
|
||||||
}
|
}
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
@ -163,12 +181,14 @@ func (e *engine) matchPolicy(policy CompiledPolicy, attr admission.Attributes, n
|
||||||
}
|
}
|
||||||
|
|
||||||
// match against main policy constraints
|
// match against main policy constraints
|
||||||
matches, err := match(policy.Policy.Spec.MatchConstraints)
|
if policy.Policy.GetSpec().MatchConstraints != nil {
|
||||||
if err != nil {
|
matches, err := match(policy.Policy.Spec.MatchConstraints)
|
||||||
return false, -1, err
|
if err != nil {
|
||||||
}
|
return false, -1, err
|
||||||
if matches {
|
}
|
||||||
return true, -1, nil
|
if matches {
|
||||||
|
return true, -1, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// match against autogen rules
|
// match against autogen rules
|
||||||
|
@ -185,7 +205,7 @@ func (e *engine) matchPolicy(policy CompiledPolicy, attr admission.Attributes, n
|
||||||
return false, -1, nil
|
return false, -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *engine) handlePolicy(ctx context.Context, policy CompiledPolicy, attr admission.Attributes, request *admissionv1.AdmissionRequest, namespace runtime.Object, context contextlib.ContextInterface) PolicyResponse {
|
func (e *engine) handlePolicy(ctx context.Context, policy CompiledPolicy, jsonPayload interface{}, attr admission.Attributes, request *admissionv1.AdmissionRequest, namespace runtime.Object, context contextlib.ContextInterface) PolicyResponse {
|
||||||
response := PolicyResponse{
|
response := PolicyResponse{
|
||||||
Actions: policy.Actions,
|
Actions: policy.Actions,
|
||||||
Policy: policy.Policy,
|
Policy: policy.Policy,
|
||||||
|
@ -201,7 +221,15 @@ func (e *engine) handlePolicy(ctx context.Context, policy CompiledPolicy, attr a
|
||||||
}
|
}
|
||||||
autogenIndex = index
|
autogenIndex = index
|
||||||
}
|
}
|
||||||
result, err := policy.CompiledPolicy.Evaluate(ctx, nil, attr, request, namespace, context, autogenIndex)
|
|
||||||
|
var result *celpolicy.EvaluationResult
|
||||||
|
var err error
|
||||||
|
if jsonPayload != nil {
|
||||||
|
result, err = policy.CompiledPolicy.Evaluate(ctx, jsonPayload, nil, nil, nil, context, -1)
|
||||||
|
} else {
|
||||||
|
result, err = policy.CompiledPolicy.Evaluate(ctx, nil, attr, request, namespace, context, autogenIndex)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: error is about match conditions here ?
|
// TODO: error is about match conditions here ?
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Rules = handlers.WithResponses(engineapi.RuleError("evaluation", engineapi.Validation, "failed to load context", err, nil))
|
response.Rules = handlers.WithResponses(engineapi.RuleError("evaluation", engineapi.Validation, "failed to load context", err, nil))
|
||||||
|
|
|
@ -25,14 +25,6 @@ func Test_evaluateJson(t *testing.T) {
|
||||||
{
|
{
|
||||||
"message": "HTTP calls are not allowed",
|
"message": "HTTP calls are not allowed",
|
||||||
"expression": "!object.Stages.exists(s, \n s.Commands.exists(c, \n c.Args.exists(a, \n a.Value.contains('http://') || a.Value.contains('https://')\n )\n )\n)"
|
"expression": "!object.Stages.exists(s, \n s.Commands.exists(c, \n c.Args.exists(a, \n a.Value.contains('http://') || a.Value.contains('https://')\n )\n )\n)"
|
||||||
},
|
|
||||||
{
|
|
||||||
"message": "curl is not allowed",
|
|
||||||
"expression": "!object.Stages.exists(s, \n s.Commands.exists(c, \n c.CmdLine.contains('curl')\n )\n)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message": "wget is not allowed",
|
|
||||||
"expression": "!object.Stages.exists(s, \n s.Commands.exists(c, \n c.CmdLine.contains('wget')\n )\n)"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -242,5 +234,5 @@ func Test_evaluateJson(t *testing.T) {
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Log(result)
|
assert.Assert(t, result.Result == false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
{
|
||||||
|
"MetaArgs": [
|
||||||
|
{
|
||||||
|
"Key": "BUILD_PLATFORM",
|
||||||
|
"DefaultValue": "\"linux/amd64\"",
|
||||||
|
"ProvidedValue": null,
|
||||||
|
"Value": "\"linux/amd64\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "BUILDER_IMAGE",
|
||||||
|
"DefaultValue": "\"golang:1.20.6-alpine3.18\"",
|
||||||
|
"ProvidedValue": null,
|
||||||
|
"Value": "\"golang:1.20.6-alpine3.18\""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Stages": [
|
||||||
|
{
|
||||||
|
"Name": "builder",
|
||||||
|
"BaseName": "\"golang:1.20.6-alpine3.18\"",
|
||||||
|
"Platform": "$BUILD_PLATFORM",
|
||||||
|
"Comment": "",
|
||||||
|
"SourceCode": "FROM --platform=$BUILD_PLATFORM $BUILDER_IMAGE as builder",
|
||||||
|
"Location": [
|
||||||
|
{
|
||||||
|
"Start": {
|
||||||
|
"Line": 4,
|
||||||
|
"Character": 0
|
||||||
|
},
|
||||||
|
"End": {
|
||||||
|
"Line": 4,
|
||||||
|
"Character": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"As": "builder",
|
||||||
|
"From": {
|
||||||
|
"Image": "\"golang:1.20.6-alpine3.18\""
|
||||||
|
},
|
||||||
|
"Commands": [
|
||||||
|
{
|
||||||
|
"Name": "WORKDIR",
|
||||||
|
"Path": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Chmod": "",
|
||||||
|
"Chown": "",
|
||||||
|
"DestPath": "./",
|
||||||
|
"From": "",
|
||||||
|
"Link": false,
|
||||||
|
"Name": "COPY",
|
||||||
|
"SourceContents": null,
|
||||||
|
"SourcePaths": [
|
||||||
|
"."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Args": [
|
||||||
|
{
|
||||||
|
"Comment": "",
|
||||||
|
"Key": "SIGNER_BINARY_LINK",
|
||||||
|
"Value": "\"https://d2hvyiie56hcat.cloudfront.net/linux/amd64/plugin/latest/notation-aws-signer-plugin.zip\""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Name": "ARG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Args": [
|
||||||
|
{
|
||||||
|
"Comment": "",
|
||||||
|
"Key": "SIGNER_BINARY_FILE",
|
||||||
|
"Value": "\"notation-aws-signer-plugin.zip\""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Name": "ARG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"CmdLine": [
|
||||||
|
"wget -O ${SIGNER_BINARY_FILE} ${SIGNER_BINARY_LINK}"
|
||||||
|
],
|
||||||
|
"Files": null,
|
||||||
|
"FlagsUsed": [],
|
||||||
|
"Name": "RUN",
|
||||||
|
"PrependShell": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"CmdLine": [
|
||||||
|
"apk update && apk add unzip && unzip -o ${SIGNER_BINARY_FILE}"
|
||||||
|
],
|
||||||
|
"Files": null,
|
||||||
|
"FlagsUsed": [],
|
||||||
|
"Name": "RUN",
|
||||||
|
"PrependShell": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"CmdLine": [
|
||||||
|
"GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags=\"-w -s\" -o kyverno-notation-aws ."
|
||||||
|
],
|
||||||
|
"Files": null,
|
||||||
|
"FlagsUsed": [],
|
||||||
|
"Name": "RUN",
|
||||||
|
"PrependShell": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "",
|
||||||
|
"BaseName": "gcr.io/distroless/static:nonroot",
|
||||||
|
"Platform": "",
|
||||||
|
"Comment": "",
|
||||||
|
"SourceCode": "FROM gcr.io/distroless/static:nonroot",
|
||||||
|
"Location": [
|
||||||
|
{
|
||||||
|
"Start": {
|
||||||
|
"Line": 20,
|
||||||
|
"Character": 0
|
||||||
|
},
|
||||||
|
"End": {
|
||||||
|
"Line": 20,
|
||||||
|
"Character": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"From": {
|
||||||
|
"Image": "gcr.io/distroless/static:nonroot"
|
||||||
|
},
|
||||||
|
"Commands": [
|
||||||
|
{
|
||||||
|
"Name": "WORKDIR",
|
||||||
|
"Path": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Env": [
|
||||||
|
{
|
||||||
|
"Key": "PLUGINS_DIR",
|
||||||
|
"Value": "/plugins"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Name": "ENV"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Chmod": "",
|
||||||
|
"Chown": "",
|
||||||
|
"DestPath": "plugins/com.amazonaws.signer.notation.plugin/notation-com.amazonaws.signer.notation.plugin",
|
||||||
|
"From": "builder",
|
||||||
|
"Link": false,
|
||||||
|
"Name": "COPY",
|
||||||
|
"SourceContents": null,
|
||||||
|
"SourcePaths": [
|
||||||
|
"notation-com.amazonaws.signer.notation.plugin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Chmod": "",
|
||||||
|
"Chown": "",
|
||||||
|
"DestPath": "kyverno-notation-aws",
|
||||||
|
"From": "builder",
|
||||||
|
"Link": false,
|
||||||
|
"Name": "COPY",
|
||||||
|
"SourceContents": null,
|
||||||
|
"SourcePaths": [
|
||||||
|
"kyverno-notation-aws"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"CmdLine": [
|
||||||
|
"/kyverno-notation-aws"
|
||||||
|
],
|
||||||
|
"Files": null,
|
||||||
|
"Name": "ENTRYPOINT",
|
||||||
|
"PrependShell": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
apiVersion: policies.kyverno.io/v1alpha1
|
||||||
|
kind: ValidatingPolicy
|
||||||
|
metadata:
|
||||||
|
name: check-dockerfile-disallow-curl
|
||||||
|
spec:
|
||||||
|
evaluation:
|
||||||
|
mode: JSON
|
||||||
|
validations:
|
||||||
|
- message: "curl is not allowed"
|
||||||
|
expression: >-
|
||||||
|
!object.Stages.exists(s,
|
||||||
|
s.Commands.exists(c,
|
||||||
|
has(c.CmdLine) && c.CmdLine.exists(cmd, string(cmd).contains('curl'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
---
|
||||||
|
apiVersion: policies.kyverno.io/v1alpha1
|
||||||
|
kind: ValidatingPolicy
|
||||||
|
metadata:
|
||||||
|
name: check-dockerfile-disallow-wget
|
||||||
|
spec:
|
||||||
|
evaluation:
|
||||||
|
mode: JSON
|
||||||
|
validations:
|
||||||
|
- message: "wget is not allowed"
|
||||||
|
expression: >-
|
||||||
|
!object.Stages.exists(s,
|
||||||
|
s.Commands.exists(c,
|
||||||
|
has(c.CmdLine) && c.CmdLine.exists(cmd, string(cmd).contains('wget'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
- message: "HTTP calls are not allowed"
|
||||||
|
expression: >-
|
||||||
|
!object.Stages.exists(s,
|
||||||
|
s.Commands.exists(c,
|
||||||
|
c.Args.exists(a,
|
||||||
|
a.Value.contains('http://') || a.Value.contains('https://')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
Loading…
Add table
Reference in a new issue