2020-05-29 15:32:29 +05:30
package common
import (
2020-09-01 09:38:49 -07:00
"bufio"
"bytes"
2020-05-29 15:32:29 +05:30
"encoding/json"
"fmt"
2020-09-01 09:38:49 -07:00
"io"
2020-05-29 15:32:29 +05:30
"io/ioutil"
2021-02-08 23:38:06 +05:30
"net/http"
2020-10-19 12:36:55 -07:00
"os"
"path/filepath"
2021-02-02 18:43:19 +05:30
"strings"
2020-08-31 19:32:00 +05:30
2021-02-25 15:21:55 -08:00
jsonpatch "github.com/evanphx/json-patch/v5"
2021-02-07 20:26:56 -08:00
"github.com/go-git/go-billy/v5"
2020-07-29 21:41:58 +05:30
"github.com/go-logr/logr"
2020-10-07 11:12:31 -07:00
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
2021-09-02 00:02:55 +05:30
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha2"
2021-04-29 22:39:44 +05:30
pkgcommon "github.com/kyverno/kyverno/pkg/common"
2021-02-07 20:26:56 -08:00
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"
2021-07-02 16:47:40 +03:00
"github.com/kyverno/kyverno/pkg/engine/variables"
2020-11-17 13:07:30 -08:00
sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
2021-04-29 22:39:44 +05:30
"github.com/kyverno/kyverno/pkg/kyverno/store"
2020-10-07 11:12:31 -07:00
"github.com/kyverno/kyverno/pkg/policymutation"
2021-09-02 00:02:55 +05:30
"github.com/kyverno/kyverno/pkg/policyreport"
2020-10-07 11:12:31 -07:00
"github.com/kyverno/kyverno/pkg/utils"
2021-02-07 20:26:56 -08:00
ut "github.com/kyverno/kyverno/pkg/utils"
yamlv2 "gopkg.in/yaml.v2"
2020-11-17 13:07:30 -08:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
2021-02-07 20:26:56 -08:00
"sigs.k8s.io/controller-runtime/pkg/log"
2020-11-17 13:07:30 -08:00
yaml_v2 "sigs.k8s.io/yaml"
2020-05-29 15:32:29 +05:30
)
2021-08-31 21:18:54 +05:30
type ResultCounts struct {
Pass int
Fail int
Warn int
Error int
Skip int
}
2021-02-02 18:43:19 +05:30
type Policy struct {
Name string ` json:"name" `
Resources [ ] Resource ` json:"resources" `
2021-04-29 22:39:44 +05:30
Rules [ ] Rule ` json:"rules" `
}
type Rule struct {
Name string ` json:"name" `
Values map [ string ] string ` json:"values" `
2021-02-02 18:43:19 +05:30
}
type Values struct {
2021-03-10 02:15:45 +05:30
Policies [ ] Policy ` json:"policies" `
NamespaceSelectors [ ] NamespaceSelector ` json:"namespaceSelector" `
}
2021-04-29 22:39:44 +05:30
type Resource struct {
Name string ` json:"name" `
Values map [ string ] string ` json:"values" `
}
2021-03-10 02:15:45 +05:30
type NamespaceSelector struct {
Name string ` json:"name" `
Labels map [ string ] string ` json:"labels" `
2021-02-02 18:43:19 +05:30
}
2021-08-31 21:18:54 +05:30
// GetPolicies - Extracting the policies from multiple YAML
2020-12-07 11:26:04 -08:00
func GetPolicies ( paths [ ] string ) ( policies [ ] * v1 . ClusterPolicy , errors [ ] error ) {
2020-11-03 01:25:32 +05:30
for _ , path := range paths {
2020-12-07 11:26:04 -08:00
log . Log . V ( 5 ) . Info ( "reading policies" , "path" , path )
2021-02-08 23:38:06 +05:30
var (
fileDesc os . FileInfo
err error
)
2021-06-22 18:48:23 +05:30
isHttpPath := IsHttpRegex . MatchString ( path )
2021-02-08 23:38:06 +05:30
// path clean and retrieving file info can be possible if it's not an HTTP URL
if ! isHttpPath {
path = filepath . Clean ( path )
fileDesc , err = os . Stat ( path )
if err != nil {
err := fmt . Errorf ( "failed to process %v: %v" , path , err . Error ( ) )
errors = append ( errors , err )
continue
}
2020-10-21 20:05:05 +05:30
}
2020-12-07 11:26:04 -08:00
2021-02-08 23:38:06 +05:30
// apply file from a directory is possible only if the path is not HTTP URL
if ! isHttpPath && fileDesc . IsDir ( ) {
2020-11-03 01:25:32 +05:30
files , err := ioutil . ReadDir ( path )
2020-10-07 06:20:53 +05:30
if err != nil {
2021-02-08 23:38:06 +05:30
err := fmt . Errorf ( "failed to process %v: %v" , path , err . Error ( ) )
errors = append ( errors , err )
2020-12-07 11:26:04 -08:00
continue
2020-10-07 06:20:53 +05:30
}
2020-12-07 11:26:04 -08:00
2020-11-03 01:25:32 +05:30
listOfFiles := make ( [ ] string , 0 )
for _ , file := range files {
2020-12-07 11:26:04 -08:00
ext := filepath . Ext ( file . Name ( ) )
if ext == "" || ext == ".yaml" || ext == ".yml" {
listOfFiles = append ( listOfFiles , filepath . Join ( path , file . Name ( ) ) )
}
2020-11-01 23:18:58 +05:30
}
2020-12-07 11:26:04 -08:00
policiesFromDir , errorsFromDir := GetPolicies ( listOfFiles )
errors = append ( errors , errorsFromDir ... )
2020-11-03 01:25:32 +05:30
policies = append ( policies , policiesFromDir ... )
2020-12-07 11:26:04 -08:00
2020-11-03 01:25:32 +05:30
} else {
2021-02-08 23:38:06 +05:30
var fileBytes [ ] byte
if isHttpPath {
resp , err := http . Get ( path )
if err != nil {
err := fmt . Errorf ( "failed to process %v: %v" , path , err . Error ( ) )
errors = append ( errors , err )
continue
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
err := fmt . Errorf ( "failed to process %v: %v" , path , err . Error ( ) )
errors = append ( errors , err )
continue
}
fileBytes , err = ioutil . ReadAll ( resp . Body )
if err != nil {
err := fmt . Errorf ( "failed to process %v: %v" , path , err . Error ( ) )
errors = append ( errors , err )
continue
}
} else {
fileBytes , err = ioutil . ReadFile ( path )
if err != nil {
err := fmt . Errorf ( "failed to process %v: %v" , path , err . Error ( ) )
errors = append ( errors , err )
continue
}
2020-11-03 01:25:32 +05:30
}
2020-12-07 11:26:04 -08:00
policiesFromFile , errFromFile := utils . GetPolicy ( fileBytes )
if errFromFile != nil {
err := fmt . Errorf ( "failed to process %s: %v" , path , errFromFile . Error ( ) )
errors = append ( errors , err )
continue
2020-11-01 23:18:58 +05:30
}
2020-10-21 20:05:05 +05:30
2020-12-07 11:26:04 -08:00
policies = append ( policies , policiesFromFile ... )
2020-11-01 23:18:58 +05:30
}
2020-10-21 20:05:05 +05:30
}
2020-12-07 11:26:04 -08:00
log . Log . V ( 3 ) . Info ( "read policies" , "policies" , len ( policies ) , "errors" , len ( errors ) )
return policies , errors
2020-05-29 15:32:29 +05:30
}
2020-07-07 16:20:55 +05:30
2020-08-24 03:41:03 +05:30
// PolicyHasVariables - check for variables in the policy
2020-11-19 15:03:15 +05:30
func PolicyHasVariables ( policy v1 . ClusterPolicy ) [ ] [ ] string {
2020-08-24 03:41:03 +05:30
policyRaw , _ := json . Marshal ( policy )
2020-11-17 13:07:30 -08:00
matches := RegexVariables . FindAllStringSubmatch ( string ( policyRaw ) , - 1 )
2020-11-19 15:03:15 +05:30
return matches
2020-08-24 03:41:03 +05:30
}
2021-07-02 16:47:40 +03:00
// for now forbidden sections are match, exclude and
func ruleForbiddenSectionsHaveVariables ( rule v1 . Rule ) error {
var err error
err = JSONPatchPathHasVariables ( rule . Mutation . PatchesJSON6902 )
if err != nil {
return fmt . Errorf ( "Rule \"%s\" should not have variables in patchesJSON6902 path section" , rule . Name )
}
err = objectHasVariables ( rule . ExcludeResources )
if err != nil {
return fmt . Errorf ( "Rule \"%s\" should not have variables in exclude section" , rule . Name )
}
err = objectHasVariables ( rule . MatchResources )
if err != nil {
return fmt . Errorf ( "Rule \"%s\" should not have variables in match section" , rule . Name )
}
return nil
}
func JSONPatchPathHasVariables ( patch string ) error {
jsonPatch , err := yaml . ToJSON ( [ ] byte ( patch ) )
if err != nil {
return err
}
decodedPatch , err := jsonpatch . DecodePatch ( jsonPatch )
if err != nil {
return err
}
for _ , operation := range decodedPatch {
path , err := operation . Path ( )
if err != nil {
return err
}
vars := variables . RegexVariables . FindAllString ( path , - 1 )
if len ( vars ) > 0 {
return fmt . Errorf ( "Operation \"%s\" has forbidden variables" , operation . Kind ( ) )
}
}
return nil
}
func objectHasVariables ( object interface { } ) error {
var err error
objectJSON , err := json . Marshal ( object )
if err != nil {
return err
}
if len ( RegexVariables . FindAllStringSubmatch ( string ( objectJSON ) , - 1 ) ) > 0 {
return fmt . Errorf ( "Object has forbidden variables" )
}
return nil
}
2020-10-19 12:36:55 -07:00
// PolicyHasNonAllowedVariables - checks for unexpected variables in the policy
2021-07-02 16:47:40 +03:00
func PolicyHasNonAllowedVariables ( policy v1 . ClusterPolicy ) error {
for _ , rule := range policy . Spec . Rules {
var err error
2020-08-24 03:41:03 +05:30
2021-07-02 16:47:40 +03:00
ruleJSON , err := json . Marshal ( rule )
if err != nil {
return err
}
2020-08-24 03:41:03 +05:30
2021-07-02 16:47:40 +03:00
err = ruleForbiddenSectionsHaveVariables ( rule )
if err != nil {
return err
2020-09-23 02:41:49 +05:30
}
2020-10-19 12:36:55 -07:00
2021-07-02 16:47:40 +03:00
matchesAll := RegexVariables . FindAllStringSubmatch ( string ( ruleJSON ) , - 1 )
matchesAllowed := AllowedVariables . FindAllStringSubmatch ( string ( ruleJSON ) , - 1 )
if ( len ( matchesAll ) > len ( matchesAllowed ) ) && len ( rule . Context ) == 0 {
return fmt . Errorf ( "Rule \"%s\" has forbidden variables. Allowed variables are: {{request.*}}, {{serviceAccountName}}, {{serviceAccountNamespace}}, {{@}} and ones defined by the context" , rule . Name )
}
2020-08-24 03:41:03 +05:30
}
2020-10-19 12:36:55 -07:00
2021-07-02 16:47:40 +03:00
return nil
2020-08-24 03:41:03 +05:30
}
2020-07-29 21:41:58 +05:30
// MutatePolicy - applies mutation to a policy
func MutatePolicy ( policy * v1 . ClusterPolicy , logger logr . Logger ) ( * v1 . ClusterPolicy , error ) {
patches , _ := policymutation . GenerateJSONPatchesForDefaults ( policy , logger )
2020-08-19 15:11:21 -07:00
if len ( patches ) == 0 {
return policy , nil
}
2020-07-29 21:41:58 +05:30
type jsonPatch struct {
Path string ` json:"path" `
Op string ` json:"op" `
Value interface { } ` json:"value" `
}
var jsonPatches [ ] jsonPatch
err := json . Unmarshal ( patches , & jsonPatches )
if err != nil {
2020-11-17 13:07:30 -08:00
return nil , sanitizederror . NewWithError ( fmt . Sprintf ( "failed to unmarshal patches for %s policy" , policy . Name ) , err )
2020-07-29 21:41:58 +05:30
}
2020-10-21 20:05:05 +05:30
2020-07-29 21:41:58 +05:30
patch , err := jsonpatch . DecodePatch ( patches )
if err != nil {
2020-11-17 13:07:30 -08:00
return nil , sanitizederror . NewWithError ( fmt . Sprintf ( "failed to decode patch for %s policy" , policy . Name ) , err )
2020-07-29 21:41:58 +05:30
}
policyBytes , _ := json . Marshal ( policy )
if err != nil {
2020-11-17 13:07:30 -08:00
return nil , sanitizederror . NewWithError ( fmt . Sprintf ( "failed to marshal %s policy" , policy . Name ) , err )
2020-07-29 21:41:58 +05:30
}
modifiedPolicy , err := patch . Apply ( policyBytes )
if err != nil {
2020-11-17 13:07:30 -08:00
return nil , sanitizederror . NewWithError ( fmt . Sprintf ( "failed to apply %s policy" , policy . Name ) , err )
2020-07-29 21:41:58 +05:30
}
var p v1 . ClusterPolicy
err = json . Unmarshal ( modifiedPolicy , & p )
if err != nil {
2020-11-17 13:07:30 -08:00
return nil , sanitizederror . NewWithError ( fmt . Sprintf ( "failed to unmarshal %s policy" , policy . Name ) , err )
2020-07-29 21:41:58 +05:30
}
return & p , nil
}
2020-08-19 10:21:32 +05:30
2020-08-31 17:33:01 +05:30
// GetCRDs - Extracting the crds from multiple YAML
func GetCRDs ( paths [ ] string ) ( unstructuredCrds [ ] * unstructured . Unstructured , err error ) {
2020-08-31 18:43:58 +05:30
unstructuredCrds = make ( [ ] * unstructured . Unstructured , 0 )
2020-08-31 17:33:01 +05:30
for _ , path := range paths {
path = filepath . Clean ( path )
fileDesc , err := os . Stat ( path )
if err != nil {
return nil , err
}
if fileDesc . IsDir ( ) {
files , err := ioutil . ReadDir ( path )
if err != nil {
2020-11-17 13:07:30 -08:00
return nil , sanitizederror . NewWithError ( fmt . Sprintf ( "failed to parse %v" , path ) , err )
2020-08-31 17:33:01 +05:30
}
listOfFiles := make ( [ ] string , 0 )
for _ , file := range files {
listOfFiles = append ( listOfFiles , filepath . Join ( path , file . Name ( ) ) )
}
policiesFromDir , err := GetCRDs ( listOfFiles )
if err != nil {
2020-11-17 13:07:30 -08:00
return nil , sanitizederror . NewWithError ( fmt . Sprintf ( "failed to extract crds from %v" , listOfFiles ) , err )
2020-08-31 17:33:01 +05:30
}
unstructuredCrds = append ( unstructuredCrds , policiesFromDir ... )
} else {
getCRDs , err := GetCRD ( path )
if err != nil {
2020-10-10 03:49:28 +05:30
fmt . Printf ( "\nError: failed to extract crds from %s. \nCause: %s\n" , path , err )
2020-08-31 19:32:00 +05:30
os . Exit ( 2 )
2020-08-31 17:33:01 +05:30
}
unstructuredCrds = append ( unstructuredCrds , getCRDs ... )
}
}
2020-08-31 18:43:58 +05:30
return unstructuredCrds , nil
2020-08-31 17:33:01 +05:30
}
// GetCRD - Extracts crds from a YAML
func GetCRD ( path string ) ( unstructuredCrds [ ] * unstructured . Unstructured , err error ) {
2020-08-19 10:21:32 +05:30
path = filepath . Clean ( path )
2020-08-31 18:43:58 +05:30
unstructuredCrds = make ( [ ] * unstructured . Unstructured , 0 )
2020-08-31 17:33:01 +05:30
yamlbytes , err := ioutil . ReadFile ( path )
2020-08-19 10:21:32 +05:30
if err != nil {
2020-08-30 20:43:05 +05:30
return nil , err
2020-08-19 10:21:32 +05:30
}
2020-08-31 17:33:01 +05:30
buf := bytes . NewBuffer ( yamlbytes )
reader := yaml . NewYAMLReader ( bufio . NewReader ( buf ) )
2020-08-19 10:21:32 +05:30
2020-08-31 17:33:01 +05:30
for {
// Read one YAML document at a time, until io.EOF is returned
b , err := reader . Read ( )
if err == io . EOF || len ( b ) == 0 {
break
} else if err != nil {
2020-10-10 03:49:28 +05:30
fmt . Printf ( "\nError: unable to read crd from %s. Cause: %s\n" , path , err )
os . Exit ( 2 )
2020-08-31 17:33:01 +05:30
}
2020-08-31 18:43:58 +05:30
var u unstructured . Unstructured
2020-08-31 17:33:01 +05:30
err = yaml_v2 . Unmarshal ( b , & u )
if err != nil {
return nil , err
}
unstructuredCrds = append ( unstructuredCrds , & u )
2020-08-19 10:21:32 +05:30
}
2020-08-31 18:43:58 +05:30
2020-08-31 17:33:01 +05:30
return unstructuredCrds , nil
2020-08-19 10:21:32 +05:30
}
2020-10-07 06:20:53 +05:30
// IsInputFromPipe - check if input is passed using pipe
func IsInputFromPipe ( ) bool {
fileInfo , _ := os . Stdin . Stat ( )
return fileInfo . Mode ( ) & os . ModeCharDevice == 0
}
2021-02-02 18:43:19 +05:30
2021-07-24 00:02:48 +05:30
// RemoveDuplicateAndObjectVariables - remove duplicate variables
func RemoveDuplicateAndObjectVariables ( matches [ ] [ ] string ) string {
2021-02-02 18:43:19 +05:30
var variableStr string
for _ , m := range matches {
for _ , v := range m {
foundVariable := strings . Contains ( variableStr , v )
if ! foundVariable {
2021-07-24 00:02:48 +05:30
if ! strings . Contains ( v , "request.object" ) {
variableStr = variableStr + " " + v
}
2021-02-02 18:43:19 +05:30
}
}
}
return variableStr
}
2021-07-28 10:52:32 +05:30
func GetVariable ( variablesString , valuesFile string , fs billy . Filesystem , isGit bool , policyResourcePath string ) ( map [ string ] string , map [ string ] map [ string ] Resource , map [ string ] map [ string ] string , error ) {
2021-04-29 22:39:44 +05:30
valuesMapResource := make ( map [ string ] map [ string ] Resource )
valuesMapRule := make ( map [ string ] map [ string ] Rule )
2021-03-10 02:15:45 +05:30
namespaceSelectorMap := make ( map [ string ] map [ string ] string )
2021-02-03 18:24:50 +05:30
variables := make ( map [ string ] string )
2021-08-01 16:27:47 +05:30
reqObjVars := ""
2021-07-28 10:52:32 +05:30
2021-02-18 01:00:41 +05:30
var yamlFile [ ] byte
var err error
2021-02-02 18:43:19 +05:30
if variablesString != "" {
kvpairs := strings . Split ( strings . Trim ( variablesString , " " ) , "," )
for _ , kvpair := range kvpairs {
kvs := strings . Split ( strings . Trim ( kvpair , " " ) , "=" )
2021-07-24 00:02:48 +05:30
if strings . Contains ( kvs [ 0 ] , "request.object" ) {
2021-08-01 16:27:47 +05:30
if ! strings . Contains ( reqObjVars , kvs [ 0 ] ) {
reqObjVars = reqObjVars + "," + kvs [ 0 ]
}
continue
2021-07-24 00:02:48 +05:30
}
2021-07-28 10:52:32 +05:30
2021-02-02 18:43:19 +05:30
variables [ strings . Trim ( kvs [ 0 ] , " " ) ] = strings . Trim ( kvs [ 1 ] , " " )
}
}
2021-07-24 00:02:48 +05:30
2021-02-02 18:43:19 +05:30
if valuesFile != "" {
2021-02-18 01:00:41 +05:30
if isGit {
2021-06-22 18:56:44 +05:30
filep , err := fs . Open ( filepath . Join ( policyResourcePath , valuesFile ) )
2021-02-18 01:00:41 +05:30
if err != nil {
fmt . Printf ( "Unable to open variable file: %s. error: %s" , valuesFile , err )
}
yamlFile , err = ioutil . ReadAll ( filep )
} else {
2021-06-22 18:56:44 +05:30
yamlFile , err = ioutil . ReadFile ( filepath . Join ( policyResourcePath , valuesFile ) )
2021-02-18 01:00:41 +05:30
}
2021-02-02 18:43:19 +05:30
if err != nil {
2021-07-28 10:52:32 +05:30
return variables , valuesMapResource , namespaceSelectorMap , sanitizederror . NewWithError ( "unable to read yaml" , err )
2021-02-02 18:43:19 +05:30
}
valuesBytes , err := yaml . ToJSON ( yamlFile )
if err != nil {
2021-07-28 10:52:32 +05:30
return variables , valuesMapResource , namespaceSelectorMap , sanitizederror . NewWithError ( "failed to convert json" , err )
2021-02-02 18:43:19 +05:30
}
values := & Values { }
if err := json . Unmarshal ( valuesBytes , values ) ; err != nil {
2021-07-28 10:52:32 +05:30
return variables , valuesMapResource , namespaceSelectorMap , sanitizederror . NewWithError ( "failed to decode yaml" , err )
2021-02-02 18:43:19 +05:30
}
for _ , p := range values . Policies {
2021-04-29 22:39:44 +05:30
resourceMap := make ( map [ string ] Resource )
2021-02-02 18:43:19 +05:30
for _ , r := range p . Resources {
2021-07-28 10:52:32 +05:30
for variableInFile := range r . Values {
2021-07-24 00:02:48 +05:30
if strings . Contains ( variableInFile , "request.object" ) {
2021-08-01 16:27:47 +05:30
if ! strings . Contains ( reqObjVars , variableInFile ) {
reqObjVars = reqObjVars + "," + variableInFile
}
delete ( r . Values , variableInFile )
continue
2021-07-24 00:02:48 +05:30
}
}
2021-04-29 22:39:44 +05:30
resourceMap [ r . Name ] = r
}
valuesMapResource [ p . Name ] = resourceMap
if p . Rules != nil {
ruleMap := make ( map [ string ] Rule )
for _ , r := range p . Rules {
ruleMap [ r . Name ] = r
}
valuesMapRule [ p . Name ] = ruleMap
2021-02-02 18:43:19 +05:30
}
}
2021-03-10 02:15:45 +05:30
for _ , n := range values . NamespaceSelectors {
namespaceSelectorMap [ n . Name ] = n . Labels
}
2021-02-02 18:43:19 +05:30
}
2021-08-01 16:27:47 +05:30
if reqObjVars != "" {
2021-08-07 05:46:12 +05:30
fmt . Printf ( ( "\nNOTICE: request.object.* variables are automatically parsed from the supplied resource. Ignoring value of variables `%v`.\n" ) , reqObjVars )
2021-08-01 16:27:47 +05:30
}
2021-04-29 22:39:44 +05:30
storePolices := make ( [ ] store . Policy , 0 )
for policyName , ruleMap := range valuesMapRule {
storeRules := make ( [ ] store . Rule , 0 )
for _ , rule := range ruleMap {
storeRules = append ( storeRules , store . Rule {
Name : rule . Name ,
Values : rule . Values ,
} )
}
storePolices = append ( storePolices , store . Policy {
Name : policyName ,
Rules : storeRules ,
} )
}
store . SetContext ( store . Context {
Policies : storePolices ,
} )
2021-07-28 10:52:32 +05:30
return variables , valuesMapResource , namespaceSelectorMap , nil
2021-02-02 18:43:19 +05:30
}
// 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 ,
2021-09-02 18:15:22 +05:30
mutateLogPath string , mutateLogPathIsDir bool , variables map [ string ] string , policyReport bool , namespaceSelectorMap map [ string ] map [ string ] string , stdin bool , rc * ResultCounts ) ( * response . EngineResponse , policyreport . Info , error ) {
2021-07-28 10:52:32 +05:30
operationIsDelete := false
if variables [ "request.operation" ] == "DELETE" {
operationIsDelete = true
}
2021-02-02 18:43:19 +05:30
2021-09-01 00:07:19 +05:30
// responseError := false
2021-02-02 18:43:19 +05:30
engineResponses := make ( [ ] * response . EngineResponse , 0 )
2021-03-10 02:15:45 +05:30
namespaceLabels := make ( map [ string ] string )
2021-02-02 18:43:19 +05:30
2021-03-11 00:24:21 +05:30
policyWithNamespaceSelector := false
for _ , p := range policy . Spec . Rules {
2021-03-11 10:31:53 +05:30
if p . MatchResources . ResourceDescription . NamespaceSelector != nil ||
p . ExcludeResources . ResourceDescription . NamespaceSelector != nil {
2021-03-11 00:24:21 +05:30
policyWithNamespaceSelector = true
break
}
}
if policyWithNamespaceSelector {
resourceNamespace := resource . GetNamespace ( )
namespaceLabels = namespaceSelectorMap [ resource . GetNamespace ( ) ]
if resourceNamespace != "default" && len ( namespaceLabels ) < 1 {
2021-09-02 18:15:22 +05:30
return & response . EngineResponse { } , policyreport . Info { } , sanitizederror . NewWithError ( fmt . Sprintf ( "failed to get namesapce labels for resource %s. use --values-file flag to pass the namespace labels" , resource . GetName ( ) ) , nil )
2021-03-11 00:24:21 +05:30
}
2021-03-10 02:15:45 +05:30
}
2021-03-11 00:24:21 +05:30
2021-02-02 18:43:19 +05:30
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 ( )
2021-07-24 00:02:48 +05:30
resourceRaw , err := resource . MarshalJSON ( )
if err != nil {
log . Log . Error ( err , "failed to marshal resource" )
}
if operationIsDelete {
err = ctx . AddResourceInOldObject ( resourceRaw )
} else {
err = ctx . AddResource ( resourceRaw )
}
if err != nil {
log . Log . Error ( err , "failed to load resource in context" )
}
2021-02-02 18:43:19 +05:30
for key , value := range variables {
2021-04-29 22:39:44 +05:30
jsonData := pkgcommon . VariableToJSON ( key , value )
2021-02-02 18:43:19 +05:30
ctx . AddJSON ( jsonData )
}
2021-03-10 02:15:45 +05:30
mutateResponse := engine . Mutate ( & engine . PolicyContext { Policy : * policy , NewResource : * resource , JSONContext : ctx , NamespaceLabels : namespaceLabels } )
2021-02-02 18:43:19 +05:30
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 )
}
2021-09-01 00:07:19 +05:30
// responseError = true
2021-02-02 18:43:19 +05:30
} else {
if len ( mutateResponse . PolicyResponse . Rules ) > 0 {
yamlEncodedResource , err := yamlv2 . Marshal ( mutateResponse . PatchedResource . Object )
if err != nil {
2021-09-02 18:15:22 +05:30
return & response . EngineResponse { } , policyreport . Info { } , sanitizederror . NewWithError ( "failed to marshal" , err )
2021-02-02 18:43:19 +05:30
}
if mutateLogPath == "" {
2021-03-26 23:33:45 +05:30
mutatedResource := string ( yamlEncodedResource ) + string ( "\n---" )
2021-02-02 18:43:19 +05:30
if len ( strings . TrimSpace ( mutatedResource ) ) > 0 {
2021-03-26 23:33:45 +05:30
if ! stdin {
fmt . Printf ( "\nmutate policy %s applied to %s:" , policy . Name , resPath )
}
2021-02-02 18:43:19 +05:30
fmt . Printf ( "\n" + mutatedResource )
fmt . Printf ( "\n" )
}
} else {
err := PrintMutatedOutput ( mutateLogPath , mutateLogPathIsDir , string ( yamlEncodedResource ) , resource . GetName ( ) + "-mutated" )
if err != nil {
2021-09-02 18:15:22 +05:30
return & response . EngineResponse { } , policyreport . Info { } , sanitizederror . NewWithError ( "failed to print mutated result" , err )
2021-02-02 18:43:19 +05:30
}
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 )
}
}
}
2021-09-02 03:54:02 +05:30
var policyHasValidate bool
for _ , rule := range policy . Spec . Rules {
if rule . HasValidate ( ) {
policyHasValidate = true
}
}
var info policyreport . Info
2021-09-02 18:15:22 +05:30
var validateResponse * response . EngineResponse
2021-09-02 03:54:02 +05:30
if policyHasValidate {
policyCtx := & engine . PolicyContext { Policy : * policy , NewResource : mutateResponse . PatchedResource , JSONContext : ctx , NamespaceLabels : namespaceLabels }
2021-09-02 18:15:22 +05:30
validateResponse = engine . Validate ( policyCtx )
2021-09-02 03:54:02 +05:30
info = CheckValidateEngineResponse ( policy , validateResponse , resPath , rc , policyReport )
}
2021-02-02 18:43:19 +05:30
var policyHasGenerate bool
for _ , rule := range policy . Spec . Rules {
if rule . HasGenerate ( ) {
policyHasGenerate = true
}
}
if policyHasGenerate {
2021-02-17 02:18:04 +05:30
policyContext := & engine . PolicyContext {
NewResource : * resource ,
Policy : * policy ,
ExcludeGroupRole : [ ] string { } ,
ExcludeResourceFunc : func ( s1 , s2 , s3 string ) bool {
return false
} ,
2021-03-10 02:15:45 +05:30
JSONContext : context . NewContext ( ) ,
NamespaceLabels : namespaceLabels ,
2021-02-17 02:18:04 +05:30
}
generateResponse := engine . Generate ( policyContext )
2021-09-02 03:54:02 +05:30
processGenerateEngineResponse ( policy , generateResponse , resPath , rc )
2021-02-02 18:43:19 +05:30
}
2021-02-07 20:26:56 -08:00
2021-09-02 18:15:22 +05:30
return validateResponse , info , nil
2021-02-02 18:43:19 +05:30
}
// 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 {
2021-07-09 18:01:46 -07:00
// truncation for the case when mutateLogPath is a file (not a directory) is handled under pkg/kyverno/apply/test_command.go
2021-02-02 18:43:19 +05:30
f , err = os . OpenFile ( mutateLogPath , os . O_APPEND | os . O_WRONLY , 0644 )
} else {
2021-03-12 06:00:37 +05:30
f , err = os . OpenFile ( mutateLogPath + "/" + fileName + ".yaml" , os . O_CREATE | os . O_WRONLY , 0644 )
2021-02-02 18:43:19 +05:30
}
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
2021-06-22 18:56:44 +05:30
func GetPoliciesFromPaths ( fs billy . Filesystem , dirPath [ ] string , isGit bool , policyResourcePath string ) ( policies [ ] * v1 . ClusterPolicy , err error ) {
2021-02-02 18:43:19 +05:30
var errors [ ] error
if isGit {
for _ , pp := range dirPath {
2021-06-22 18:56:44 +05:30
filep , err := fs . Open ( filepath . Join ( policyResourcePath , pp ) )
2021-02-18 01:00:41 +05:30
if err != nil {
fmt . Printf ( "Error: file not available with path %s: %v" , filep . Name ( ) , err . Error ( ) )
continue
}
2021-02-02 18:43:19 +05:30
bytes , err := ioutil . ReadAll ( filep )
if err != nil {
fmt . Printf ( "Error: failed to read file %s: %v" , filep . Name ( ) , err . Error ( ) )
2021-02-18 01:00:41 +05:30
continue
2021-02-02 18:43:19 +05:30
}
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 ( ) )
}
}
}
2021-02-07 20:26:56 -08:00
}
2021-02-02 18:43:19 +05:30
return
}
// GetResourceAccordingToResourcePath - get resources according to the resource path
2021-02-07 20:26:56 -08:00
func GetResourceAccordingToResourcePath ( fs billy . Filesystem , resourcePaths [ ] string ,
2021-06-22 18:56:44 +05:30
cluster bool , policies [ ] * v1 . ClusterPolicy , dClient * client . Client , namespace string , policyReport bool , isGit bool , policyResourcePath string ) ( resources [ ] * unstructured . Unstructured , err error ) {
2021-02-02 18:43:19 +05:30
if isGit {
2021-06-22 18:56:44 +05:30
resources , err = GetResourcesWithTest ( fs , policies , resourcePaths , isGit , policyResourcePath )
2021-02-02 18:43:19 +05:30
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
2021-02-07 20:26:56 -08:00
}
2021-09-02 00:02:55 +05:30
2021-09-02 02:24:04 +05:30
func CheckValidateEngineResponse ( policy * v1 . ClusterPolicy , validateResponse * response . EngineResponse , resPath string , rc * ResultCounts , policyReport bool ) policyreport . Info {
2021-09-02 00:02:55 +05:30
var violatedRules [ ] v1 . ViolatedRule
printCount := 0
for _ , policyRule := range policy . Spec . Rules {
ruleFoundInEngineResponse := false
for i , valResponseRule := range validateResponse . PolicyResponse . Rules {
if policyRule . Name == valResponseRule . Name {
ruleFoundInEngineResponse = true
vrule := v1 . ViolatedRule {
Name : valResponseRule . Name ,
Type : valResponseRule . Type ,
Message : valResponseRule . Message ,
}
if valResponseRule . Success {
2021-09-02 03:54:02 +05:30
rc . Pass ++
2021-09-02 00:02:55 +05:30
vrule . Check = report . StatusPass
} else {
2021-09-02 01:06:29 +05:30
if ! policyReport {
if printCount < 1 {
fmt . Printf ( "\npolicy %s -> resource %s failed: \n" , policy . Name , resPath )
printCount ++
}
fmt . Printf ( "%d. %s: %s \n" , i + 1 , valResponseRule . Name , valResponseRule . Message )
}
2021-09-02 03:54:02 +05:30
rc . Fail ++
2021-09-02 00:02:55 +05:30
vrule . Check = report . StatusFail
}
violatedRules = append ( violatedRules , vrule )
continue
}
}
if ! ruleFoundInEngineResponse {
2021-09-02 03:54:02 +05:30
rc . Skip ++
2021-09-02 00:02:55 +05:30
vruleSkip := v1 . ViolatedRule {
Name : policyRule . Name ,
Type : "Validation" ,
Message : policyRule . Validation . Message ,
Check : report . StatusSkip ,
}
violatedRules = append ( violatedRules , vruleSkip )
}
}
return buildPVInfo ( validateResponse , violatedRules )
}
func buildPVInfo ( er * response . EngineResponse , violatedRules [ ] v1 . ViolatedRule ) policyreport . Info {
info := policyreport . Info {
PolicyName : er . PolicyResponse . Policy . Name ,
Namespace : er . PatchedResource . GetNamespace ( ) ,
Results : [ ] policyreport . EngineResponseResult {
{
Resource : er . GetResourceSpec ( ) ,
Rules : violatedRules ,
} ,
} ,
}
return info
}
2021-09-02 03:54:02 +05:30
func processGenerateEngineResponse ( policy * v1 . ClusterPolicy , generateResponse * response . EngineResponse , resPath string , rc * ResultCounts ) {
printCount := 0
for _ , policyRule := range policy . Spec . Rules {
ruleFoundInEngineResponse := false
for i , genResponseRule := range generateResponse . PolicyResponse . Rules {
if policyRule . Name == genResponseRule . Name {
ruleFoundInEngineResponse = true
if genResponseRule . Success {
rc . Pass ++
} else {
if printCount < 1 {
2021-09-02 04:40:05 +05:30
fmt . Println ( "\ngenerate resource is valid" , "policy" , policy . Name , "resource" , resPath )
2021-09-02 03:54:02 +05:30
printCount ++
}
fmt . Printf ( "%d. %s \n" , i + 1 , genResponseRule . Name )
rc . Fail ++
}
continue
}
}
if ! ruleFoundInEngineResponse {
rc . Skip ++
}
}
}