2023-09-06 06:48:55 +02:00
package processor
2023-05-10 11:12:53 +03:00
import (
"context"
"fmt"
2023-09-06 06:48:55 +02:00
"os"
"path/filepath"
2023-05-10 11:12:53 +03:00
"strings"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
2023-09-06 06:48:55 +02:00
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
valuesapi "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/values"
2023-09-06 02:06:44 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log"
2023-09-06 17:17:12 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/store"
2023-05-10 11:12:53 +03:00
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
"github.com/kyverno/kyverno/pkg/autogen"
2023-09-06 06:48:55 +02:00
"github.com/kyverno/kyverno/pkg/clients/dclient"
2023-05-10 11:12:53 +03:00
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
2023-06-10 11:20:34 +02:00
"github.com/kyverno/kyverno/pkg/engine/adapters"
2023-05-10 11:12:53 +03:00
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
2023-06-16 19:07:08 +05:30
"github.com/kyverno/kyverno/pkg/engine/factories"
2023-05-10 11:12:53 +03:00
"github.com/kyverno/kyverno/pkg/engine/jmespath"
2023-08-07 01:24:52 +05:30
"github.com/kyverno/kyverno/pkg/imageverifycache"
2023-05-10 11:12:53 +03:00
"github.com/kyverno/kyverno/pkg/registryclient"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
2023-09-06 06:48:55 +02:00
yamlv2 "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2023-05-10 11:12:53 +03:00
"k8s.io/apimachinery/pkg/runtime/schema"
)
2023-09-06 06:48:55 +02:00
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 ) {
2023-05-10 11:12:53 +03:00
jp := jmespath . New ( config . NewDefaultConfiguration ( false ) )
var engineResponses [ ] engineapi . EngineResponse
namespaceLabels := make ( map [ string ] string )
2023-05-12 16:14:48 +02:00
operation := kyvernov1 . Create
2023-05-10 11:12:53 +03:00
2023-09-06 06:48:55 +02:00
if p . Variables [ "request.operation" ] == "DELETE" {
2023-05-12 16:14:48 +02:00
operation = kyvernov1 . Delete
2023-05-10 11:12:53 +03:00
}
2023-09-10 21:10:02 +02:00
rules := autogen . ComputeRules ( p . Policy )
2023-05-10 11:12:53 +03:00
2023-09-10 21:10:02 +02:00
if needsNamespaceLabels ( rules ... ) {
2023-09-06 06:48:55 +02:00
resourceNamespace := p . Resource . GetNamespace ( )
namespaceLabels = p . NamespaceSelectorMap [ p . Resource . GetNamespace ( ) ]
2023-05-10 11:12:53 +03:00
if resourceNamespace != "default" && len ( namespaceLabels ) < 1 {
2023-09-06 06:48:55 +02:00
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 )
2023-05-10 11:12:53 +03:00
}
}
2023-09-06 06:48:55 +02:00
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 )
2023-05-10 11:12:53 +03:00
2023-09-06 06:48:55 +02:00
resourceRaw , err := p . Resource . MarshalJSON ( )
2023-05-10 11:12:53 +03:00
if err != nil {
2023-09-06 02:06:44 +02:00
log . Log . Error ( err , "failed to marshal resource" )
2023-05-10 11:12:53 +03:00
}
updatedResource , err := kubeutils . BytesToUnstructured ( resourceRaw )
if err != nil {
2023-09-06 02:06:44 +02:00
log . Log . Error ( err , "unable to convert raw resource to unstructured" )
2023-05-10 11:12:53 +03:00
}
if err != nil {
2023-09-06 02:06:44 +02:00
log . Log . Error ( err , "failed to load resource in context" )
2023-05-10 11:12:53 +03:00
}
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
2023-09-06 06:48:55 +02:00
if p . Client == nil {
for _ , s := range p . Subresources {
2023-05-10 11:12:53 +03:00
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 ]
}
}
}
2023-08-23 18:22:37 +03:00
var client engineapi . Client
2023-09-06 06:48:55 +02:00
if p . Client != nil {
client = adapters . Client ( p . Client )
2023-08-23 18:22:37 +03:00
}
2023-06-14 12:06:52 +02:00
rclient := registryclient . NewOrDie ( )
2023-05-10 11:12:53 +03:00
eng := engine . NewEngine (
cfg ,
config . NewDefaultMetricsConfiguration ( ) ,
jmespath . New ( cfg ) ,
2023-08-23 18:22:37 +03:00
client ,
2023-06-16 19:07:08 +05:30
factories . DefaultRegistryClientFactory ( adapters . RegistryClient ( rclient ) , nil ) ,
2023-08-07 01:24:52 +05:30
imageverifycache . DisabledImageVerifyCache ( ) ,
2023-05-10 11:12:53 +03:00
store . ContextLoaderFactory ( nil ) ,
nil ,
2023-06-02 14:18:10 +02:00
"" ,
2023-05-10 11:12:53 +03:00
)
2023-05-12 16:14:48 +02:00
policyContext , err := engine . NewPolicyContext (
jp ,
* updatedResource ,
operation ,
2023-09-06 06:48:55 +02:00
p . UserInfo ,
2023-05-12 16:14:48 +02:00
cfg ,
)
if err != nil {
2023-09-06 02:06:44 +02:00
log . Log . Error ( err , "failed to create policy context" )
2023-05-12 16:14:48 +02:00
}
policyContext = policyContext .
2023-09-06 06:48:55 +02:00
WithPolicy ( p . Policy ) .
2023-05-10 11:12:53 +03:00
WithNamespaceLabels ( namespaceLabels ) .
WithResourceKind ( gvk , subresource )
2023-09-06 06:48:55 +02:00
for key , value := range p . Variables {
2023-05-12 16:14:48 +02:00
err = policyContext . JSONContext ( ) . AddVariable ( key , value )
if err != nil {
2023-09-06 02:06:44 +02:00
log . Log . Error ( err , "failed to add variable to context" )
2023-05-12 16:14:48 +02:00
}
}
2023-05-10 11:12:53 +03:00
mutateResponse := eng . Mutate ( context . Background ( ) , policyContext )
2023-08-07 12:52:26 +05:30
combineRuleResponses ( mutateResponse )
2023-05-10 11:12:53 +03:00
engineResponses = append ( engineResponses , mutateResponse )
2023-09-06 06:48:55 +02:00
err = p . processMutateEngineResponse ( mutateResponse , resPath )
2023-05-10 11:12:53 +03:00
if err != nil {
if ! sanitizederror . IsErrorSanitized ( err ) {
return engineResponses , sanitizederror . NewWithError ( "failed to print mutated result" , err )
}
}
2023-08-18 15:58:47 +05:30
verifyImageResponse , _ := eng . VerifyAndPatchImages ( context . TODO ( ) , policyContext )
if ! verifyImageResponse . IsEmpty ( ) {
verifyImageResponse = combineRuleResponses ( verifyImageResponse )
engineResponses = append ( engineResponses , verifyImageResponse )
}
2023-05-10 11:12:53 +03:00
var policyHasValidate bool
2023-09-10 21:10:02 +02:00
for _ , rule := range rules {
2023-05-10 11:12:53 +03:00
if rule . HasValidate ( ) || rule . HasVerifyImageChecks ( ) {
policyHasValidate = true
}
}
policyContext = policyContext . WithNewResource ( mutateResponse . PatchedResource )
var validateResponse engineapi . EngineResponse
if policyHasValidate {
validateResponse = eng . Validate ( context . Background ( ) , policyContext )
2023-08-07 12:52:26 +05:30
validateResponse = combineRuleResponses ( validateResponse )
2023-05-10 11:12:53 +03:00
}
if ! validateResponse . IsEmpty ( ) {
engineResponses = append ( engineResponses , validateResponse )
}
var policyHasGenerate bool
2023-09-10 21:10:02 +02:00
for _ , rule := range rules {
2023-05-10 11:12:53 +03:00
if rule . HasGenerate ( ) {
policyHasGenerate = true
}
}
if policyHasGenerate {
generateResponse := eng . ApplyBackgroundChecks ( context . TODO ( ) , policyContext )
if ! generateResponse . IsEmpty ( ) {
2023-09-06 06:48:55 +02:00
newRuleResponse , err := handleGeneratePolicy ( & generateResponse , * policyContext , p . RuleToCloneSourceResource )
2023-05-10 11:12:53 +03:00
if err != nil {
2023-09-06 02:06:44 +02:00
log . Log . Error ( err , "failed to apply generate policy" )
2023-05-10 11:12:53 +03:00
} else {
generateResponse . PolicyResponse . Rules = newRuleResponse
}
2023-08-07 12:52:26 +05:30
combineRuleResponses ( generateResponse )
2023-05-10 11:12:53 +03:00
engineResponses = append ( engineResponses , generateResponse )
}
2023-09-06 06:48:55 +02:00
p . Rc . addGenerateResponse ( p . AuditWarn , resPath , generateResponse )
2023-05-10 11:12:53 +03:00
}
2023-09-06 06:48:55 +02:00
p . Rc . addEngineResponses ( p . AuditWarn , engineResponses ... )
2023-08-08 13:05:01 +05:30
2023-05-10 11:12:53 +03:00
return engineResponses , nil
}
2023-08-07 12:52:26 +05:30
2023-09-06 06:48:55 +02:00
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 )
}
2023-08-07 12:52:26 +05:30
2023-09-06 06:48:55 +02:00
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" )
2023-08-07 12:52:26 +05:30
}
2023-09-06 06:48:55 +02:00
} else {
err := p . printMutatedOutput ( string ( yamlEncodedResource ) )
if err != nil {
return sanitizederror . NewWithError ( "failed to print mutated result" , err )
2023-08-07 12:52:26 +05:30
}
2023-09-06 06:48:55 +02:00
fmt . Printf ( "\n\nMutation:\nMutation has been applied successfully. Check the files." )
2023-08-07 12:52:26 +05:30
}
2023-09-06 06:48:55 +02:00
}
return nil
}
2023-08-07 12:52:26 +05:30
2023-09-06 06:48:55 +02:00
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 , 0 o600 ) // #nosec G304
if err != nil {
return err
2023-08-07 12:52:26 +05:30
}
2023-09-06 06:48:55 +02:00
file = f
} else {
f , err := os . OpenFile ( filepath . Join ( mutateLogPath , filename + ".yaml" ) , os . O_CREATE | os . O_WRONLY , 0 o600 ) // #nosec G304
if err != nil {
return err
2023-08-07 12:52:26 +05:30
}
2023-09-06 06:48:55 +02:00
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" )
2023-08-07 12:52:26 +05:30
}
2023-09-06 06:48:55 +02:00
return err
}
if err := file . Close ( ) ; err != nil {
return err
2023-08-07 12:52:26 +05:30
}
2023-09-06 06:48:55 +02:00
return nil
2023-08-07 12:52:26 +05:30
}