1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-09 09:26:54 +00:00
kyverno/cmd/cli/kubectl-kyverno/processor/policy_processor.go
Charles-Edouard Brétéché e98bfd1cd9
refactor: introduce cli processor package (#8281)
* refactor: introduce cli processor package

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* tests

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* counts

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
2023-09-06 12:48:55 +08:00

298 lines
9.4 KiB
Go

package processor
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
valuesapi "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/values"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log"
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/adapters"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/factories"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/imageverifycache"
"github.com/kyverno/kyverno/pkg/registryclient"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
yamlv2 "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type PolicyProcessor struct {
Policy kyvernov1.PolicyInterface
Resource *unstructured.Unstructured
MutateLogPath string
MutateLogPathIsDir bool
Variables map[string]interface{}
UserInfo *kyvernov1beta1.RequestInfo
PolicyReport bool
NamespaceSelectorMap map[string]map[string]string
Stdin bool
Rc *ResultCounts
PrintPatchResource bool
RuleToCloneSourceResource map[string]string
Client dclient.Interface
AuditWarn bool
Subresources []valuesapi.Subresource
}
func (p *PolicyProcessor) ApplyPolicyOnResource() ([]engineapi.EngineResponse, error) {
jp := jmespath.New(config.NewDefaultConfiguration(false))
var engineResponses []engineapi.EngineResponse
namespaceLabels := make(map[string]string)
operation := kyvernov1.Create
if p.Variables["request.operation"] == "DELETE" {
operation = kyvernov1.Delete
}
policyWithNamespaceSelector := false
OuterLoop:
for _, p := range autogen.ComputeRules(p.Policy) {
if p.MatchResources.ResourceDescription.NamespaceSelector != nil ||
p.ExcludeResources.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break
}
for _, m := range p.MatchResources.Any {
if m.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
for _, m := range p.MatchResources.All {
if m.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
for _, e := range p.ExcludeResources.Any {
if e.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
for _, e := range p.ExcludeResources.All {
if e.ResourceDescription.NamespaceSelector != nil {
policyWithNamespaceSelector = true
break OuterLoop
}
}
}
if policyWithNamespaceSelector {
resourceNamespace := p.Resource.GetNamespace()
namespaceLabels = p.NamespaceSelectorMap[p.Resource.GetNamespace()]
if resourceNamespace != "default" && len(namespaceLabels) < 1 {
return engineResponses, sanitizederror.NewWithError(fmt.Sprintf("failed to get namespace labels for resource %s. use --values-file flag to pass the namespace labels", p.Resource.GetName()), nil)
}
}
resPath := fmt.Sprintf("%s/%s/%s", p.Resource.GetNamespace(), p.Resource.GetKind(), p.Resource.GetName())
log.Log.V(3).Info("applying policy on resource", "policy", p.Policy.GetName(), "resource", resPath)
resourceRaw, err := p.Resource.MarshalJSON()
if err != nil {
log.Log.Error(err, "failed to marshal resource")
}
updatedResource, err := kubeutils.BytesToUnstructured(resourceRaw)
if err != nil {
log.Log.Error(err, "unable to convert raw resource to unstructured")
}
if err != nil {
log.Log.Error(err, "failed to load resource in context")
}
cfg := config.NewDefaultConfiguration(false)
gvk, subresource := updatedResource.GroupVersionKind(), ""
// If --cluster flag is not set, then we need to find the top level resource GVK and subresource
if p.Client == nil {
for _, s := range p.Subresources {
subgvk := schema.GroupVersionKind{
Group: s.APIResource.Group,
Version: s.APIResource.Version,
Kind: s.APIResource.Kind,
}
if gvk == subgvk {
gvk = schema.GroupVersionKind{
Group: s.ParentResource.Group,
Version: s.ParentResource.Version,
Kind: s.ParentResource.Kind,
}
parts := strings.Split(s.APIResource.Name, "/")
subresource = parts[1]
}
}
}
var client engineapi.Client
if p.Client != nil {
client = adapters.Client(p.Client)
}
rclient := registryclient.NewOrDie()
eng := engine.NewEngine(
cfg,
config.NewDefaultMetricsConfiguration(),
jmespath.New(cfg),
client,
factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil),
imageverifycache.DisabledImageVerifyCache(),
store.ContextLoaderFactory(nil),
nil,
"",
)
policyContext, err := engine.NewPolicyContext(
jp,
*updatedResource,
operation,
p.UserInfo,
cfg,
)
if err != nil {
log.Log.Error(err, "failed to create policy context")
}
policyContext = policyContext.
WithPolicy(p.Policy).
WithNamespaceLabels(namespaceLabels).
WithResourceKind(gvk, subresource)
for key, value := range p.Variables {
err = policyContext.JSONContext().AddVariable(key, value)
if err != nil {
log.Log.Error(err, "failed to add variable to context")
}
}
mutateResponse := eng.Mutate(context.Background(), policyContext)
combineRuleResponses(mutateResponse)
engineResponses = append(engineResponses, mutateResponse)
err = p.processMutateEngineResponse(mutateResponse, resPath)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return engineResponses, sanitizederror.NewWithError("failed to print mutated result", err)
}
}
verifyImageResponse, _ := eng.VerifyAndPatchImages(context.TODO(), policyContext)
if !verifyImageResponse.IsEmpty() {
verifyImageResponse = combineRuleResponses(verifyImageResponse)
engineResponses = append(engineResponses, verifyImageResponse)
}
var policyHasValidate bool
for _, rule := range autogen.ComputeRules(p.Policy) {
if rule.HasValidate() || rule.HasVerifyImageChecks() {
policyHasValidate = true
}
}
policyContext = policyContext.WithNewResource(mutateResponse.PatchedResource)
var validateResponse engineapi.EngineResponse
if policyHasValidate {
validateResponse = eng.Validate(context.Background(), policyContext)
validateResponse = combineRuleResponses(validateResponse)
}
if !validateResponse.IsEmpty() {
engineResponses = append(engineResponses, validateResponse)
}
var policyHasGenerate bool
for _, rule := range autogen.ComputeRules(p.Policy) {
if rule.HasGenerate() {
policyHasGenerate = true
}
}
if policyHasGenerate {
generateResponse := eng.ApplyBackgroundChecks(context.TODO(), policyContext)
if !generateResponse.IsEmpty() {
newRuleResponse, err := handleGeneratePolicy(&generateResponse, *policyContext, p.RuleToCloneSourceResource)
if err != nil {
log.Log.Error(err, "failed to apply generate policy")
} else {
generateResponse.PolicyResponse.Rules = newRuleResponse
}
combineRuleResponses(generateResponse)
engineResponses = append(engineResponses, generateResponse)
}
p.Rc.addGenerateResponse(p.AuditWarn, resPath, generateResponse)
}
p.Rc.addEngineResponses(p.AuditWarn, engineResponses...)
return engineResponses, nil
}
func (p *PolicyProcessor) processMutateEngineResponse(response engineapi.EngineResponse, resourcePath string) error {
printMutatedRes := p.Rc.addMutateResponse(resourcePath, response)
if printMutatedRes && p.PrintPatchResource {
yamlEncodedResource, err := yamlv2.Marshal(response.PatchedResource.Object)
if err != nil {
return sanitizederror.NewWithError("failed to marshal", err)
}
if p.MutateLogPath == "" {
mutatedResource := string(yamlEncodedResource) + string("\n---")
if len(strings.TrimSpace(mutatedResource)) > 0 {
if !p.Stdin {
fmt.Printf("\nmutate policy %s applied to %s:", p.Policy.GetName(), resourcePath)
}
fmt.Printf("\n" + mutatedResource + "\n")
}
} else {
err := p.printMutatedOutput(string(yamlEncodedResource))
if err != nil {
return sanitizederror.NewWithError("failed to print mutated result", err)
}
fmt.Printf("\n\nMutation:\nMutation has been applied successfully. Check the files.")
}
}
return nil
}
func (p *PolicyProcessor) printMutatedOutput(yaml string) error {
var file *os.File
mutateLogPath := filepath.Clean(p.MutateLogPath)
filename := p.Resource.GetName() + "-mutated"
if !p.MutateLogPathIsDir {
// truncation for the case when mutateLogPath is a file (not a directory) is handled under pkg/kyverno/apply/test_command.go
f, err := os.OpenFile(mutateLogPath, os.O_APPEND|os.O_WRONLY, 0o600) // #nosec G304
if err != nil {
return err
}
file = f
} else {
f, err := os.OpenFile(filepath.Join(mutateLogPath, filename+".yaml"), os.O_CREATE|os.O_WRONLY, 0o600) // #nosec G304
if err != nil {
return err
}
file = f
}
if _, err := file.Write([]byte(yaml + "\n---\n\n")); err != nil {
if err := file.Close(); err != nil {
log.Log.Error(err, "failed to close file")
}
return err
}
if err := file.Close(); err != nil {
return err
}
return nil
}