2021-02-07 20:26:56 -08:00
package test
2021-02-01 16:22:41 +05:30
import (
"encoding/json"
2021-02-07 20:26:56 -08:00
"fmt"
2021-02-01 16:22:41 +05:30
"io/ioutil"
2021-02-07 20:26:56 -08:00
"net/url"
2021-02-01 16:22:41 +05:30
"os"
"path/filepath"
2021-10-01 14:16:33 +05:30
"regexp"
2021-02-07 20:26:56 -08:00
"sort"
2021-02-01 16:22:41 +05:30
"strings"
2021-08-21 19:35:17 +02:00
"time"
2021-02-07 20:26:56 -08:00
"github.com/fatih/color"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
2021-10-01 14:16:33 +05:30
"github.com/go-logr/logr"
2021-02-07 20:26:56 -08:00
"github.com/kataras/tablewriter"
2021-08-21 19:35:17 +02:00
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha2"
2021-02-07 20:26:56 -08:00
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/engine/utils"
2021-10-01 14:16:33 +05:30
"github.com/kyverno/kyverno/pkg/generate"
2021-02-01 16:22:41 +05:30
"github.com/kyverno/kyverno/pkg/kyverno/common"
2021-02-07 20:26:56 -08:00
sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
2021-05-03 19:54:19 -04:00
"github.com/kyverno/kyverno/pkg/kyverno/store"
2021-02-01 16:22:41 +05:30
"github.com/kyverno/kyverno/pkg/openapi"
2021-02-07 20:26:56 -08:00
policy2 "github.com/kyverno/kyverno/pkg/policy"
2021-02-01 16:22:41 +05:30
"github.com/kyverno/kyverno/pkg/policyreport"
2021-05-07 19:27:15 -04:00
util "github.com/kyverno/kyverno/pkg/utils"
2021-02-01 16:22:41 +05:30
"github.com/lensesio/tableprinter"
2021-02-07 20:26:56 -08:00
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
2021-08-21 19:35:17 +02:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-10-01 14:16:33 +05:30
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2021-02-07 20:26:56 -08:00
"k8s.io/apimachinery/pkg/util/yaml"
log "sigs.k8s.io/controller-runtime/pkg/log"
2021-02-01 16:22:41 +05:30
)
2021-10-01 14:16:33 +05:30
var longHelp = `
Test command provides a facility to test policies on resources . For that , user needs to provide the path of the folder containing test . yaml file .
kyverno test / path / to / folderContaningTestYamls
or
kyverno test / path / to / githubRepository
The test . yaml file is configuration file for test command . It consists of 4 parts : -
"policies" ( required ) -- > element lists one or more path of policies
"resources" ( required ) -- > element lists one or more path of resources .
"variables" ( optional ) -- > element with one variables files
"results" ( required ) -- > element lists one more expected result .
`
var exampleHelp = `
For Validate Policy
test . yaml
- name : test - 1
policies :
- < path >
- < path >
resources :
- < path >
- < path >
results :
- policy : < name >
rule : < name >
resource : < name >
namespace : < name > ( OPTIONAL )
kind : < name >
result : < pass / fail / skip >
For more visit -- > https : //kyverno.io/docs/kyverno-cli/#test
For Mutate Policy
1 ) Policy ( Namespaced - policy )
test . yaml
- name : test - 1
policies :
- < path >
- < path >
resources :
- < path >
- < path >
results :
- policy : < policy_namespace > / < policy_name >
rule : < name >
resource : < name >
namespace : < name > ( OPTIONAL )
patchedResource : < path >
kind : < name >
result : < pass / fail / skip >
2 ) ClusterPolicy ( cluster - wide policy )
test . yaml
- name : test - 1
policies :
- < path >
- < path >
resources :
- < path >
- < path >
results :
- policy : < name >
rule : < name >
resource : < name >
namespace : < name > ( OPTIONAL )
kind : < name >
patchedResource : < path >
result : < pass / fail / skip >
NOTE : -
In the results section , policy ( if ClusterPolicy ) or < policy_namespace > / < policy_name > ( if Policy ) , rule , resource , kind and result are mandatory fields for all type of policy .
pass -- > patched Resource generated from engine equals to patched Resource provided by the user .
fail -- > patched Resource generated from engine is not equals to patched provided by the user .
skip -- > rule is not applied .
`
2021-02-01 16:22:41 +05:30
// Command returns version command
func Command ( ) * cobra . Command {
2021-02-18 01:00:41 +05:30
var cmd * cobra . Command
var valuesFile , fileName string
cmd = & cobra . Command {
2021-10-01 14:16:33 +05:30
Use : "test" ,
Short : "run tests from directory" ,
Long : longHelp ,
Example : exampleHelp ,
2021-02-01 23:34:15 +05:30
RunE : func ( cmd * cobra . Command , dirPath [ ] string ) ( err error ) {
2021-02-01 16:22:41 +05:30
defer func ( ) {
if err != nil {
if ! sanitizederror . IsErrorSanitized ( err ) {
log . Log . Error ( err , "failed to sanitize" )
err = fmt . Errorf ( "internal error" )
}
}
} ( )
2021-03-05 05:39:18 +05:30
_ , err = testCommandExecute ( dirPath , valuesFile , fileName )
2021-02-01 16:22:41 +05:30
if err != nil {
2021-02-02 18:43:19 +05:30
log . Log . V ( 3 ) . Info ( "a directory is required" )
2021-02-01 16:22:41 +05:30
return err
}
return nil
} ,
}
2021-02-18 01:00:41 +05:30
cmd . Flags ( ) . StringVarP ( & fileName , "file-name" , "f" , "test.yaml" , "test filename" )
return cmd
2021-02-01 16:22:41 +05:30
}
type Test struct {
2021-02-07 20:26:56 -08:00
Name string ` json:"name" `
Policies [ ] string ` json:"policies" `
Resources [ ] string ` json:"resources" `
Variables string ` json:"variables" `
Results [ ] TestResults ` json:"results" `
2021-02-01 16:22:41 +05:30
}
type TestResults struct {
2021-09-07 20:23:03 +05:30
Policy string ` json:"policy" `
Rule string ` json:"rule" `
Result report . PolicyResult ` json:"result" `
Status report . PolicyResult ` json:"status" `
Resource string ` json:"resource" `
2021-10-01 14:16:33 +05:30
Kind string ` json:"kind" `
Namespace string ` json:"namespace" `
PatchedResource string ` json:"patchedResource" `
2021-09-07 22:27:29 +05:30
AutoGeneratedRule string ` json:"auto_generated_rule" `
2021-02-01 16:22:41 +05:30
}
type ReportResult struct {
2021-02-07 20:26:56 -08:00
TestResults
2021-02-01 16:22:41 +05:30
Resources [ ] * corev1 . ObjectReference ` json:"resources" `
}
type Resource struct {
2021-02-07 20:26:56 -08:00
Name string ` json:"name" `
2021-02-01 16:22:41 +05:30
Values map [ string ] string ` json:"values" `
}
type Table struct {
2021-02-07 20:26:56 -08:00
ID int ` header:"#" `
2021-09-02 23:11:35 +05:30
Policy string ` header:"policy" `
Rule string ` header:"rule" `
Resource string ` header:"resource" `
2021-02-07 20:26:56 -08:00
Result string ` header:"result" `
2021-02-01 16:22:41 +05:30
}
type Policy struct {
Name string ` json:"name" `
Resources [ ] Resource ` json:"resources" `
}
type Values struct {
Policies [ ] Policy ` json:"policies" `
}
2021-03-05 05:39:18 +05:30
type resultCounts struct {
2021-09-02 23:11:35 +05:30
Skip int
Pass int
Fail int
2021-03-05 05:39:18 +05:30
}
2021-09-02 23:11:35 +05:30
func testCommandExecute ( dirPath [ ] string , valuesFile string , fileName string ) ( rc * resultCounts , err error ) {
2021-02-01 16:22:41 +05:30
var errors [ ] error
fs := memfs . New ( )
2021-09-02 23:11:35 +05:30
rc = & resultCounts { }
2021-03-12 01:16:36 +05:30
var testYamlCount int
2021-09-03 16:41:13 +05:30
2021-02-01 23:34:15 +05:30
if len ( dirPath ) == 0 {
2021-03-05 05:39:18 +05:30
return rc , sanitizederror . NewWithError ( fmt . Sprintf ( "a directory is required" ) , err )
2021-02-07 20:26:56 -08:00
}
2021-09-03 16:41:13 +05:30
2021-02-02 18:43:19 +05:30
if strings . Contains ( string ( dirPath [ 0 ] ) , "https://" ) {
2021-03-05 05:39:18 +05:30
gitURL , err := url . Parse ( dirPath [ 0 ] )
2021-02-01 16:22:41 +05:30
if err != nil {
2021-03-05 05:39:18 +05:30
return rc , sanitizederror . NewWithError ( "failed to parse URL" , err )
2021-02-01 16:22:41 +05:30
}
2021-09-03 16:41:13 +05:30
2021-03-05 05:39:18 +05:30
pathElems := strings . Split ( gitURL . Path [ 1 : ] , "/" )
2021-09-02 23:11:35 +05:30
if len ( pathElems ) <= 1 {
2021-03-05 05:39:18 +05:30
err := fmt . Errorf ( "invalid URL path %s - expected https://github.com/:owner/:repository/:branch" , gitURL . Path )
fmt . Printf ( "Error: failed to parse URL \nCause: %s\n" , err )
os . Exit ( 1 )
2021-02-01 16:22:41 +05:30
}
2021-09-03 16:41:13 +05:30
2021-03-12 01:16:36 +05:30
gitURL . Path = strings . Join ( [ ] string { pathElems [ 0 ] , pathElems [ 1 ] } , "/" )
2021-03-05 05:39:18 +05:30
repoURL := gitURL . String ( )
2021-03-12 01:16:36 +05:30
branch := strings . ReplaceAll ( dirPath [ 0 ] , repoURL + "/" , "" )
2021-09-02 23:11:35 +05:30
if branch == "" {
branch = "main"
}
2021-09-03 16:41:13 +05:30
2021-03-12 01:16:36 +05:30
_ , cloneErr := clone ( repoURL , fs , branch )
if cloneErr != nil {
fmt . Printf ( "Error: failed to clone repository \nCause: %s\n" , cloneErr )
log . Log . V ( 3 ) . Info ( fmt . Sprintf ( "failed to clone repository %v as it is not valid" , repoURL ) , "error" , cloneErr )
2021-03-05 05:39:18 +05:30
os . Exit ( 1 )
2021-02-01 16:22:41 +05:30
}
2021-09-03 16:41:13 +05:30
2021-02-01 16:22:41 +05:30
policyYamls , err := listYAMLs ( fs , "/" )
if err != nil {
2021-03-05 05:39:18 +05:30
return rc , sanitizederror . NewWithError ( "failed to list YAMLs in repository" , err )
2021-02-01 16:22:41 +05:30
}
sort . Strings ( policyYamls )
2021-09-03 16:41:13 +05:30
2021-02-01 16:22:41 +05:30
for _ , yamlFilePath := range policyYamls {
2021-02-07 20:26:56 -08:00
file , err := fs . Open ( yamlFilePath )
2021-05-03 08:20:22 -04:00
if err != nil {
errors = append ( errors , sanitizederror . NewWithError ( "Error: failed to open file" , err ) )
continue
}
2021-09-03 16:41:13 +05:30
2021-02-18 01:00:41 +05:30
if strings . Contains ( file . Name ( ) , fileName ) {
2021-03-12 01:16:36 +05:30
testYamlCount ++
2021-02-18 01:00:41 +05:30
policyresoucePath := strings . Trim ( yamlFilePath , fileName )
bytes , err := ioutil . ReadAll ( file )
if err != nil {
2021-05-03 08:20:22 -04:00
errors = append ( errors , sanitizederror . NewWithError ( "Error: failed to read file" , err ) )
2021-02-18 01:00:41 +05:30
continue
}
2021-09-03 16:41:13 +05:30
2021-02-18 01:00:41 +05:30
policyBytes , err := yaml . ToJSON ( bytes )
if err != nil {
2021-05-03 08:20:22 -04:00
errors = append ( errors , sanitizederror . NewWithError ( "failed to convert to JSON" , err ) )
2021-02-18 01:00:41 +05:30
continue
}
2021-09-03 16:41:13 +05:30
2021-03-05 05:39:18 +05:30
if err := applyPoliciesFromPath ( fs , policyBytes , valuesFile , true , policyresoucePath , rc ) ; err != nil {
return rc , sanitizederror . NewWithError ( "failed to apply test command" , err )
2021-02-18 01:00:41 +05:30
}
2021-02-01 16:22:41 +05:30
}
2021-05-03 08:20:22 -04:00
}
2021-09-03 16:41:13 +05:30
2021-05-03 08:20:22 -04:00
if testYamlCount == 0 {
fmt . Printf ( "\n No test yamls available \n" )
2021-02-01 16:22:41 +05:30
}
2021-09-03 16:41:13 +05:30
2021-02-01 16:22:41 +05:30
} else {
2021-02-01 23:34:15 +05:30
path := filepath . Clean ( dirPath [ 0 ] )
2021-05-03 08:20:22 -04:00
errors = getLocalDirTestFiles ( fs , path , fileName , valuesFile , rc )
}
2021-09-03 16:41:13 +05:30
2021-05-03 08:20:22 -04:00
if len ( errors ) > 0 && log . Log . V ( 1 ) . Enabled ( ) {
fmt . Printf ( "ignoring errors: \n" )
for _ , e := range errors {
fmt . Printf ( " %v \n" , e . Error ( ) )
2021-02-01 16:22:41 +05:30
}
}
2021-09-02 03:21:07 +05:30
if rc . Fail > 0 {
2021-03-05 05:39:18 +05:30
os . Exit ( 1 )
}
os . Exit ( 0 )
return rc , nil
2021-02-01 16:22:41 +05:30
}
2021-02-01 23:34:15 +05:30
2021-09-02 23:11:35 +05:30
func getLocalDirTestFiles ( fs billy . Filesystem , path , fileName , valuesFile string , rc * resultCounts ) [ ] error {
2021-05-03 08:20:22 -04:00
var errors [ ] error
2021-02-18 01:00:41 +05:30
files , err := ioutil . ReadDir ( path )
if err != nil {
2021-05-03 08:20:22 -04:00
return [ ] error { fmt . Errorf ( "failed to read %v: %v" , path , err . Error ( ) ) }
2021-02-18 01:00:41 +05:30
}
for _ , file := range files {
if file . IsDir ( ) {
2021-05-03 08:20:22 -04:00
getLocalDirTestFiles ( fs , filepath . Join ( path , file . Name ( ) ) , fileName , valuesFile , rc )
2021-02-18 01:00:41 +05:30
continue
}
if strings . Contains ( file . Name ( ) , fileName ) {
yamlFile , err := ioutil . ReadFile ( filepath . Join ( path , file . Name ( ) ) )
if err != nil {
2021-05-03 08:20:22 -04:00
errors = append ( errors , sanitizederror . NewWithError ( "unable to read yaml" , err ) )
2021-02-18 01:00:41 +05:30
continue
}
valuesBytes , err := yaml . ToJSON ( yamlFile )
if err != nil {
2021-05-03 08:20:22 -04:00
errors = append ( errors , sanitizederror . NewWithError ( "failed to convert json" , err ) )
2021-02-18 01:00:41 +05:30
continue
}
2021-03-05 05:39:18 +05:30
if err := applyPoliciesFromPath ( fs , valuesBytes , valuesFile , false , path , rc ) ; err != nil {
2021-05-03 08:20:22 -04:00
errors = append ( errors , sanitizederror . NewWithError ( fmt . Sprintf ( "failed to apply test command from file %s" , file . Name ( ) ) , err ) )
2021-02-18 01:00:41 +05:30
continue
}
}
}
2021-05-03 08:20:22 -04:00
return errors
2021-02-18 01:00:41 +05:30
}
2021-10-01 14:16:33 +05:30
func buildPolicyResults ( resps [ ] * response . EngineResponse , testResults [ ] TestResults , infos [ ] policyreport . Info , policyResourcePath string , fs billy . Filesystem , isGit bool ) ( map [ string ] report . PolicyReportResult , [ ] TestResults ) {
2021-05-07 19:27:15 -04:00
results := make ( map [ string ] report . PolicyReportResult )
2021-08-21 19:35:17 +02:00
now := metav1 . Timestamp { Seconds : time . Now ( ) . Unix ( ) }
2021-09-07 20:23:03 +05:30
2021-05-07 19:27:15 -04:00
for _ , resp := range resps {
2021-06-30 00:43:11 +03:00
policyName := resp . PolicyResponse . Policy . Name
2021-05-07 19:27:15 -04:00
resourceName := resp . PolicyResponse . Resource . Name
2021-10-01 14:16:33 +05:30
resourceKind := resp . PolicyResponse . Resource . Kind
resourceNamespace := resp . PolicyResponse . Resource . Namespace
policyNamespace := resp . PolicyResponse . Policy . Namespace
2021-07-06 10:44:43 +05:30
2021-05-07 19:27:15 -04:00
var rules [ ] string
for _ , rule := range resp . PolicyResponse . Rules {
rules = append ( rules , rule . Name )
}
result := report . PolicyReportResult {
Policy : policyName ,
Resources : [ ] * corev1 . ObjectReference {
{
Name : resourceName ,
} ,
} ,
}
2021-10-01 14:16:33 +05:30
var patcheResourcePath [ ] string
2021-09-07 20:23:03 +05:30
for i , test := range testResults {
2021-10-01 14:16:33 +05:30
var userDefinedPolicyNamespace string
var userDefinedPolicyName string
found , _ := isNamespacedPolicy ( test . Policy )
if found {
userDefinedPolicyNamespace , userDefinedPolicyName = getUserDefinedPolicyNameAndNamespace ( test . Policy )
test . Policy = userDefinedPolicyName
}
2021-05-07 19:27:15 -04:00
if test . Policy == policyName && test . Resource == resourceName {
2021-10-01 14:16:33 +05:30
var resultsKey string
resultsKey = GetResultKeyAccordingToTestResults ( userDefinedPolicyNamespace , test . Policy , test . Rule , test . Namespace , test . Kind , test . Resource )
2021-05-07 19:27:15 -04:00
if ! util . ContainsString ( rules , test . Rule ) {
2021-09-07 20:23:03 +05:30
if ! util . ContainsString ( rules , "autogen-" + test . Rule ) {
2021-09-07 22:27:29 +05:30
if ! util . ContainsString ( rules , "autogen-cronjob-" + test . Rule ) {
result . Result = report . StatusSkip
} else {
testResults [ i ] . AutoGeneratedRule = "autogen-cronjob"
test . Rule = "autogen-cronjob-" + test . Rule
2021-10-01 14:16:33 +05:30
resultsKey = GetResultKeyAccordingToTestResults ( userDefinedPolicyNamespace , test . Policy , test . Rule , test . Namespace , test . Kind , test . Resource )
2021-09-07 22:27:29 +05:30
}
2021-09-07 20:23:03 +05:30
} else {
2021-09-07 22:27:29 +05:30
testResults [ i ] . AutoGeneratedRule = "autogen"
2021-09-07 20:23:03 +05:30
test . Rule = "autogen-" + test . Rule
2021-10-01 14:16:33 +05:30
resultsKey = GetResultKeyAccordingToTestResults ( userDefinedPolicyNamespace , test . Policy , test . Rule , test . Namespace , test . Kind , test . Resource )
}
if results [ resultsKey ] . Result == "" {
result . Result = report . StatusSkip
results [ resultsKey ] = result
2021-09-07 20:23:03 +05:30
}
2021-05-07 19:27:15 -04:00
}
2021-09-07 22:27:29 +05:30
2021-10-01 14:16:33 +05:30
patcheResourcePath = append ( patcheResourcePath , test . PatchedResource )
2021-05-07 19:27:15 -04:00
if _ , ok := results [ resultsKey ] ; ! ok {
results [ resultsKey ] = result
}
}
2021-10-01 14:16:33 +05:30
}
for _ , rule := range resp . PolicyResponse . Rules {
if rule . Type != utils . Mutation . String ( ) {
continue
}
var resultsKey [ ] string
var resultKey string
var result report . PolicyReportResult
resultsKey = GetAllPossibleResultsKey ( policyNamespace , policyName , rule . Name , resourceNamespace , resourceKind , resourceName )
for _ , resultK := range resultsKey {
if val , ok := results [ resultK ] ; ok {
result = val
resultKey = resultK
} else {
continue
}
var x string
for _ , path := range patcheResourcePath {
result . Result = report . StatusFail
x = getAndComparePatchedResource ( path , resp . PatchedResource , isGit , policyResourcePath , fs )
if x == "pass" {
result . Result = report . StatusPass
break
}
}
results [ resultKey ] = result
}
2021-05-07 19:27:15 -04:00
}
}
2021-09-07 20:23:03 +05:30
2021-02-01 16:22:41 +05:30
for _ , info := range infos {
for _ , infoResult := range info . Results {
for _ , rule := range infoResult . Rules {
if rule . Type != utils . Validation . String ( ) {
continue
}
2021-09-07 22:27:29 +05:30
2021-05-07 19:27:15 -04:00
var result report . PolicyReportResult
2021-10-01 14:16:33 +05:30
var resultsKey [ ] string
var resultKey string
resultsKey = GetAllPossibleResultsKey ( "" , info . PolicyName , rule . Name , infoResult . Resource . Namespace , infoResult . Resource . Kind , infoResult . Resource . Name )
for _ , resultK := range resultsKey {
if val , ok := results [ resultK ] ; ok {
result = val
resultKey = resultK
} else {
continue
}
2021-02-01 16:22:41 +05:30
}
2021-09-07 22:27:29 +05:30
2021-09-02 23:11:35 +05:30
result . Rule = rule . Name
2021-08-21 19:35:17 +02:00
result . Result = report . PolicyResult ( rule . Check )
result . Source = policyreport . SourceValue
result . Timestamp = now
2021-10-01 14:16:33 +05:30
results [ resultKey ] = result
2021-02-01 16:22:41 +05:30
}
}
}
2021-09-02 23:11:35 +05:30
2021-09-07 20:23:03 +05:30
return results , testResults
2021-02-01 16:22:41 +05:30
}
2021-10-01 14:16:33 +05:30
func GetAllPossibleResultsKey ( policyNamespace , policy , rule , namespace , kind , resource string ) [ ] string {
var resultsKey [ ] string
resultKey1 := fmt . Sprintf ( "%s-%s-%s-%s" , policy , rule , kind , resource )
resultKey2 := fmt . Sprintf ( "%s-%s-%s-%s-%s" , policy , rule , namespace , kind , resource )
resultKey3 := fmt . Sprintf ( "%s-%s-%s-%s-%s" , policyNamespace , policy , rule , kind , resource )
resultKey4 := fmt . Sprintf ( "%s-%s-%s-%s-%s-%s" , policyNamespace , policy , rule , namespace , kind , resource )
resultsKey = append ( resultsKey , resultKey1 , resultKey2 , resultKey3 , resultKey4 )
return resultsKey
}
func GetResultKeyAccordingToTestResults ( policyNs , policy , rule , namespace , kind , resource string ) string {
var resultKey string
resultKey = fmt . Sprintf ( "%s-%s-%s-%s" , policy , rule , kind , resource )
if namespace != "" || policyNs != "" {
if policyNs != "" {
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s" , policyNs , policy , rule , kind , resource )
if namespace != "" {
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s-%s" , policyNs , policy , rule , namespace , kind , resource )
}
} else {
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s" , policy , rule , namespace , kind , resource )
}
}
return resultKey
}
func isNamespacedPolicy ( policyNames string ) ( bool , error ) {
return regexp . MatchString ( "^[a-z]*/[a-z]*" , policyNames )
}
func getUserDefinedPolicyNameAndNamespace ( policyName string ) ( string , string ) {
policy := policyName
policy_n_ns := strings . Split ( policyName , "/" )
namespace := policy_n_ns [ 0 ]
policy = policy_n_ns [ 1 ]
return namespace , policy
}
// getAndComparePatchedResource --> Get the patchedResource from the path provided by user
// And compare this patchedResource with engine generated patcheResource.
func getAndComparePatchedResource ( path string , enginePatchedResource unstructured . Unstructured , isGit bool , policyResourcePath string , fs billy . Filesystem ) string {
var status string
patchedResources , err := common . GetPatchedResourceFromPath ( fs , path , isGit , policyResourcePath )
if err != nil {
os . Exit ( 1 )
}
var log logr . Logger
matched , err := generate . ValidateResourceWithPattern ( log , enginePatchedResource . UnstructuredContent ( ) , patchedResources . UnstructuredContent ( ) )
if err != nil {
status = "fail"
}
if matched == "" {
status = "pass"
}
return status
}
func getPolicyResourceFullPaths ( path [ ] string , policyResourcePath string , isGit bool ) [ ] string {
2021-02-18 01:00:41 +05:30
var pol [ ] string
if ! isGit {
for _ , p := range path {
2021-10-01 14:16:33 +05:30
pol = append ( pol , getPolicyResourceFullPath ( p , policyResourcePath , isGit ) )
2021-02-18 01:00:41 +05:30
}
return pol
}
return path
}
2021-10-01 14:16:33 +05:30
func getPolicyResourceFullPath ( path string , policyResourcePath string , isGit bool ) string {
var pol string
if ! isGit {
pol = filepath . Join ( policyResourcePath , path )
return pol
}
return path
}
2021-09-02 23:11:35 +05:30
func applyPoliciesFromPath ( fs billy . Filesystem , policyBytes [ ] byte , valuesFile string , isGit bool , policyResourcePath string , rc * resultCounts ) ( err error ) {
2021-02-01 16:22:41 +05:30
openAPIController , err := openapi . NewOpenAPIController ( )
2021-10-01 14:16:33 +05:30
engineResponses := make ( [ ] * response . EngineResponse , 0 )
2021-02-02 18:43:19 +05:30
var dClient * client . Client
2021-02-01 16:22:41 +05:30
values := & Test { }
2021-02-02 18:43:19 +05:30
var variablesString string
2021-09-02 18:15:22 +05:30
var pvInfos [ ] policyreport . Info
2021-09-02 23:11:35 +05:30
var resultCounts common . ResultCounts
2021-05-03 19:54:19 -04:00
store . SetMock ( true )
2021-02-01 16:22:41 +05:30
if err := json . Unmarshal ( policyBytes , values ) ; err != nil {
return sanitizederror . NewWithError ( "failed to decode yaml" , err )
2021-02-07 20:26:56 -08:00
}
2021-02-18 01:00:41 +05:30
fmt . Printf ( "\nExecuting %s..." , values . Name )
2021-09-28 19:06:16 +05:30
valuesFile = values . Variables
2021-09-20 22:16:57 +05:30
variables , globalValMap , valuesMap , namespaceSelectorMap , err := common . GetVariable ( variablesString , values . Variables , fs , isGit , policyResourcePath )
2021-02-01 16:22:41 +05:30
if err != nil {
if ! sanitizederror . IsErrorSanitized ( err ) {
2021-02-07 20:26:56 -08:00
return sanitizederror . NewWithError ( "failed to decode yaml" , err )
2021-02-01 16:22:41 +05:30
}
2021-02-07 20:26:56 -08:00
return err
2021-02-01 16:22:41 +05:30
}
2021-02-18 01:00:41 +05:30
2021-10-01 14:16:33 +05:30
fullPolicyPath := getPolicyResourceFullPaths ( values . Policies , policyResourcePath , isGit )
fullResourcePath := getPolicyResourceFullPaths ( values . Resources , policyResourcePath , isGit )
2021-02-18 01:00:41 +05:30
2021-10-01 14:16:33 +05:30
for i , result := range values . Results {
var a [ ] string
a = append ( a , result . PatchedResource )
a = getPolicyResourceFullPaths ( a , policyResourcePath , isGit )
values . Results [ i ] . PatchedResource = a [ 0 ]
}
2021-06-22 18:56:44 +05:30
policies , err := common . GetPoliciesFromPaths ( fs , fullPolicyPath , isGit , policyResourcePath )
2021-02-01 16:22:41 +05:30
if err != nil {
fmt . Printf ( "Error: failed to load policies\nCause: %s\n" , err )
os . Exit ( 1 )
}
2021-07-09 18:01:46 -07:00
2021-02-02 18:43:19 +05:30
mutatedPolicies , err := common . MutatePolices ( policies )
2021-02-07 20:26:56 -08:00
if err != nil {
2021-02-01 16:22:41 +05:30
if ! sanitizederror . IsErrorSanitized ( err ) {
2021-02-07 20:26:56 -08:00
return sanitizederror . NewWithError ( "failed to mutate policy" , err )
2021-02-01 16:22:41 +05:30
}
2021-02-07 20:26:56 -08:00
}
2021-07-09 18:01:46 -07:00
2021-09-03 16:41:13 +05:30
err = common . PrintMutatedPolicy ( mutatedPolicies )
if err != nil {
return sanitizederror . NewWithError ( "failed to print mutated policy" , err )
2021-09-02 23:11:35 +05:30
}
2021-09-03 16:41:13 +05:30
2021-06-22 18:56:44 +05:30
resources , err := common . GetResourceAccordingToResourcePath ( fs , fullResourcePath , false , mutatedPolicies , dClient , "" , false , isGit , policyResourcePath )
2021-02-07 20:26:56 -08:00
if err != nil {
fmt . Printf ( "Error: failed to load resources\nCause: %s\n" , err )
os . Exit ( 1 )
}
2021-07-09 18:01:46 -07:00
2021-02-07 20:26:56 -08:00
msgPolicies := "1 policy"
if len ( mutatedPolicies ) > 1 {
msgPolicies = fmt . Sprintf ( "%d policies" , len ( policies ) )
}
2021-07-09 18:01:46 -07:00
2021-02-07 20:26:56 -08:00
msgResources := "1 resource"
if len ( resources ) > 1 {
msgResources = fmt . Sprintf ( "%d resources" , len ( resources ) )
}
2021-07-09 18:01:46 -07:00
2021-02-07 20:26:56 -08:00
if len ( mutatedPolicies ) > 0 && len ( resources ) > 0 {
fmt . Printf ( "\napplying %s to %s... \n" , msgPolicies , msgResources )
}
2021-07-09 18:01:46 -07:00
2021-02-07 20:26:56 -08:00
for _ , policy := range mutatedPolicies {
err := policy2 . Validate ( policy , nil , true , openAPIController )
2021-02-01 16:22:41 +05:30
if err != nil {
2021-07-09 18:01:46 -07:00
log . Log . Error ( err , "skipping invalid policy" , "name" , policy . Name )
2021-02-07 20:26:56 -08:00
continue
2021-02-01 16:22:41 +05:30
}
2021-07-09 18:01:46 -07:00
2021-02-07 20:26:56 -08:00
matches := common . PolicyHasVariables ( * policy )
2021-07-24 00:02:48 +05:30
variable := common . RemoveDuplicateAndObjectVariables ( matches )
2021-09-03 18:43:11 +05:30
if len ( variable ) > 0 {
if len ( variables ) == 0 {
// check policy in variable file
if valuesFile == "" || valuesMap [ policy . Name ] == nil {
fmt . Printf ( "test skipped for policy %v (as required variables are not provided by the users) \n \n" , policy . Name )
}
}
}
2021-09-03 16:41:13 +05:30
kindOnwhichPolicyIsApplied := common . GetKindsFromPolicy ( policy )
2021-09-02 23:11:35 +05:30
2021-02-07 20:26:56 -08:00
for _ , resource := range resources {
2021-09-20 22:16:57 +05:30
thisPolicyResourceValues , err := common . CheckVariableForPolicy ( valuesMap , globalValMap , policy . GetName ( ) , resource . GetName ( ) , resource . GetKind ( ) , variables , kindOnwhichPolicyIsApplied , variable )
2021-09-03 16:41:13 +05:30
if err != nil {
return sanitizederror . NewWithError ( fmt . Sprintf ( "policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag" , policy . Name , resource . GetName ( ) ) , err )
2021-02-01 16:22:41 +05:30
}
2021-02-07 20:26:56 -08:00
2021-10-01 14:16:33 +05:30
ers , info , err := common . ApplyPolicyOnResource ( policy , resource , "" , false , thisPolicyResourceValues , true , namespaceSelectorMap , false , & resultCounts , false )
2021-02-07 20:26:56 -08:00
if err != nil {
return sanitizederror . NewWithError ( fmt . Errorf ( "failed to apply policy %v on resource %v" , policy . Name , resource . GetName ( ) ) . Error ( ) , err )
2021-02-01 16:22:41 +05:30
}
2021-10-01 14:16:33 +05:30
engineResponses = append ( engineResponses , ers )
2021-09-02 18:15:22 +05:30
pvInfos = append ( pvInfos , info )
2021-02-01 16:22:41 +05:30
}
2021-02-07 20:26:56 -08:00
}
2021-10-01 14:16:33 +05:30
resultsMap , testResults := buildPolicyResults ( engineResponses , values . Results , pvInfos , policyResourcePath , fs , isGit )
2021-09-07 20:23:03 +05:30
resultErr := printTestResult ( resultsMap , testResults , rc )
2021-02-07 20:26:56 -08:00
if resultErr != nil {
return sanitizederror . NewWithError ( "Unable to genrate result. Error:" , resultErr )
}
2021-02-01 16:22:41 +05:30
return
}
2021-09-02 23:11:35 +05:30
func printTestResult ( resps map [ string ] report . PolicyReportResult , testResults [ ] TestResults , rc * resultCounts ) error {
2021-02-01 16:22:41 +05:30
printer := tableprinter . New ( os . Stdout )
table := [ ] * Table { }
2021-05-07 19:27:15 -04:00
boldGreen := color . New ( color . FgGreen ) . Add ( color . Bold )
2021-02-01 16:22:41 +05:30
boldRed := color . New ( color . FgRed ) . Add ( color . Bold )
2021-05-03 08:55:04 -04:00
boldYellow := color . New ( color . FgYellow ) . Add ( color . Bold )
2021-02-01 16:22:41 +05:30
boldFgCyan := color . New ( color . FgCyan ) . Add ( color . Bold )
for i , v := range testResults {
res := new ( Table )
2021-02-07 20:26:56 -08:00
res . ID = i + 1
2021-09-02 23:11:35 +05:30
res . Policy = boldFgCyan . Sprintf ( v . Policy )
res . Rule = boldFgCyan . Sprintf ( v . Rule )
2021-10-01 14:16:33 +05:30
namespace := "default"
if v . Namespace != "" {
namespace = v . Namespace
}
res . Resource = boldFgCyan . Sprintf ( namespace ) + "/" + boldFgCyan . Sprintf ( v . Kind ) + "/" + boldFgCyan . Sprintf ( v . Resource )
2021-09-07 20:23:03 +05:30
var ruleNameInResultKey string
2021-09-07 22:27:29 +05:30
if v . AutoGeneratedRule != "" {
ruleNameInResultKey = fmt . Sprintf ( "%s-%s" , v . AutoGeneratedRule , v . Rule )
2021-09-07 20:23:03 +05:30
} else {
ruleNameInResultKey = v . Rule
}
2021-10-01 14:16:33 +05:30
resultKey := fmt . Sprintf ( "%s-%s-%s-%s" , v . Policy , ruleNameInResultKey , v . Kind , v . Resource )
found , _ := isNamespacedPolicy ( v . Policy )
if found || v . Namespace != "" {
if found {
var ns string
ns , v . Policy = getUserDefinedPolicyNameAndNamespace ( v . Policy )
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s" , ns , v . Policy , ruleNameInResultKey , v . Kind , v . Resource )
res . Policy = boldFgCyan . Sprintf ( ns ) + "/" + boldFgCyan . Sprintf ( v . Policy )
res . Resource = boldFgCyan . Sprintf ( namespace ) + "/" + boldFgCyan . Sprintf ( v . Kind ) + "/" + boldFgCyan . Sprintf ( v . Resource )
if v . Namespace != "" {
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s-%s" , ns , v . Policy , ruleNameInResultKey , v . Namespace , v . Kind , v . Resource )
}
} else {
res . Resource = boldFgCyan . Sprintf ( namespace ) + "/" + boldFgCyan . Sprintf ( v . Kind ) + "/" + boldFgCyan . Sprintf ( v . Resource )
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s" , v . Policy , ruleNameInResultKey , v . Namespace , v . Kind , v . Resource )
}
}
2021-05-07 19:27:15 -04:00
var testRes report . PolicyReportResult
if val , ok := resps [ resultKey ] ; ok {
testRes = val
} else {
res . Result = boldYellow . Sprintf ( "Not found" )
2021-09-02 03:21:07 +05:30
rc . Fail ++
2021-05-07 19:27:15 -04:00
table = append ( table , res )
continue
2021-02-01 16:22:41 +05:30
}
2021-08-27 07:52:44 +02:00
if v . Result == "" && v . Status != "" {
v . Result = v . Status
}
2021-08-21 19:35:17 +02:00
if testRes . Result == v . Result {
if testRes . Result == report . StatusSkip {
2021-10-01 14:16:33 +05:30
res . Result = boldYellow . Sprintf ( "Skip" )
2021-09-02 03:21:07 +05:30
rc . Skip ++
2021-05-07 19:27:15 -04:00
} else {
res . Result = boldGreen . Sprintf ( "Pass" )
2021-09-02 03:21:07 +05:30
rc . Pass ++
2021-02-01 16:22:41 +05:30
}
2021-05-07 19:27:15 -04:00
} else {
res . Result = boldRed . Sprintf ( "Fail" )
2021-09-02 03:21:07 +05:30
rc . Fail ++
2021-02-01 16:22:41 +05:30
}
2021-02-03 18:24:50 +05:30
table = append ( table , res )
2021-02-07 20:26:56 -08:00
}
2021-02-01 16:22:41 +05:30
printer . BorderTop , printer . BorderBottom , printer . BorderLeft , printer . BorderRight = true , true , true , true
printer . CenterSeparator = "│"
printer . ColumnSeparator = "│"
printer . RowSeparator = "─"
printer . RowCharLimit = 300
2021-02-07 20:26:56 -08:00
printer . RowLengthTitle = func ( rowsLength int ) bool {
2021-02-01 16:22:41 +05:30
return rowsLength > 10
}
printer . HeaderBgColor = tablewriter . BgBlackColor
printer . HeaderFgColor = tablewriter . FgGreenColor
2021-10-01 14:16:33 +05:30
fmt . Printf ( "\n" )
2021-02-01 16:22:41 +05:30
printer . Print ( table )
return nil
2021-02-07 20:26:56 -08:00
}