1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 07:57:07 +00:00

adding kyverno test command with git support

Signed-off-by: vyankatesh_neualto <vyankatesh@neualto.com>
This commit is contained in:
vyankatesh_neualto 2021-02-02 18:43:19 +05:30
parent 01ac9058d9
commit ce9ab9ef69
4 changed files with 357 additions and 487 deletions

View file

@ -1,10 +1,7 @@
package apply package apply
import ( import (
"bufio"
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@ -14,21 +11,17 @@ import (
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1" v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
pkgCommon "github.com/kyverno/kyverno/pkg/common" pkgCommon "github.com/kyverno/kyverno/pkg/common"
client "github.com/kyverno/kyverno/pkg/dclient" client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/kyverno/common" "github.com/kyverno/kyverno/pkg/kyverno/common"
sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError" sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
"github.com/kyverno/kyverno/pkg/openapi" "github.com/kyverno/kyverno/pkg/openapi"
policy2 "github.com/kyverno/kyverno/pkg/policy" policy2 "github.com/kyverno/kyverno/pkg/policy"
"github.com/kyverno/kyverno/pkg/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
yamlv2 "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
log "sigs.k8s.io/controller-runtime/pkg/log" log "sigs.k8s.io/controller-runtime/pkg/log"
yaml1 "sigs.k8s.io/yaml" yaml1 "sigs.k8s.io/yaml"
"github.com/go-git/go-billy/v5/memfs"
) )
type resultCounts struct { type resultCounts struct {
@ -148,12 +141,13 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
variablesString string, valuesFile string, namespace string, policyPaths []string) (validateEngineResponses []*response.EngineResponse, rc *resultCounts, resources []*unstructured.Unstructured, skippedPolicies []SkippedPolicy, err error) { variablesString string, valuesFile string, namespace string, policyPaths []string) (validateEngineResponses []*response.EngineResponse, rc *resultCounts, resources []*unstructured.Unstructured, skippedPolicies []SkippedPolicy, err error) {
kubernetesConfig := genericclioptions.NewConfigFlags(true) kubernetesConfig := genericclioptions.NewConfigFlags(true)
fs := memfs.New()
if valuesFile != "" && variablesString != "" { if valuesFile != "" && variablesString != "" {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("pass the values either using set flag or values_file flag", err) return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("pass the values either using set flag or values_file flag", err)
} }
variables, valuesMap, err := getVariable(variablesString, valuesFile) variables, valuesMap, err := common.GetVariable(variablesString, valuesFile)
if err != nil { if err != nil {
if !sanitizederror.IsErrorSanitized(err) { if !sanitizederror.IsErrorSanitized(err) {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to decode yaml", err) return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to decode yaml", err)
@ -186,7 +180,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("a stdin pipe can be used for either policies or resources, not both", err) return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("a stdin pipe can be used for either policies or resources, not both", err)
} }
policies, err := getPoliciesFromPaths(policyPaths) policies, err := common.GetPoliciesFromPaths(fs,policyPaths, false)
if err != nil { if err != nil {
fmt.Printf("Error: failed to load policies\nCause: %s\n", err) fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
os.Exit(1) os.Exit(1)
@ -204,14 +198,14 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
return validateEngineResponses, rc, resources, skippedPolicies, err return validateEngineResponses, rc, resources, skippedPolicies, err
} }
mutatedPolicies, err := mutatePolices(policies) mutatedPolicies, err := common.MutatePolices(policies)
if err != nil { if err != nil {
if !sanitizederror.IsErrorSanitized(err) { if !sanitizederror.IsErrorSanitized(err) {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to mutate policy", err) return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to mutate policy", err)
} }
} }
resources, err = getResourceAccordingToResourcePath(resourcePaths, cluster, mutatedPolicies, dClient, namespace, policyReport) resources, err = common.GetResourceAccordingToResourcePath(fs, resourcePaths, cluster, mutatedPolicies, dClient, namespace, policyReport, false)
if err != nil { if err != nil {
fmt.Printf("Error: failed to load resources\nCause: %s\n", err) fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
os.Exit(1) os.Exit(1)
@ -245,7 +239,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
} }
matches := common.PolicyHasVariables(*policy) matches := common.PolicyHasVariables(*policy)
variable := removeDuplicatevariables(matches) variable := common.RemoveDuplicateVariables(matches)
if len(matches) > 0 && variablesString == "" && valuesFile == "" { if len(matches) > 0 && variablesString == "" && valuesFile == "" {
rc.skip++ rc.skip++
@ -274,11 +268,18 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err) return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err)
} }
ers, validateErs, err := applyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, rc, policyReport) ers, validateErs, responseError, rcErs, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport)
if err != nil { if err != nil {
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err) return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
} }
if responseError == true {
rc.fail++
} else {
rc.pass++
}
if rcErs == true {
rc.error++
}
engineResponses = append(engineResponses, ers...) engineResponses = append(engineResponses, ers...)
validateEngineResponses = append(validateEngineResponses, validateErs) validateEngineResponses = append(validateEngineResponses, validateErs)
} }
@ -287,44 +288,6 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
return validateEngineResponses, rc, resources, skippedPolicies, nil return validateEngineResponses, rc, resources, skippedPolicies, nil
} }
// getVariable - get the variables from console/file
func getVariable(variablesString, valuesFile string) (variables map[string]string, valuesMap map[string]map[string]Resource, err error) {
if variablesString != "" {
kvpairs := strings.Split(strings.Trim(variablesString, " "), ",")
for _, kvpair := range kvpairs {
kvs := strings.Split(strings.Trim(kvpair, " "), "=")
variables[strings.Trim(kvs[0], " ")] = strings.Trim(kvs[1], " ")
}
}
if valuesFile != "" {
yamlFile, err := ioutil.ReadFile(valuesFile)
if err != nil {
return variables, valuesMap, sanitizederror.NewWithError("unable to read yaml", err)
}
valuesBytes, err := yaml.ToJSON(yamlFile)
if err != nil {
return variables, valuesMap, sanitizederror.NewWithError("failed to convert json", err)
}
values := &Values{}
if err := json.Unmarshal(valuesBytes, values); err != nil {
return variables, valuesMap, sanitizederror.NewWithError("failed to decode yaml", err)
}
for _, p := range values.Policies {
pmap := make(map[string]Resource)
for _, r := range p.Resources {
pmap[r.Name] = r
}
valuesMap[p.Name] = pmap
}
}
return variables, valuesMap, nil
}
// checkMutateLogPath - checking path for printing mutated resource (-o flag) // checkMutateLogPath - checking path for printing mutated resource (-o flag)
func checkMutateLogPath(mutateLogPath string) (mutateLogPathIsDir bool, err error) { func checkMutateLogPath(mutateLogPath string) (mutateLogPathIsDir bool, err error) {
if mutateLogPath != "" { if mutateLogPath != "" {
@ -347,68 +310,6 @@ func checkMutateLogPath(mutateLogPath string) (mutateLogPathIsDir bool, err erro
return mutateLogPathIsDir, err return mutateLogPathIsDir, err
} }
// getPoliciesFromPaths - get policies according to the resource path
func getPoliciesFromPaths(policyPaths []string) (policies []*v1.ClusterPolicy, err error) {
if len(policyPaths) > 0 && policyPaths[0] == "-" {
if common.IsInputFromPipe() {
policyStr := ""
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
policyStr = policyStr + scanner.Text() + "\n"
}
yamlBytes := []byte(policyStr)
policies, err = utils.GetPolicy(yamlBytes)
if err != nil {
return nil, sanitizederror.NewWithError("failed to extract the resources", err)
}
}
} else {
var errors []error
policies, errors = common.GetPolicies(policyPaths)
if len(policies) == 0 {
if len(errors) > 0 {
return nil, sanitizederror.NewWithErrors("failed to read policies", errors)
}
return nil, sanitizederror.New(fmt.Sprintf("no policies found in paths %v", policyPaths))
}
if len(errors) > 0 && log.Log.V(1).Enabled() {
fmt.Printf("ignoring errors: \n")
for _, e := range errors {
fmt.Printf(" %v \n", e.Error())
}
}
}
return
}
// getResourceAccordingToResourcePath - get resources according to the resource path
func getResourceAccordingToResourcePath(resourcePaths []string, cluster bool, policies []*v1.ClusterPolicy, dClient *client.Client, namespace string, policyReport bool) (resources []*unstructured.Unstructured, err error) {
if len(resourcePaths) > 0 && resourcePaths[0] == "-" {
if common.IsInputFromPipe() {
resourceStr := ""
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
resourceStr = resourceStr + scanner.Text() + "\n"
}
yamlBytes := []byte(resourceStr)
resources, err = common.GetResource(yamlBytes)
if err != nil {
return nil, sanitizederror.NewWithError("failed to extract the resources", err)
}
}
} else if (len(resourcePaths) > 0 && resourcePaths[0] != "-") || len(resourcePaths) < 0 || cluster {
resources, err = common.GetResources(policies, resourcePaths, dClient, cluster, namespace, policyReport)
if err != nil {
return resources, err
}
}
return resources, err
}
// printReportOrViolation - printing policy report/violations // printReportOrViolation - printing policy report/violations
func printReportOrViolation(policyReport bool, validateEngineResponses []*response.EngineResponse, rc *resultCounts, resourcePaths []string, resourcesLen int, skippedPolicies []SkippedPolicy) { func printReportOrViolation(policyReport bool, validateEngineResponses []*response.EngineResponse, rc *resultCounts, resourcePaths []string, resourcesLen int, skippedPolicies []SkippedPolicy) {
if policyReport { if policyReport {
@ -437,164 +338,6 @@ func printReportOrViolation(policyReport bool, validateEngineResponses []*respon
} }
} }
// applyPolicyOnResource - function to apply policy on resource
func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured,
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string,
rc *resultCounts, policyReport bool) ([]*response.EngineResponse, *response.EngineResponse, error) {
responseError := false
engineResponses := make([]*response.EngineResponse, 0)
resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName())
log.Log.V(3).Info("applying policy on resource", "policy", policy.Name, "resource", resPath)
ctx := context.NewContext()
for key, value := range variables {
startString := ""
endString := ""
for _, k := range strings.Split(key, ".") {
startString += fmt.Sprintf(`{"%s":`, k)
endString += `}`
}
midString := fmt.Sprintf(`"%s"`, value)
finalString := startString + midString + endString
var jsonData = []byte(finalString)
ctx.AddJSON(jsonData)
}
mutateResponse := engine.Mutate(&engine.PolicyContext{Policy: *policy, NewResource: *resource, JSONContext: ctx})
engineResponses = append(engineResponses, mutateResponse)
if !mutateResponse.IsSuccessful() {
fmt.Printf("Failed to apply mutate policy %s -> resource %s", policy.Name, resPath)
for i, r := range mutateResponse.PolicyResponse.Rules {
fmt.Printf("\n%d. %s", i+1, r.Message)
}
responseError = true
} else {
if len(mutateResponse.PolicyResponse.Rules) > 0 {
yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object)
if err != nil {
rc.error++
}
if mutateLogPath == "" {
mutatedResource := string(yamlEncodedResource)
if len(strings.TrimSpace(mutatedResource)) > 0 {
fmt.Printf("\nmutate policy %s applied to %s:", policy.Name, resPath)
fmt.Printf("\n" + mutatedResource)
fmt.Printf("\n")
}
} else {
err := printMutatedOutput(mutateLogPath, mutateLogPathIsDir, string(yamlEncodedResource), resource.GetName()+"-mutated")
if err != nil {
return engineResponses, &response.EngineResponse{}, sanitizederror.NewWithError("failed to print mutated result", err)
}
fmt.Printf("\n\nMutation:\nMutation has been applied successfully. Check the files.")
}
}
}
if resource.GetKind() == "Pod" && len(resource.GetOwnerReferences()) > 0 {
if policy.HasAutoGenAnnotation() {
if _, ok := policy.GetAnnotations()[engine.PodControllersAnnotation]; ok {
delete(policy.Annotations, engine.PodControllersAnnotation)
}
}
}
policyCtx := &engine.PolicyContext{Policy: *policy, NewResource: mutateResponse.PatchedResource, JSONContext: ctx}
validateResponse := engine.Validate(policyCtx)
if !policyReport {
if !validateResponse.IsSuccessful() {
fmt.Printf("\npolicy %s -> resource %s failed: \n", policy.Name, resPath)
for i, r := range validateResponse.PolicyResponse.Rules {
if !r.Success {
fmt.Printf("%d. %s: %s \n", i+1, r.Name, r.Message)
}
}
responseError = true
}
}
var policyHasGenerate bool
for _, rule := range policy.Spec.Rules {
if rule.HasGenerate() {
policyHasGenerate = true
}
}
if policyHasGenerate {
generateResponse := engine.Generate(engine.PolicyContext{Policy: *policy, NewResource: *resource})
engineResponses = append(engineResponses, generateResponse)
if len(generateResponse.PolicyResponse.Rules) > 0 {
log.Log.V(3).Info("generate resource is valid", "policy", policy.Name, "resource", resPath)
} else {
fmt.Printf("generate policy %s resource %s is invalid \n", policy.Name, resPath)
for i, r := range generateResponse.PolicyResponse.Rules {
fmt.Printf("%d. %s \b", i+1, r.Message)
}
responseError = true
}
}
if responseError == true {
rc.fail++
} else {
rc.pass++
}
return engineResponses, validateResponse, nil
}
// mutatePolicies - function to apply mutation on policies
func mutatePolices(policies []*v1.ClusterPolicy) ([]*v1.ClusterPolicy, error) {
newPolicies := make([]*v1.ClusterPolicy, 0)
logger := log.Log.WithName("apply")
for _, policy := range policies {
p, err := common.MutatePolicy(policy, logger)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return nil, sanitizederror.NewWithError("failed to mutate policy.", err)
}
return nil, err
}
newPolicies = append(newPolicies, p)
}
return newPolicies, nil
}
// printMutatedOutput - function to print output in provided file or directory
func printMutatedOutput(mutateLogPath string, mutateLogPathIsDir bool, yaml string, fileName string) error {
var f *os.File
var err error
yaml = yaml + ("\n---\n\n")
if !mutateLogPathIsDir {
f, err = os.OpenFile(mutateLogPath, os.O_APPEND|os.O_WRONLY, 0644)
} else {
f, err = os.OpenFile(mutateLogPath+"/"+fileName+".yaml", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
}
if err != nil {
return err
}
if _, err := f.Write([]byte(yaml)); err != nil {
f.Close()
return err
}
if err := f.Close(); err != nil {
return err
}
return nil
}
// createFileOrFolder - creating file or folder according to path provided // createFileOrFolder - creating file or folder according to path provided
func createFileOrFolder(mutateLogPath string, mutateLogPathIsDir bool) error { func createFileOrFolder(mutateLogPath string, mutateLogPathIsDir bool) error {
mutateLogPath = filepath.Clean(mutateLogPath) mutateLogPath = filepath.Clean(mutateLogPath)
@ -643,16 +386,3 @@ func createFileOrFolder(mutateLogPath string, mutateLogPathIsDir bool) error {
return nil return nil
} }
// removeDuplicatevariables - remove duplicate variables
func removeDuplicatevariables(matches [][]string) string {
var variableStr string
for _, m := range matches {
for _, v := range m {
foundVariable := strings.Contains(variableStr, v)
if !foundVariable {
variableStr = variableStr + " " + v
}
}
}
return variableStr
}

View file

@ -9,6 +9,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log"
jsonpatch "github.com/evanphx/json-patch" jsonpatch "github.com/evanphx/json-patch"
@ -20,9 +21,32 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apimachinery/pkg/util/yaml"
yaml_v2 "sigs.k8s.io/yaml" yaml_v2 "sigs.k8s.io/yaml"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response"
yamlv2 "gopkg.in/yaml.v2"
"github.com/go-git/go-billy/v5"
ut "github.com/kyverno/kyverno/pkg/utils"
client "github.com/kyverno/kyverno/pkg/dclient"
) )
// GetPolicies - Extracting the policies from multiple YAML // GetPolicies - Extracting the policies from multiple YAML
type Resource struct {
Name string `json:"name"`
Values map[string]string `json:"values"`
}
type Policy struct {
Name string `json:"name"`
Resources []Resource `json:"resources"`
}
type Values struct {
Policies []Policy `json:"policies"`
}
func GetPolicies(paths []string) (policies []*v1.ClusterPolicy, errors []error) { func GetPolicies(paths []string) (policies []*v1.ClusterPolicy, errors []error) {
for _, path := range paths { for _, path := range paths {
log.Log.V(5).Info("reading policies", "path", path) log.Log.V(5).Info("reading policies", "path", path)
@ -222,3 +246,299 @@ func IsInputFromPipe() bool {
fileInfo, _ := os.Stdin.Stat() fileInfo, _ := os.Stdin.Stat()
return fileInfo.Mode()&os.ModeCharDevice == 0 return fileInfo.Mode()&os.ModeCharDevice == 0
} }
// RemoveDuplicateVariables - remove duplicate variables
func RemoveDuplicateVariables(matches [][]string) string {
var variableStr string
for _, m := range matches {
for _, v := range m {
foundVariable := strings.Contains(variableStr, v)
if !foundVariable {
variableStr = variableStr + " " + v
}
}
}
return variableStr
}
// GetVariable - get the variables from console/file
func GetVariable(variablesString, valuesFile string) (variables map[string]string, valuesMap map[string]map[string]Resource, err error) {
if variablesString != "" {
kvpairs := strings.Split(strings.Trim(variablesString, " "), ",")
for _, kvpair := range kvpairs {
kvs := strings.Split(strings.Trim(kvpair, " "), "=")
variables[strings.Trim(kvs[0], " ")] = strings.Trim(kvs[1], " ")
}
}
if valuesFile != "" {
yamlFile, err := ioutil.ReadFile(valuesFile)
if err != nil {
return variables, valuesMap, sanitizederror.NewWithError("unable to read yaml", err)
}
valuesBytes, err := yaml.ToJSON(yamlFile)
if err != nil {
return variables, valuesMap, sanitizederror.NewWithError("failed to convert json", err)
}
values := &Values{}
if err := json.Unmarshal(valuesBytes, values); err != nil {
return variables, valuesMap, sanitizederror.NewWithError("failed to decode yaml", err)
}
for _, p := range values.Policies {
pmap := make(map[string]Resource)
for _, r := range p.Resources {
pmap[r.Name] = r
}
valuesMap[p.Name] = pmap
}
}
return variables, valuesMap, nil
}
// MutatePolices - function to apply mutation on policies
func MutatePolices(policies []*v1.ClusterPolicy) ([]*v1.ClusterPolicy, error) {
newPolicies := make([]*v1.ClusterPolicy, 0)
logger := log.Log.WithName("apply")
for _, policy := range policies {
p, err := MutatePolicy(policy, logger)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return nil, sanitizederror.NewWithError("failed to mutate policy.", err)
}
return nil, err
}
newPolicies = append(newPolicies, p)
}
return newPolicies, nil
}
// ApplyPolicyOnResource - function to apply policy on resource
func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured,
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, policyReport bool) ([]*response.EngineResponse, *response.EngineResponse, bool, bool, error) {
responseError := false
rcError := false
engineResponses := make([]*response.EngineResponse, 0)
resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName())
log.Log.V(3).Info("applying policy on resource", "policy", policy.Name, "resource", resPath)
ctx := context.NewContext()
for key, value := range variables {
startString := ""
endString := ""
for _, k := range strings.Split(key, ".") {
startString += fmt.Sprintf(`{"%s":`, k)
endString += `}`
}
midString := fmt.Sprintf(`"%s"`, value)
finalString := startString + midString + endString
var jsonData = []byte(finalString)
ctx.AddJSON(jsonData)
}
mutateResponse := engine.Mutate(&engine.PolicyContext{Policy: *policy, NewResource: *resource, JSONContext: ctx})
engineResponses = append(engineResponses, mutateResponse)
if !mutateResponse.IsSuccessful() {
fmt.Printf("Failed to apply mutate policy %s -> resource %s", policy.Name, resPath)
for i, r := range mutateResponse.PolicyResponse.Rules {
fmt.Printf("\n%d. %s", i+1, r.Message)
}
responseError = true
} else {
if len(mutateResponse.PolicyResponse.Rules) > 0 {
yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object)
if err != nil {
rcError = true
}
if mutateLogPath == "" {
mutatedResource := string(yamlEncodedResource)
if len(strings.TrimSpace(mutatedResource)) > 0 {
fmt.Printf("\nmutate policy %s applied to %s:", policy.Name, resPath)
fmt.Printf("\n" + mutatedResource)
fmt.Printf("\n")
}
} else {
err := PrintMutatedOutput(mutateLogPath, mutateLogPathIsDir, string(yamlEncodedResource), resource.GetName()+"-mutated")
if err != nil {
return engineResponses, &response.EngineResponse{}, responseError, rcError, sanitizederror.NewWithError("failed to print mutated result", err)
}
fmt.Printf("\n\nMutation:\nMutation has been applied successfully. Check the files.")
}
}
}
if resource.GetKind() == "Pod" && len(resource.GetOwnerReferences()) > 0 {
if policy.HasAutoGenAnnotation() {
if _, ok := policy.GetAnnotations()[engine.PodControllersAnnotation]; ok {
delete(policy.Annotations, engine.PodControllersAnnotation)
}
}
}
policyCtx := &engine.PolicyContext{Policy: *policy, NewResource: mutateResponse.PatchedResource, JSONContext: ctx}
validateResponse := engine.Validate(policyCtx)
if !policyReport {
if !validateResponse.IsSuccessful() {
fmt.Printf("\npolicy %s -> resource %s failed: \n", policy.Name, resPath)
for i, r := range validateResponse.PolicyResponse.Rules {
if !r.Success {
fmt.Printf("%d. %s: %s \n", i+1, r.Name, r.Message)
}
}
responseError = true
}
}
var policyHasGenerate bool
for _, rule := range policy.Spec.Rules {
if rule.HasGenerate() {
policyHasGenerate = true
}
}
if policyHasGenerate {
generateResponse := engine.Generate(engine.PolicyContext{Policy: *policy, NewResource: *resource})
engineResponses = append(engineResponses, generateResponse)
if len(generateResponse.PolicyResponse.Rules) > 0 {
log.Log.V(3).Info("generate resource is valid", "policy", policy.Name, "resource", resPath)
} else {
fmt.Printf("generate policy %s resource %s is invalid \n", policy.Name, resPath)
for i, r := range generateResponse.PolicyResponse.Rules {
fmt.Printf("%d. %s \b", i+1, r.Message)
}
responseError = true
}
}
return engineResponses, validateResponse, responseError, rcError, nil
}
// PrintMutatedOutput - function to print output in provided file or directory
func PrintMutatedOutput(mutateLogPath string, mutateLogPathIsDir bool, yaml string, fileName string) error {
var f *os.File
var err error
yaml = yaml + ("\n---\n\n")
if !mutateLogPathIsDir {
f, err = os.OpenFile(mutateLogPath, os.O_APPEND|os.O_WRONLY, 0644)
} else {
f, err = os.OpenFile(mutateLogPath+"/"+fileName+".yaml", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
}
if err != nil {
return err
}
if _, err := f.Write([]byte(yaml)); err != nil {
f.Close()
return err
}
if err := f.Close(); err != nil {
return err
}
return nil
}
// GetPoliciesFromPaths - get policies according to the resource path
func GetPoliciesFromPaths(fs billy.Filesystem, dirPath []string, isGit bool) (policies []*v1.ClusterPolicy, err error) {
var errors []error
if isGit {
for _, pp := range dirPath {
filep, err := fs.Open(pp)
bytes, err := ioutil.ReadAll(filep)
if err != nil {
fmt.Printf("Error: failed to read file %s: %v", filep.Name(), err.Error())
}
policyBytes, err := yaml.ToJSON(bytes)
if err != nil {
fmt.Printf("failed to convert to JSON: %v", err)
continue
}
policiesFromFile, errFromFile := ut.GetPolicy(policyBytes)
if errFromFile != nil {
err := fmt.Errorf("failed to process : %v", errFromFile.Error())
errors = append(errors, err)
continue
}
policies = append(policies, policiesFromFile...)
}
} else {
if len(dirPath) > 0 && dirPath[0] == "-" {
if IsInputFromPipe() {
policyStr := ""
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
policyStr = policyStr + scanner.Text() + "\n"
}
yamlBytes := []byte(policyStr)
policies, err = ut.GetPolicy(yamlBytes)
if err != nil {
return nil, sanitizederror.NewWithError("failed to extract the resources", err)
}
}
} else {
var errors []error
policies, errors = GetPolicies(dirPath)
if len(policies) == 0 {
if len(errors) > 0 {
return nil, sanitizederror.NewWithErrors("failed to read file", errors)
}
return nil, sanitizederror.New(fmt.Sprintf("no file found in paths %v", dirPath))
}
if len(errors) > 0 && log.Log.V(1).Enabled() {
fmt.Printf("ignoring errors: \n")
for _, e := range errors {
fmt.Printf(" %v \n", e.Error())
}
}
}
}
return
}
// GetResourceAccordingToResourcePath - get resources according to the resource path
func GetResourceAccordingToResourcePath(fs billy.Filesystem, resourcePaths []string,
cluster bool, policies []*v1.ClusterPolicy, dClient *client.Client, namespace string, policyReport bool, isGit bool) (resources []*unstructured.Unstructured, err error) {
if isGit {
resources, err = GetResourcesWithTest(fs, policies, resourcePaths, isGit)
if err != nil {
return nil, sanitizederror.NewWithError("failed to extract the resources", err)
}
} else {
if len(resourcePaths) > 0 && resourcePaths[0] == "-" {
if IsInputFromPipe() {
resourceStr := ""
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
resourceStr = resourceStr + scanner.Text() + "\n"
}
yamlBytes := []byte(resourceStr)
resources, err = GetResource(yamlBytes)
if err != nil {
return nil, sanitizederror.NewWithError("failed to extract the resources", err)
}
}
} else if (len(resourcePaths) > 0 && resourcePaths[0] != "-") || len(resourcePaths) < 0 || cluster {
resources, err = GetResources(policies, resourcePaths, dClient, cluster, namespace, policyReport)
if err != nil {
return resources, err
}
}
}
return resources, err
}

