2020-03-06 03:00:18 +05:30
package apply
import (
2022-09-30 10:12:21 +02:00
"context"
2020-03-06 03:00:18 +05:30
"fmt"
2023-09-14 01:55:19 +02:00
"io"
2022-12-02 20:03:04 +05:30
"net/url"
2020-06-08 17:01:56 +05:30
"os"
2020-07-21 00:41:30 +05:30
"path/filepath"
"strings"
2020-04-03 10:30:52 +05:30
"time"
2021-02-07 20:26:56 -08:00
"github.com/go-git/go-billy/v5/memfs"
2022-12-02 20:03:04 +05:30
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
2024-06-20 11:44:43 +02:00
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
2025-02-11 19:05:22 +02:00
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
2023-09-06 16:44:50 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/command"
2023-12-20 13:45:26 +01:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/deprecations"
2024-02-01 03:58:14 +05:30
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/exception"
2023-09-06 02:06:44 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log"
2023-09-05 10:55:01 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/color"
2023-09-06 01:01:31 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/policy"
2023-09-06 06:48:55 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/processor"
2023-09-06 01:01:31 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/source"
2023-09-06 17:17:12 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/store"
2023-09-05 19:10:27 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/userinfo"
2022-04-14 17:50:18 +05:30
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common"
2023-09-06 16:03:51 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/variables"
2024-10-16 15:24:37 +02:00
"github.com/kyverno/kyverno/pkg/autogen"
2025-01-17 10:53:17 +01:00
"github.com/kyverno/kyverno/pkg/cel/engine"
celpolicy "github.com/kyverno/kyverno/pkg/cel/policy"
2022-08-31 14:03:47 +08:00
"github.com/kyverno/kyverno/pkg/clients/dclient"
2022-09-20 19:05:18 +05:30
"github.com/kyverno/kyverno/pkg/config"
2023-04-12 14:51:03 +02:00
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
2025-02-03 19:12:40 +05:30
"github.com/kyverno/kyverno/pkg/imagedataloader"
2022-12-03 19:56:09 +01:00
gitutils "github.com/kyverno/kyverno/pkg/utils/git"
2023-04-28 21:54:17 +08:00
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
2020-03-06 03:00:18 +05:30
"github.com/spf13/cobra"
2025-02-06 04:49:41 +01:00
admissionv1 "k8s.io/api/admission/v1"
2025-01-31 16:21:43 +02:00
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
2025-01-20 12:43:13 +01:00
corev1 "k8s.io/api/core/v1"
2020-10-15 17:29:07 -07:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2024-12-19 09:42:54 +02:00
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
2022-11-22 14:37:27 +01:00
"k8s.io/client-go/dynamic"
2022-07-25 07:49:51 +02:00
"k8s.io/client-go/kubernetes"
2020-03-06 03:00:18 +05:30
)
2024-02-01 15:50:58 +01:00
type SkippedInvalidPolicies struct {
2021-10-14 22:44:11 +05:30
skipped [ ] string
invalid [ ] string
}
2024-02-01 15:50:58 +01:00
type ApplyCommandConfig struct {
2024-09-09 13:42:16 -07:00
KubeConfig string
Context string
Namespace string
MutateLogPath string
Variables [ ] string
ValuesFile string
UserInfoPath string
Cluster bool
PolicyReport bool
2025-01-07 12:22:11 +06:00
OutputFormat string
2024-09-09 13:42:16 -07:00
Stdin bool
RegistryAccess bool
AuditWarn bool
ResourcePaths [ ] string
PolicyPaths [ ] string
2024-12-19 09:42:54 +02:00
TargetResourcePaths [ ] string
2024-09-09 13:42:16 -07:00
GitBranch string
warnExitCode int
warnNoPassed bool
Exception [ ] string
ContinueOnFail bool
inlineExceptions bool
GenerateExceptions bool
GeneratedExceptionTTL time . Duration
2022-09-20 19:05:18 +05:30
}
2020-12-07 11:26:04 -08:00
func Command ( ) * cobra . Command {
2023-07-31 17:15:47 +03:00
var removeColor , detailedResults , table bool
2024-02-01 15:50:58 +01:00
applyCommandConfig := & ApplyCommandConfig { }
2023-09-12 17:20:21 +02:00
cmd := & cobra . Command {
2023-09-12 19:03:37 +02:00
Use : "apply" ,
Short : command . FormatDescription ( true , websiteUrl , false , description ... ) ,
Long : command . FormatDescription ( false , websiteUrl , false , description ... ) ,
Example : command . FormatExamples ( examples ... ) ,
SilenceUsage : true ,
2023-09-13 11:53:19 +02:00
RunE : func ( cmd * cobra . Command , args [ ] string ) ( err error ) {
2023-09-14 01:55:19 +02:00
out := cmd . OutOrStdout ( )
2023-10-27 11:50:36 +02:00
color . Init ( removeColor )
2023-09-13 11:53:19 +02:00
applyCommandConfig . PolicyPaths = args
2023-09-14 01:55:19 +02:00
rc , _ , skipInvalidPolicies , responses , err := applyCommandConfig . applyCommandHelper ( out )
2020-10-30 16:38:19 +05:30
if err != nil {
return err
2020-08-11 22:59:50 +05:30
}
2024-02-02 02:11:17 +05:30
cmd . SilenceErrors = true
2023-09-14 01:55:19 +02:00
printSkippedAndInvalidPolicies ( out , skipInvalidPolicies )
2023-07-04 18:28:22 +02:00
if applyCommandConfig . PolicyReport {
2025-01-07 12:22:11 +06:00
printReports ( out , responses , applyCommandConfig . AuditWarn , applyCommandConfig . OutputFormat )
2024-09-09 13:42:16 -07:00
} else if applyCommandConfig . GenerateExceptions {
2025-01-07 12:22:11 +06:00
printExceptions ( out , responses , applyCommandConfig . AuditWarn , applyCommandConfig . OutputFormat , applyCommandConfig . GeneratedExceptionTTL )
2023-07-06 13:48:19 +02:00
} else if table {
2023-09-14 13:45:18 +02:00
printTable ( out , detailedResults , applyCommandConfig . AuditWarn , responses ... )
2023-07-04 18:28:22 +02:00
} else {
2024-09-09 19:14:38 +05:30
for _ , response := range responses {
var failedRules [ ] engineapi . RuleResponse
resPath := fmt . Sprintf ( "%s/%s/%s" , response . Resource . GetNamespace ( ) , response . Resource . GetKind ( ) , response . Resource . GetName ( ) )
for _ , rule := range response . PolicyResponse . Rules {
if rule . Status ( ) == engineapi . RuleStatusFail {
failedRules = append ( failedRules , rule )
}
if rule . RuleType ( ) == engineapi . Mutation {
if rule . Status ( ) == engineapi . RuleStatusSkip {
2025-01-22 17:32:30 +01:00
fmt . Fprintln ( out , "\nskipped mutate policy" , response . Policy ( ) . GetName ( ) , "->" , "resource" , resPath )
2024-09-09 19:14:38 +05:30
} else if rule . Status ( ) == engineapi . RuleStatusError {
2025-01-22 17:32:30 +01:00
fmt . Fprintln ( out , "\nerror while applying mutate policy" , response . Policy ( ) . GetName ( ) , "->" , "resource" , resPath , "\nerror: " , rule . Message ( ) )
2024-09-09 19:14:38 +05:30
}
}
}
if len ( failedRules ) > 0 {
auditWarn := false
if applyCommandConfig . AuditWarn && response . GetValidationFailureAction ( ) . Audit ( ) {
auditWarn = true
}
if auditWarn {
2025-01-22 17:32:30 +01:00
fmt . Fprintln ( out , "policy" , response . Policy ( ) . GetName ( ) , "->" , "resource" , resPath , "failed as audit warning:" )
2024-09-09 19:14:38 +05:30
} else {
2025-01-22 17:32:30 +01:00
fmt . Fprintln ( out , "policy" , response . Policy ( ) . GetName ( ) , "->" , "resource" , resPath , "failed:" )
2024-09-09 19:14:38 +05:30
}
for i , rule := range failedRules {
fmt . Fprintln ( out , i + 1 , "-" , rule . Name ( ) , rule . Message ( ) )
}
fmt . Fprintln ( out , "" )
}
}
2023-09-14 01:55:19 +02:00
printViolations ( out , rc )
2023-07-04 18:28:22 +02:00
}
2024-02-29 08:08:21 -05:00
return exit ( out , rc , applyCommandConfig . warnExitCode , applyCommandConfig . warnNoPassed )
2020-12-20 01:21:31 +05:30
} ,
}
2023-07-03 12:04:07 +02:00
cmd . Flags ( ) . StringSliceVarP ( & applyCommandConfig . ResourcePaths , "resource" , "r" , [ ] string { } , "Path to resource files" )
2024-02-20 03:05:23 +05:30
cmd . Flags ( ) . StringSliceVarP ( & applyCommandConfig . ResourcePaths , "resources" , "" , [ ] string { } , "Path to resource files" )
2024-12-19 09:42:54 +02:00
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-resources" , "" , [ ] string { } , "Path to a directory containing target resources files for policies that have mutate existing" )
2022-09-20 19:05:18 +05:30
cmd . Flags ( ) . BoolVarP ( & applyCommandConfig . Cluster , "cluster" , "c" , false , "Checks if policies should be applied to cluster in the current context" )
2024-11-27 10:33:05 +02:00
cmd . Flags ( ) . StringVarP ( & applyCommandConfig . MutateLogPath , "output" , "o" , "" , "Prints the mutated/generated resources in provided file/directory" )
2021-08-02 16:38:43 +05:30
// currently `set` flag supports variable for single policy applied on single resource
2022-09-20 19:05:18 +05:30
cmd . Flags ( ) . StringVarP ( & applyCommandConfig . UserInfoPath , "userinfo" , "u" , "" , "Admission Info including Roles, Cluster Roles and Subjects" )
2023-07-03 12:04:07 +02:00
cmd . Flags ( ) . StringSliceVarP ( & applyCommandConfig . Variables , "set" , "s" , nil , "Variables that are required" )
2022-09-20 19:05:18 +05:30
cmd . Flags ( ) . StringVarP ( & applyCommandConfig . ValuesFile , "values-file" , "f" , "" , "File containing values for policy variables" )
cmd . Flags ( ) . BoolVarP ( & applyCommandConfig . PolicyReport , "policy-report" , "p" , false , "Generates policy report when passed (default policyviolation)" )
2025-01-07 12:22:11 +06:00
cmd . Flags ( ) . StringVarP ( & applyCommandConfig . OutputFormat , "output-format" , "" , "yaml" , "Specifies the policy report format (json or yaml). Default: yaml." )
2022-09-20 19:05:18 +05:30
cmd . Flags ( ) . StringVarP ( & applyCommandConfig . Namespace , "namespace" , "n" , "" , "Optional Policy parameter passed with cluster flag" )
cmd . Flags ( ) . BoolVarP ( & applyCommandConfig . Stdin , "stdin" , "i" , false , "Optional mutate policy parameter to pipe directly through to kubectl" )
2023-07-03 12:04:07 +02:00
cmd . Flags ( ) . BoolVar ( & applyCommandConfig . RegistryAccess , "registry" , false , "If set to true, access the image registry using local docker credentials to populate external data" )
cmd . Flags ( ) . StringVar ( & applyCommandConfig . KubeConfig , "kubeconfig" , "" , "path to kubeconfig file with authorization and master location information" )
cmd . Flags ( ) . StringVar ( & applyCommandConfig . Context , "context" , "" , "The name of the kubeconfig context to use" )
2022-12-02 20:03:04 +05:30
cmd . Flags ( ) . StringVarP ( & applyCommandConfig . GitBranch , "git-branch" , "b" , "" , "test git repository branch" )
2023-07-03 12:04:07 +02:00
cmd . Flags ( ) . BoolVar ( & applyCommandConfig . AuditWarn , "audit-warn" , false , "If set to true, will flag audit policies as warnings instead of failures" )
2022-12-05 13:15:22 -05:00
cmd . Flags ( ) . IntVar ( & applyCommandConfig . warnExitCode , "warn-exit-code" , 0 , "Set the exit code for warnings; if failures or errors are found, will exit 1" )
2023-07-03 12:04:07 +02:00
cmd . Flags ( ) . BoolVar ( & applyCommandConfig . warnNoPassed , "warn-no-pass" , false , "Specify if warning exit code should be raised if no objects satisfied a policy; can be used together with --warn-exit-code flag" )
2023-07-06 13:48:19 +02:00
cmd . Flags ( ) . BoolVar ( & removeColor , "remove-color" , false , "Remove any color from output" )
2023-07-31 17:15:47 +03:00
cmd . Flags ( ) . BoolVar ( & detailedResults , "detailed-results" , false , "If set to true, display detailed results" )
2023-07-06 13:48:19 +02:00
cmd . Flags ( ) . BoolVarP ( & table , "table" , "t" , false , "Show results in table format" )
2024-02-04 16:11:31 +05:30
cmd . Flags ( ) . StringSliceVarP ( & applyCommandConfig . Exception , "exception" , "e" , nil , "Policy exception to be considered when evaluating policies against resources" )
2024-02-20 03:05:23 +05:30
cmd . Flags ( ) . StringSliceVarP ( & applyCommandConfig . Exception , "exceptions" , "" , nil , "Policy exception to be considered when evaluating policies against resources" )
2024-05-23 13:46:33 +05:30
cmd . Flags ( ) . BoolVar ( & applyCommandConfig . ContinueOnFail , "continue-on-fail" , false , "If set to true, will continue to apply policies on the next resource upon failure to apply to the current resource instead of exiting out" )
2024-06-06 08:33:28 +02:00
cmd . Flags ( ) . BoolVarP ( & applyCommandConfig . inlineExceptions , "exceptions-with-resources" , "" , false , "Evaluate policy exceptions from the resources path" )
2024-09-09 13:42:16 -07:00
cmd . Flags ( ) . BoolVarP ( & applyCommandConfig . GenerateExceptions , "generate-exceptions" , "" , false , "Generate policy exceptions for each violation" )
cmd . Flags ( ) . DurationVarP ( & applyCommandConfig . GeneratedExceptionTTL , "generated-exception-ttl" , "" , time . Hour * 24 * 30 , "Default TTL for generated exceptions" )
2020-12-20 01:21:31 +05:30
return cmd
}
2020-08-05 23:53:27 +05:30
2024-02-01 15:50:58 +01:00
func ( c * ApplyCommandConfig ) applyCommandHelper ( out io . Writer ) ( * processor . ResultCounts , [ ] * unstructured . Unstructured , SkippedInvalidPolicies , [ ] engineapi . EngineResponse , error ) {
2025-01-15 11:19:57 +01:00
var skippedInvalidPolicies SkippedInvalidPolicies
err := c . checkArguments ( )
2023-08-18 15:58:47 +05:30
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , nil , skippedInvalidPolicies , nil , err
2023-07-04 12:54:22 +02:00
}
2025-01-15 11:19:57 +01:00
mutateLogPathIsDir , err := c . getMutateLogPathIsDir ( )
2023-07-04 18:28:22 +02:00
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , nil , skippedInvalidPolicies , nil , err
2023-07-04 18:28:22 +02:00
}
2025-01-15 11:19:57 +01:00
if err := c . cleanPreviousContent ( mutateLogPathIsDir ) ; err != nil {
return nil , nil , skippedInvalidPolicies , nil , err
2023-07-04 18:28:22 +02:00
}
2024-06-20 11:44:43 +02:00
var userInfo * kyvernov2 . RequestInfo
2023-07-04 18:28:22 +02:00
if c . UserInfoPath != "" {
2023-09-18 12:51:35 +02:00
info , err := userinfo . Load ( nil , c . UserInfoPath , "" )
2023-07-04 18:28:22 +02:00
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , nil , skippedInvalidPolicies , nil , fmt . Errorf ( "failed to load request info (%w)" , err )
2023-07-04 18:28:22 +02:00
}
2023-12-20 13:45:26 +01:00
deprecations . CheckUserInfo ( out , c . UserInfoPath , info )
2023-09-18 12:51:35 +02:00
userInfo = & info . RequestInfo
2022-10-19 22:09:15 +05:30
}
2023-12-20 13:45:26 +01:00
variables , err := variables . New ( out , nil , "" , c . ValuesFile , nil , c . Variables ... )
2020-12-20 01:21:31 +05:30
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , nil , skippedInvalidPolicies , nil , fmt . Errorf ( "failed to decode yaml (%w)" , err )
2020-12-20 01:21:31 +05:30
}
2023-12-19 15:45:53 +01:00
var store store . Store
2025-01-17 10:53:17 +01:00
policies , vaps , vapBindings , vps , err := c . loadPolicies ( )
2023-08-18 15:58:47 +05:30
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , nil , skippedInvalidPolicies , nil , err
2023-07-04 18:28:22 +02:00
}
2024-12-19 09:42:54 +02:00
var targetResources [ ] * unstructured . Unstructured
if len ( c . TargetResourcePaths ) > 0 {
targetResources , err = c . loadResources ( out , c . TargetResourcePaths , policies , vaps , nil )
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , nil , skippedInvalidPolicies , nil , err
2024-12-19 09:42:54 +02:00
}
}
2025-01-15 11:19:57 +01:00
dClient , err := c . initStoreAndClusterClient ( & store , targetResources ... )
2023-08-18 15:58:47 +05:30
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , nil , skippedInvalidPolicies , nil , err
2023-08-18 15:58:47 +05:30
}
2024-12-19 09:42:54 +02:00
resources , err := c . loadResources ( out , c . ResourcePaths , policies , vaps , dClient )
2023-09-07 21:15:57 +05:30
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , nil , skippedInvalidPolicies , nil , err
2023-09-07 21:15:57 +05:30
}
2024-06-24 23:36:55 +07:00
var exceptions [ ] * kyvernov2 . PolicyException
2025-02-19 10:38:44 +02:00
var celexceptions [ ] * policiesv1alpha1 . CELPolicyException
2024-06-06 08:33:28 +02:00
if c . inlineExceptions {
exceptions = exception . SelectFrom ( resources )
} else {
2025-02-19 10:38:44 +02:00
results , err := exception . Load ( c . Exception ... )
2024-06-06 08:33:28 +02:00
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , nil , skippedInvalidPolicies , nil , fmt . Errorf ( "Error: failed to load exceptions (%s)" , err )
2024-06-06 08:33:28 +02:00
}
2025-02-19 10:38:44 +02:00
if results != nil {
exceptions = results . Exceptions
celexceptions = results . CELExceptions
}
2024-02-01 03:58:14 +05:30
}
2024-09-09 13:42:16 -07:00
if ! c . Stdin && ! c . PolicyReport && ! c . GenerateExceptions {
2023-09-11 12:49:02 +02:00
var policyRulesCount int
for _ , policy := range policies {
2024-10-16 15:24:37 +02:00
policyRulesCount += len ( autogen . Default . ComputeRules ( policy , "" ) )
2023-09-11 12:49:02 +02:00
}
2025-01-17 10:53:17 +01:00
// account for vaps
2024-01-23 13:47:38 +02:00
policyRulesCount += len ( vaps )
2025-01-17 10:53:17 +01:00
// account for vps
policyRulesCount += len ( vps )
2025-02-19 10:38:44 +02:00
exceptionsCount := len ( exceptions )
exceptionsCount += len ( celexceptions )
if exceptionsCount > 0 {
fmt . Fprintf ( out , "\nApplying %d policy rule(s) to %d resource(s) with %d exception(s)...\n" , policyRulesCount , len ( resources ) , exceptionsCount )
2024-02-01 03:58:14 +05:30
} else {
fmt . Fprintf ( out , "\nApplying %d policy rule(s) to %d resource(s)...\n" , policyRulesCount , len ( resources ) )
}
2023-09-11 12:49:02 +02:00
}
2025-01-17 10:53:17 +01:00
rc , resources1 , responses1 , err := c . applyPolicies (
2023-09-14 01:55:19 +02:00
out ,
2023-12-19 15:45:53 +01:00
& store ,
2023-09-11 12:49:02 +02:00
variables ,
policies ,
resources ,
2024-02-01 03:58:14 +05:30
exceptions ,
2025-01-15 11:19:57 +01:00
& skippedInvalidPolicies ,
2023-09-11 12:49:02 +02:00
dClient ,
userInfo ,
mutateLogPathIsDir ,
)
2023-08-18 15:58:47 +05:30
if err != nil {
2025-01-15 11:19:57 +01:00
return rc , resources1 , skippedInvalidPolicies , responses1 , err
2023-08-18 15:58:47 +05:30
}
2025-01-17 10:53:17 +01:00
responses2 , err := c . applyValidatingAdmissionPolicies ( vaps , vapBindings , resources1 , variables . NamespaceSelectors ( ) , rc , dClient )
if err != nil {
return rc , resources1 , skippedInvalidPolicies , responses1 , err
}
2025-02-19 10:38:44 +02:00
responses3 , err := c . applyValidatingPolicies ( vps , celexceptions , resources1 , variables . Namespace , rc , dClient )
2023-08-18 15:58:47 +05:30
if err != nil {
2025-01-15 11:19:57 +01:00
return rc , resources1 , skippedInvalidPolicies , responses1 , err
2023-08-18 15:58:47 +05:30
}
2023-09-11 12:49:02 +02:00
var responses [ ] engineapi . EngineResponse
responses = append ( responses , responses1 ... )
responses = append ( responses , responses2 ... )
2025-01-17 10:53:17 +01:00
responses = append ( responses , responses3 ... )
2025-01-15 11:19:57 +01:00
return rc , resources1 , skippedInvalidPolicies , responses , nil
2023-08-18 15:58:47 +05:30
}
2025-01-15 11:19:57 +01:00
func ( c * ApplyCommandConfig ) getMutateLogPathIsDir ( ) ( bool , error ) {
2023-08-18 15:58:47 +05:30
mutateLogPathIsDir , err := checkMutateLogPath ( c . MutateLogPath )
if err != nil {
2025-01-15 11:19:57 +01:00
return false , fmt . Errorf ( "failed to create file/folder (%w)" , err )
2023-08-18 15:58:47 +05:30
}
2025-01-15 11:19:57 +01:00
return mutateLogPathIsDir , nil
2023-08-18 15:58:47 +05:30
}
2025-01-17 10:53:17 +01:00
func ( c * ApplyCommandConfig ) applyValidatingAdmissionPolicies (
2025-01-31 16:21:43 +02:00
vaps [ ] admissionregistrationv1 . ValidatingAdmissionPolicy ,
vapBindings [ ] admissionregistrationv1 . ValidatingAdmissionPolicyBinding ,
2023-09-06 16:03:51 +02:00
resources [ ] * unstructured . Unstructured ,
2024-02-21 09:52:25 +02:00
namespaceSelectorMap map [ string ] map [ string ] string ,
2023-09-06 16:03:51 +02:00
rc * processor . ResultCounts ,
dClient dclient . Interface ,
2023-09-11 12:49:02 +02:00
) ( [ ] engineapi . EngineResponse , error ) {
var responses [ ] engineapi . EngineResponse
2023-08-18 15:58:47 +05:30
for _ , resource := range resources {
2023-09-11 12:49:02 +02:00
processor := processor . ValidatingAdmissionPolicyProcessor {
2024-02-21 09:52:25 +02:00
Policies : vaps ,
Bindings : vapBindings ,
Resource : resource ,
NamespaceSelectorMap : namespaceSelectorMap ,
PolicyReport : c . PolicyReport ,
Rc : rc ,
Client : dClient ,
2023-09-11 12:49:02 +02:00
}
ers , err := processor . ApplyPolicyOnResource ( )
if err != nil {
2024-05-23 13:46:33 +05:30
if c . ContinueOnFail {
2024-05-06 05:00:54 -04:00
fmt . Printf ( "failed to apply policies on resource %s (%v)\n" , resource . GetName ( ) , err )
continue
}
2023-09-12 18:07:06 +02:00
return responses , fmt . Errorf ( "failed to apply policies on resource %s (%w)" , resource . GetName ( ) , err )
2022-07-25 07:49:51 +02:00
}
2023-09-11 12:49:02 +02:00
responses = append ( responses , ers ... )
2023-08-18 15:58:47 +05:30
}
2023-09-11 12:49:02 +02:00
return responses , nil
2023-08-18 15:58:47 +05:30
}
2025-01-17 10:53:17 +01:00
func ( c * ApplyCommandConfig ) applyValidatingPolicies (
2025-02-11 19:05:22 +02:00
vps [ ] policiesv1alpha1 . ValidatingPolicy ,
2025-02-19 10:38:44 +02:00
exceptions [ ] * policiesv1alpha1 . CELPolicyException ,
2025-01-17 10:53:17 +01:00
resources [ ] * unstructured . Unstructured ,
2025-01-20 12:43:13 +01:00
namespaceProvider func ( string ) * corev1 . Namespace ,
2025-02-18 22:51:36 +02:00
rc * processor . ResultCounts ,
2025-01-24 16:42:58 +01:00
dclient dclient . Interface ,
2025-01-17 10:53:17 +01:00
) ( [ ] engineapi . EngineResponse , error ) {
ctx := context . TODO ( )
compiler := celpolicy . NewCompiler ( )
2025-02-19 10:38:44 +02:00
provider , err := engine . NewProvider ( compiler , vps , exceptions )
2025-01-20 10:43:05 +01:00
if err != nil {
return nil , err
2025-01-17 10:53:17 +01:00
}
2025-01-30 00:07:01 +01:00
eng := engine . NewEngine ( provider , namespaceProvider , nil )
2025-01-24 16:42:58 +01:00
// TODO: mock when no cluster provided
var contextProvider celpolicy . Context
if dclient != nil {
2025-02-03 19:12:40 +05:30
contextProvider , err = celpolicy . NewContextProvider (
dclient . GetKubeClient ( ) ,
[ ] imagedataloader . Option { imagedataloader . WithLocalCredentials ( c . RegistryAccess ) } ,
)
if err != nil {
return nil , err
}
2025-01-24 16:42:58 +01:00
}
2025-01-20 10:43:05 +01:00
responses := make ( [ ] engineapi . EngineResponse , 0 )
2025-01-17 10:53:17 +01:00
for _ , resource := range resources {
2025-02-06 04:49:41 +01:00
request := engine . Request (
contextProvider ,
resource . GroupVersionKind ( ) ,
// TODO
schema . GroupVersionResource { } ,
// TODO
"" ,
resource . GetName ( ) ,
resource . GetNamespace ( ) ,
admissionv1 . Create ,
resource ,
nil ,
false ,
nil ,
)
2025-01-20 10:43:05 +01:00
response , err := eng . Handle ( ctx , request )
2025-01-17 10:53:17 +01:00
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 )
}
2025-01-20 10:43:05 +01:00
// transform response into legacy engine responses
for _ , r := range response . Policies {
2025-01-21 08:59:34 +01:00
engineResponse := engineapi . EngineResponse {
2025-01-20 10:43:05 +01:00
Resource : * response . Resource ,
PolicyResponse : engineapi . PolicyResponse {
Rules : r . Rules ,
} ,
2025-01-21 08:59:34 +01:00
}
2025-01-22 17:32:30 +01:00
engineResponse = engineResponse . WithPolicy ( engineapi . NewValidatingPolicy ( & r . Policy ) )
2025-02-18 22:51:36 +02:00
rc . AddValidatingPolicyResponse ( engineResponse )
2025-01-21 08:59:34 +01:00
responses = append ( responses , engineResponse )
2025-01-20 10:43:05 +01:00
}
2025-01-17 10:53:17 +01:00
}
return responses , nil
}
func ( c * ApplyCommandConfig ) applyPolicies (
2023-09-14 01:55:19 +02:00
out io . Writer ,
2023-12-19 15:45:53 +01:00
store * store . Store ,
2023-09-06 16:03:51 +02:00
vars * variables . Variables ,
policies [ ] kyvernov1 . PolicyInterface ,
resources [ ] * unstructured . Unstructured ,
2024-06-24 23:36:55 +07:00
exceptions [ ] * kyvernov2 . PolicyException ,
2024-02-01 15:50:58 +01:00
skipInvalidPolicies * SkippedInvalidPolicies ,
2023-09-06 16:03:51 +02:00
dClient dclient . Interface ,
2024-06-20 11:44:43 +02:00
userInfo * kyvernov2 . RequestInfo ,
2023-09-06 16:03:51 +02:00
mutateLogPathIsDir bool ,
2023-09-11 12:49:02 +02:00
) ( * processor . ResultCounts , [ ] * unstructured . Unstructured , [ ] engineapi . EngineResponse , error ) {
2023-09-06 16:03:51 +02:00
if vars != nil {
2023-12-19 15:45:53 +01:00
vars . SetInStore ( store )
2023-08-18 15:58:47 +05:30
}
2024-01-29 05:16:09 +05:30
var rc processor . ResultCounts
2023-09-11 15:41:36 +02:00
// validate policies
2024-05-20 17:16:35 +08:00
validPolicies := make ( [ ] kyvernov1 . PolicyInterface , 0 , len ( policies ) )
2023-09-11 15:41:36 +02:00
for _ , pol := range policies {
// TODO we should return this info to the caller
2024-08-20 01:55:32 -07:00
sa := config . KyvernoUserName ( config . KyvernoServiceAccountName ( ) )
2025-02-18 17:51:14 +02:00
_ , err := policyvalidation . Validate ( pol , nil , nil , true , sa , sa )
2023-09-11 15:41:36 +02:00
if err != nil {
log . Log . Error ( err , "policy validation error" )
2024-01-29 05:16:09 +05:30
rc . IncrementError ( 1 )
2023-09-11 15:41:36 +02:00
if strings . HasPrefix ( err . Error ( ) , "variable 'element.name'" ) {
skipInvalidPolicies . invalid = append ( skipInvalidPolicies . invalid , pol . GetName ( ) )
} else {
skipInvalidPolicies . skipped = append ( skipInvalidPolicies . skipped , pol . GetName ( ) )
2023-08-18 15:58:47 +05:30
}
2023-09-11 15:41:36 +02:00
continue
}
validPolicies = append ( validPolicies , pol )
}
var responses [ ] engineapi . EngineResponse
for _ , resource := range resources {
processor := processor . PolicyProcessor {
2023-12-19 15:45:53 +01:00
Store : store ,
2023-09-11 15:41:36 +02:00
Policies : validPolicies ,
Resource : * resource ,
2024-02-01 03:58:14 +05:30
PolicyExceptions : exceptions ,
2023-09-11 15:41:36 +02:00
MutateLogPath : c . MutateLogPath ,
MutateLogPathIsDir : mutateLogPathIsDir ,
Variables : vars ,
UserInfo : userInfo ,
PolicyReport : c . PolicyReport ,
NamespaceSelectorMap : vars . NamespaceSelectors ( ) ,
Stdin : c . Stdin ,
Rc : & rc ,
PrintPatchResource : true ,
2024-12-19 09:42:54 +02:00
Cluster : c . Cluster ,
2023-09-11 15:41:36 +02:00
Client : dClient ,
AuditWarn : c . AuditWarn ,
Subresources : vars . Subresources ( ) ,
2023-09-14 01:55:19 +02:00
Out : out ,
2023-09-11 15:41:36 +02:00
}
ers , err := processor . ApplyPoliciesOnResource ( )
if err != nil {
2024-05-23 13:46:33 +05:30
if c . ContinueOnFail {
2025-02-13 20:32:02 +05:30
log . Log . V ( 2 ) . Info ( fmt . Sprintf ( "failed to apply policies on resource %s (%s)\n" , resource . GetName ( ) , err . Error ( ) ) )
2024-05-06 05:00:54 -04:00
continue
}
2024-09-10 18:20:53 +05:30
return & rc , resources , responses , fmt . Errorf ( "failed to apply policies on resource %s (%w)" , resource . GetName ( ) , err )
2023-09-11 15:41:36 +02:00
}
2023-09-12 17:20:21 +02:00
responses = append ( responses , ers ... )
2020-12-20 01:21:31 +05:30
}
2024-09-10 18:20:53 +05:30
for _ , policy := range validPolicies {
if policy . GetNamespace ( ) == "" && policy . GetKind ( ) == "Policy" {
2025-02-13 20:32:02 +05:30
log . Log . V ( 3 ) . Info ( fmt . Sprintf ( "Policy %s has no namespace detected. Ensure that namespaced policies are correctly loaded." , policy . GetNamespace ( ) ) )
2024-09-10 18:20:53 +05:30
}
}
2023-09-11 12:49:02 +02:00
return & rc , resources , responses , nil
2023-08-18 15:58:47 +05:30
}
2025-01-31 16:21:43 +02:00
func ( c * ApplyCommandConfig ) loadResources ( out io . Writer , paths [ ] string , policies [ ] kyvernov1 . PolicyInterface , vap [ ] admissionregistrationv1 . ValidatingAdmissionPolicy , dClient dclient . Interface ) ( [ ] * unstructured . Unstructured , error ) {
2024-12-19 09:42:54 +02:00
resources , err := common . GetResourceAccordingToResourcePath ( out , nil , paths , c . Cluster , policies , vap , dClient , c . Namespace , c . PolicyReport , "" )
2023-08-18 15:58:47 +05:30
if err != nil {
2023-09-12 18:07:06 +02:00
return resources , fmt . Errorf ( "failed to load resources (%w)" , err )
2023-08-18 15:58:47 +05:30
}
2023-09-07 21:15:57 +05:30
return resources , nil
2023-08-18 15:58:47 +05:30
}
2025-01-15 11:19:57 +01:00
func ( c * ApplyCommandConfig ) loadPolicies ( ) (
[ ] kyvernov1 . PolicyInterface ,
2025-01-31 16:21:43 +02:00
[ ] admissionregistrationv1 . ValidatingAdmissionPolicy ,
[ ] admissionregistrationv1 . ValidatingAdmissionPolicyBinding ,
2025-02-11 19:05:22 +02:00
[ ] policiesv1alpha1 . ValidatingPolicy ,
2025-01-15 11:19:57 +01:00
error ,
) {
2023-07-04 18:28:22 +02:00
// load policies
2022-12-02 20:03:04 +05:30
var policies [ ] kyvernov1 . PolicyInterface
2025-01-31 16:21:43 +02:00
var vaps [ ] admissionregistrationv1 . ValidatingAdmissionPolicy
var vapBindings [ ] admissionregistrationv1 . ValidatingAdmissionPolicyBinding
2025-02-11 19:05:22 +02:00
var vps [ ] policiesv1alpha1 . ValidatingPolicy
2022-12-02 20:03:04 +05:30
2023-09-06 01:01:31 +02:00
for _ , path := range c . PolicyPaths {
isGit := source . IsGit ( path )
2023-08-16 21:33:42 +05:30
if isGit {
2023-09-06 01:01:31 +02:00
gitSourceURL , err := url . Parse ( path )
2023-08-16 21:33:42 +05:30
if err != nil {
2025-01-17 10:53:17 +01:00
return nil , nil , nil , nil , fmt . Errorf ( "failed to load policies (%w)" , err )
2023-08-16 21:33:42 +05:30
}
pathElems := strings . Split ( gitSourceURL . Path [ 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 )
2025-01-17 10:53:17 +01:00
return nil , nil , nil , nil , fmt . Errorf ( "failed to parse URL (%w)" , err )
2023-08-16 21:33:42 +05:30
}
gitSourceURL . Path = strings . Join ( [ ] string { pathElems [ 0 ] , pathElems [ 1 ] } , "/" )
repoURL := gitSourceURL . String ( )
var gitPathToYamls string
2023-09-06 01:01:31 +02:00
c . GitBranch , gitPathToYamls = common . GetGitBranchOrPolicyPaths ( c . GitBranch , repoURL , path )
fs := memfs . New ( )
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 )
2025-01-17 10:53:17 +01:00
return nil , nil , nil , nil , fmt . Errorf ( "failed to clone repository (%w)" , err )
2023-08-16 21:33:42 +05:30
}
policyYamls , err := gitutils . ListYamls ( fs , gitPathToYamls )
if err != nil {
2025-01-17 10:53:17 +01:00
return nil , nil , nil , nil , fmt . Errorf ( "failed to list YAMLs in repository (%w)" , err )
2023-08-16 21:33:42 +05:30
}
2023-09-06 01:01:31 +02:00
for _ , policyYaml := range policyYamls {
2024-05-21 00:17:49 -07:00
loaderResults , err := policy . Load ( fs , "" , policyYaml )
2025-01-17 17:46:57 +02:00
if loaderResults != nil && loaderResults . NonFatalErrors != nil {
for _ , err := range loaderResults . NonFatalErrors {
log . Log . Error ( err . Error , "Non-fatal parsing error for single document" )
}
}
2023-09-06 01:01:31 +02:00
if err != nil {
continue
}
2024-05-21 00:17:49 -07:00
policies = append ( policies , loaderResults . Policies ... )
vaps = append ( vaps , loaderResults . VAPs ... )
vapBindings = append ( vapBindings , loaderResults . VAPBindings ... )
2025-01-17 10:53:17 +01:00
vps = append ( vps , loaderResults . ValidatingPolicies ... )
2023-09-06 01:01:31 +02:00
}
} else {
2024-05-21 00:17:49 -07:00
loaderResults , err := policy . Load ( nil , "" , path )
2025-01-17 17:46:57 +02:00
if loaderResults != nil && loaderResults . NonFatalErrors != nil {
for _ , err := range loaderResults . NonFatalErrors {
log . Log . Error ( err . Error , "Non-fatal parsing error for single document" )
}
}
2023-09-06 01:01:31 +02:00
if err != nil {
2024-05-21 00:17:49 -07:00
log . Log . V ( 3 ) . Info ( "skipping invalid YAML file" , "path" , path , "error" , err )
} else {
policies = append ( policies , loaderResults . Policies ... )
vaps = append ( vaps , loaderResults . VAPs ... )
vapBindings = append ( vapBindings , loaderResults . VAPBindings ... )
2025-01-17 10:53:17 +01:00
vps = append ( vps , loaderResults . ValidatingPolicies ... )
2023-09-06 01:01:31 +02:00
}
2022-12-02 20:03:04 +05:30
}
2024-09-10 18:20:53 +05:30
for _ , policy := range policies {
if policy . GetNamespace ( ) == "" && policy . GetKind ( ) == "Policy" {
log . Log . V ( 3 ) . Info ( fmt . Sprintf ( "Namespace is empty for a namespaced Policy %s. This might cause incorrect report generation." , policy . GetNamespace ( ) ) )
}
}
2020-12-20 01:21:31 +05:30
}
2025-01-17 10:53:17 +01:00
return policies , vaps , vapBindings , vps , nil
2023-08-18 15:58:47 +05:30
}
2021-08-02 16:38:43 +05:30
2025-01-22 17:32:30 +01:00
func ( c * ApplyCommandConfig ) initStoreAndClusterClient ( store * store . Store , targetResources ... * unstructured . Unstructured ) ( dclient . Interface , error ) {
2023-08-18 15:58:47 +05:30
store . SetLocal ( true )
store . SetRegistryAccess ( c . RegistryAccess )
if c . Cluster {
store . AllowApiCall ( true )
2020-12-20 01:21:31 +05:30
}
2023-08-18 15:58:47 +05:30
var err error
var dClient dclient . Interface
if c . Cluster {
restConfig , err := config . CreateClientConfigWithContext ( c . KubeConfig , c . Context )
2020-12-20 01:21:31 +05:30
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , err
2020-12-20 01:21:31 +05:30
}
2023-08-18 15:58:47 +05:30
kubeClient , err := kubernetes . NewForConfig ( restConfig )
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , err
2020-12-20 01:21:31 +05:30
}
2023-08-18 15:58:47 +05:30
dynamicClient , err := dynamic . NewForConfig ( restConfig )
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , err
2023-08-18 15:58:47 +05:30
}
dClient , err = dclient . NewClient ( context . Background ( ) , dynamicClient , kubeClient , 15 * time . Minute )
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , err
2020-12-20 01:21:31 +05:30
}
2020-03-06 03:00:18 +05:30
}
2024-12-19 09:42:54 +02:00
if len ( targetResources ) > 0 && ! c . Cluster {
var targets [ ] runtime . Object
for _ , t := range targetResources {
targets = append ( targets , t )
}
dClient , err = dclient . NewFakeClient ( runtime . NewScheme ( ) , map [ schema . GroupVersionResource ] string { } , targets ... )
dClient . SetDiscovery ( dclient . NewFakeDiscoveryClient ( nil ) )
if err != nil {
2025-01-15 11:19:57 +01:00
return nil , err
2024-12-19 09:42:54 +02:00
}
}
2025-01-15 11:19:57 +01:00
return dClient , err
2023-08-18 15:58:47 +05:30
}
2020-03-06 03:00:18 +05:30
2025-01-15 11:19:57 +01:00
func ( c * ApplyCommandConfig ) cleanPreviousContent ( mutateLogPathIsDir bool ) error {
2023-08-18 15:58:47 +05:30
// empty the previous contents of the file just in case if the file already existed before with some content(so as to perform overwrites)
// the truncation of files for the case when mutateLogPath is dir, is handled under pkg/kyverno/apply/common.go
if ! mutateLogPathIsDir && c . MutateLogPath != "" {
c . MutateLogPath = filepath . Clean ( c . MutateLogPath )
// Necessary for us to include the file via variable as it is part of the CLI.
_ , err := os . OpenFile ( c . MutateLogPath , os . O_TRUNC | os . O_WRONLY , 0 o600 ) // #nosec G304
if err != nil {
2025-01-15 11:19:57 +01:00
return fmt . Errorf ( "failed to truncate the existing file at %s (%w)" , c . MutateLogPath , err )
2023-05-10 11:12:53 +03:00
}
}
2025-01-15 11:19:57 +01:00
return nil
2023-08-18 15:58:47 +05:30
}
2023-05-10 11:12:53 +03:00
2025-01-15 11:19:57 +01:00
func ( c * ApplyCommandConfig ) checkArguments ( ) error {
2023-08-18 15:58:47 +05:30
if c . ValuesFile != "" && c . Variables != nil {
2025-01-15 11:19:57 +01:00
return fmt . Errorf ( "pass the values either using set flag or values_file flag" )
2023-08-18 15:58:47 +05:30
}
if len ( c . PolicyPaths ) == 0 {
2025-01-15 11:19:57 +01:00
return fmt . Errorf ( "require policy" )
2023-08-18 15:58:47 +05:30
}
if ( len ( c . PolicyPaths ) > 0 && c . PolicyPaths [ 0 ] == "-" ) && len ( c . ResourcePaths ) > 0 && c . ResourcePaths [ 0 ] == "-" {
2025-01-15 11:19:57 +01:00
return fmt . Errorf ( "a stdin pipe can be used for either policies or resources, not both" )
2023-08-18 15:58:47 +05:30
}
if len ( c . ResourcePaths ) == 0 && ! c . Cluster {
2025-01-15 11:19:57 +01:00
return fmt . Errorf ( "resource file(s) or cluster required" )
2023-08-18 15:58:47 +05:30
}
2025-01-15 11:19:57 +01:00
return nil
2020-03-06 03:00:18 +05:30
}
2024-02-29 08:08:21 -05:00
type WarnExitCodeError struct {
ExitCode int
}
func ( w WarnExitCodeError ) Error ( ) string {
return fmt . Sprintf ( "exit as warnExitCode is %d" , w . ExitCode )
}
func exit ( out io . Writer , rc * processor . ResultCounts , warnExitCode int , warnNoPassed bool ) error {
2024-05-07 10:58:09 +02:00
if rc . Fail > 0 {
2024-02-02 02:11:17 +05:30
return fmt . Errorf ( "exit as there are policy violations" )
2024-05-07 10:58:09 +02:00
} else if rc . Error > 0 {
2024-02-02 02:11:17 +05:30
return fmt . Errorf ( "exit as there are policy errors" )
2024-05-07 10:58:09 +02:00
} else if rc . Warn > 0 && warnExitCode != 0 {
2024-02-29 08:08:21 -05:00
fmt . Printf ( "exit as warnExitCode is %d" , warnExitCode )
return WarnExitCodeError {
ExitCode : warnExitCode ,
}
2024-05-07 10:58:09 +02:00
} else if rc . Pass == 0 && warnNoPassed {
2024-02-29 08:08:21 -05:00
fmt . Println ( out , "exit as no objects satisfied policy" )
return WarnExitCodeError {
ExitCode : warnExitCode ,
}
2020-10-30 16:38:19 +05:30
}
2023-09-07 21:15:57 +05:30
return nil
2020-10-30 16:38:19 +05:30
}