View file

@ -8,7 +8,6 @@ import (
"net/url" "net/url"
"sort" "sort"
"reflect" "reflect"
"bufio"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
log "sigs.k8s.io/controller-runtime/pkg/log" log "sigs.k8s.io/controller-runtime/pkg/log"
@ -16,23 +15,19 @@ import (
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1" v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/kyverno/common" "github.com/kyverno/kyverno/pkg/kyverno/common"
"k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
policy2 "github.com/kyverno/kyverno/pkg/policy" policy2 "github.com/kyverno/kyverno/pkg/policy"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/openapi" "github.com/kyverno/kyverno/pkg/openapi"
"github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/response"
yamlv2 "gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"github.com/kyverno/kyverno/pkg/policyreport" "github.com/kyverno/kyverno/pkg/policyreport"
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1" report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1"
"github.com/kyverno/kyverno/pkg/engine/utils" "github.com/kyverno/kyverno/pkg/engine/utils"
ut "github.com/kyverno/kyverno/pkg/utils"
"github.com/kataras/tablewriter" "github.com/kataras/tablewriter"
"github.com/lensesio/tableprinter" "github.com/lensesio/tableprinter"
"github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5"
"github.com/fatih/color" "github.com/fatih/color"
client "github.com/kyverno/kyverno/pkg/dclient"
) )
// Command returns version command // Command returns version command
@ -51,9 +46,9 @@ func Command() *cobra.Command {
} }
} }
}() }()
err = testCommandHelper(dirPath, valuesFile) err = testCommandExecute(dirPath, valuesFile)
if err != nil { if err != nil {
log.Log.V(3).Info("Test command fail to apply") log.Log.V(3).Info("a directory is required")
return err return err
} }
return nil return nil
@ -66,7 +61,7 @@ type Test struct {
Policies []string `json:"policies"` Policies []string `json:"policies"`
Resources []string `json:"resources"` Resources []string `json:"resources"`
Variables string `json:"variables"` Variables string `json:"variables"`
TResults []TestResults `json:"results"` Results []TestResults `json:"results"`
} }
type SkippedPolicy struct { type SkippedPolicy struct {
@ -107,14 +102,14 @@ type Values struct {
Policies []Policy `json:"policies"` Policies []Policy `json:"policies"`
} }
func testCommandHelper(dirPath []string, valuesFile string) (err error) { func testCommandExecute(dirPath []string, valuesFile string) (err error) {
var errors []error var errors []error
fs := memfs.New() fs := memfs.New()
if len(dirPath) == 0 { if len(dirPath) == 0 {
return sanitizederror.NewWithError(fmt.Sprintf("require test yamls"), err) return sanitizederror.NewWithError(fmt.Sprintf("a directory is required"), err)
} }
if strings.Contains(string(dirPath[0]), "https://github.com/") { if strings.Contains(string(dirPath[0]), "https://") {
gitUrl, err := url.Parse(dirPath[0]) gitUrl, err := url.Parse(dirPath[0])
if err != nil { if err != nil {
return sanitizederror.NewWithError("failed to parse URL", err) return sanitizederror.NewWithError("failed to parse URL", err)
@ -187,147 +182,6 @@ func testCommandHelper(dirPath []string, valuesFile string) (err error) {
} }
return nil return nil
} }
func getPoliciesFromPaths(fs billy.Filesystem, dirPath []string, isGit bool) (policies []*v1.ClusterPolicy, err error) {
var errors []error
if isGit {
for _, pp := range dirPath {
filep, err := fs.Open(pp)
bytes, err := ioutil.ReadAll(filep)
if err != nil {
fmt.Printf("Error: failed to read file %s: %v", filep.Name(), err.Error())
}
policyBytes, err := yaml.ToJSON(bytes)
if err != nil {
fmt.Printf("failed to convert to JSON: %v", err)
continue
}
policiesFromFile, errFromFile := ut.GetPolicy(policyBytes)
if errFromFile != nil {
err := fmt.Errorf("failed to process : %v", errFromFile.Error())
errors = append(errors, err)
continue
}
policies = append(policies, policiesFromFile...)
}
} else {
if len(dirPath) > 0 && dirPath[0] == "-" {
if common.IsInputFromPipe() {
policyStr := ""
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
policyStr = policyStr + scanner.Text() + "\n"
}
yamlBytes := []byte(policyStr)
policies, err = ut.GetPolicy(yamlBytes)
if err != nil {
return nil, sanitizederror.NewWithError("failed to extract the resources", err)
}
}
} else {
var errors []error
policies, errors = common.GetPolicies(dirPath)
if len(policies) == 0 {
if len(errors) > 0 {
return nil, sanitizederror.NewWithErrors("failed to read file", errors)
}
return nil, sanitizederror.New(fmt.Sprintf("no file found in paths %v", dirPath))
}
if len(errors) > 0 && log.Log.V(1).Enabled() {
fmt.Printf("ignoring errors: \n")
for _, e := range errors {
fmt.Printf(" %v \n", e.Error())
}
}
}
}
return
}
func mutatePolices(policies []*v1.ClusterPolicy) ([]*v1.ClusterPolicy, error) {
newPolicies := make([]*v1.ClusterPolicy, 0)
logger := log.Log.WithName("apply")
for _, policy := range policies {
p, err := common.MutatePolicy(policy, logger)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return nil, sanitizederror.NewWithError("failed to mutate policy.", err)
}
return nil, err
}
newPolicies = append(newPolicies, p)
}
return newPolicies, nil
}
func getResourceAccordingToResourcePath(fs billy.Filesystem,resourcePaths []string, policies []*v1.ClusterPolicy, isGit bool) (resources []*unstructured.Unstructured, err error) {
resources, err = common.GetResourcesWithTest(fs, policies, resourcePaths, isGit)
if err != nil {
return resources, err
}
return resources, err
}
func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured) ([]*response.EngineResponse, *response.EngineResponse, error) {
engineResponses := make([]*response.EngineResponse, 0)
resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName())
log.Log.V(3).Info("applying policy on resource", "policy", policy.Name, "resource", resPath)
ctx := context.NewContext()
mutateResponse := engine.Mutate(&engine.PolicyContext{Policy: *policy, NewResource: *resource, JSONContext: ctx})
engineResponses = append(engineResponses, mutateResponse)
if !mutateResponse.IsSuccessful() {
fmt.Printf("Failed to apply mutate policy %s -> resource %s", policy.Name, resPath)
for i, r := range mutateResponse.PolicyResponse.Rules {
fmt.Printf("\n%d. %s", i+1, r.Message)
}
} else {
if len(mutateResponse.PolicyResponse.Rules) > 0 {
yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object)
if err != nil {
log.Log.V(3).Info(fmt.Sprintf("yaml encoded resource not valid"), "error", err)
}
mutatedResource := string(yamlEncodedResource)
if len(strings.TrimSpace(mutatedResource)) > 0 {
fmt.Printf("\nmutate policy %s applied to %s:", policy.Name, resPath)
fmt.Printf("\n" + mutatedResource)
fmt.Printf("\n")
}
}
}
if resource.GetKind() == "Pod" && len(resource.GetOwnerReferences()) > 0 {
if policy.HasAutoGenAnnotation() {
if _, ok := policy.GetAnnotations()[engine.PodControllersAnnotation]; ok {
delete(policy.Annotations, engine.PodControllersAnnotation)
}
}
}
policyCtx := &engine.PolicyContext{Policy: *policy, NewResource: mutateResponse.PatchedResource, JSONContext: ctx}
validateResponse := engine.Validate(policyCtx)
var policyHasGenerate bool
for _, rule := range policy.Spec.Rules {
if rule.HasGenerate() {
policyHasGenerate = true
}
}
if policyHasGenerate {
generateResponse := engine.Generate(engine.PolicyContext{Policy: *policy, NewResource: *resource})
engineResponses = append(engineResponses, generateResponse)
if len(generateResponse.PolicyResponse.Rules) > 0 {
log.Log.V(3).Info("generate resource is valid", "policy", policy.Name, "resource", resPath)
} else {
fmt.Printf("generate policy %s resource %s is invalid \n", policy.Name, resPath)
for i, r := range generateResponse.PolicyResponse.Rules {
fmt.Printf("%d. %s \b", i+1, r.Message)
}
}
}
return engineResponses, validateResponse, nil
}
func buildPolicyResults(resps []*response.EngineResponse) map[string][]interface{} { func buildPolicyResults(resps []*response.EngineResponse) map[string][]interface{} {
results := make(map[string][]interface{}) results := make(map[string][]interface{})
@ -354,73 +208,39 @@ func buildPolicyResults(resps []*response.EngineResponse) map[string][]interface
} }
return results return results
} }
func getVariable( valuesFile string) ( valuesMap map[string]map[string]Resource, err error) {
if valuesFile != "" {
yamlFile, err := ioutil.ReadFile(valuesFile)
if err != nil {
return valuesMap, sanitizederror.NewWithError("unable to read yaml", err)
}
valuesBytes, err := yaml.ToJSON(yamlFile)
if err != nil {
return valuesMap, sanitizederror.NewWithError("failed to convert json", err)
}
values := &Values{}
if err := json.Unmarshal(valuesBytes, values); err != nil {
return valuesMap, sanitizederror.NewWithError("failed to decode yaml", err)
}
for _, p := range values.Policies {
pmap := make(map[string]Resource)
for _, r := range p.Resources {
pmap[r.Name] = r
}
valuesMap[p.Name] = pmap
}
}
return valuesMap, nil
}
func removeDuplicatevariables(matches [][]string) string {
var variableStr string
for _, m := range matches {
for _, v := range m {
foundVariable := strings.Contains(variableStr, v)
if !foundVariable {
variableStr = variableStr + " " + v
}
}
}
return variableStr
}
func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile string, isGit bool) (err error) { func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile string, isGit bool) (err error) {
openAPIController, err := openapi.NewOpenAPIController() openAPIController, err := openapi.NewOpenAPIController()
engineResponses := make([]*response.EngineResponse, 0) engineResponses := make([]*response.EngineResponse, 0)
validateEngineResponses := make([]*response.EngineResponse, 0) validateEngineResponses := make([]*response.EngineResponse, 0)
skippedPolicies := make([]SkippedPolicy, 0) skippedPolicies := make([]SkippedPolicy, 0)
var dClient *client.Client
values := &Test{} values := &Test{}
var variablesString string
if err := json.Unmarshal(policyBytes, values); err != nil { if err := json.Unmarshal(policyBytes, values); err != nil {
return sanitizederror.NewWithError("failed to decode yaml", err) return sanitizederror.NewWithError("failed to decode yaml", err)
} }
valuesMap, err := getVariable(values.Variables) _, valuesMap, err := common.GetVariable(variablesString, values.Variables)
if err != nil { if err != nil {
if !sanitizederror.IsErrorSanitized(err) { if !sanitizederror.IsErrorSanitized(err) {
return sanitizederror.NewWithError("failed to decode yaml", err) return sanitizederror.NewWithError("failed to decode yaml", err)
} }
return err return err
} }
policies, err := getPoliciesFromPaths(fs, values.Policies, isGit) policies, err := common.GetPoliciesFromPaths(fs, values.Policies, isGit)
if err != nil { if err != nil {
fmt.Printf("Error: failed to load policies\nCause: %s\n", err) fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
os.Exit(1) os.Exit(1)
} }
mutatedPolicies, err := mutatePolices(policies) mutatedPolicies, err := common.MutatePolices(policies)
if err != nil { if err != nil {
if !sanitizederror.IsErrorSanitized(err) { if !sanitizederror.IsErrorSanitized(err) {
return sanitizederror.NewWithError("failed to mutate policy", err) return sanitizederror.NewWithError("failed to mutate policy", err)
} }
} }
resources, err := getResourceAccordingToResourcePath(fs,values.Resources, mutatedPolicies, isGit) resources, err := common.GetResourceAccordingToResourcePath(fs, values.Resources, false, mutatedPolicies, dClient, "", false, isGit)
if err != nil { if err != nil {
fmt.Printf("Error: failed to load resources\nCause: %s\n", err) fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
os.Exit(1) os.Exit(1)
@ -447,7 +267,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
continue continue
} }
matches := common.PolicyHasVariables(*policy) matches := common.PolicyHasVariables(*policy)
variable := removeDuplicatevariables(matches) variable := common.RemoveDuplicateVariables(matches)
if len(matches) > 0 && valuesFile == "" { if len(matches) > 0 && valuesFile == "" {
skipPolicy := SkippedPolicy{ skipPolicy := SkippedPolicy{
Name: policy.GetName(), Name: policy.GetName(),
@ -467,8 +287,8 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
if len(common.PolicyHasVariables(*policy)) > 0 && len(thisPolicyResourceValues) == 0 { if len(common.PolicyHasVariables(*policy)) > 0 && len(thisPolicyResourceValues) == 0 {
return sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err) return sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err)
} }
ers, validateErs, err := applyPolicyOnResource(policy, resource) ers, validateErs, _, _, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true)
if err != nil { if err != nil {
return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err) return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
} }
@ -477,7 +297,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
} }
} }
resultsMap := buildPolicyResults(validateEngineResponses) resultsMap := buildPolicyResults(validateEngineResponses)
resuleErr := printTestResult(resultsMap, values.TResults) resuleErr := printTestResult(resultsMap, values.Results)
if resuleErr != nil { if resuleErr != nil {
return sanitizederror.NewWithError("Unable to genrate result. error:", resuleErr) return sanitizederror.NewWithError("Unable to genrate result. error:", resuleErr)
os.Exit(1) os.Exit(1)
@ -485,6 +305,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
return return
} }
func printTestResult(resps map[string][]interface {}, testResults []TestResults) (error){ func printTestResult(resps map[string][]interface {}, testResults []TestResults) (error){
printer := tableprinter.New(os.Stdout) printer := tableprinter.New(os.Stdout)
table := []*Table{} table := []*Table{}
@ -502,6 +323,7 @@ func printTestResult(resps map[string][]interface {}, testResults []TestResults)
} }
var c []ReportResult var c []ReportResult
json.Unmarshal(valuesBytes, &c) json.Unmarshal(valuesBytes, &c)
res.Result = boldRed.Sprintf("Fail")
if len(c) != 0 { if len(c) != 0 {
var resource1 TestResults var resource1 TestResults
for _, c1 := range c { for _, c1 := range c {
@ -510,11 +332,9 @@ func printTestResult(resps map[string][]interface {}, testResults []TestResults)
resource1.Rule = c1.Rule resource1.Rule = c1.Rule
resource1.Status = c1.Status resource1.Status = c1.Status
resource1.Resource = c1.Resources[0].Name resource1.Resource = c1.Resources[0].Name
if v != resource1 {
res.Result = boldRed.Sprintf("Fail") if v == resource1 {
} else {
res.Result = "Pass" res.Result = "Pass"
continue
} }
} }
} }