2020-10-15 17:29:07 -07:00
package common
import (
2022-05-11 14:04:40 +02:00
"context"
2020-10-15 17:29:07 -07:00
"errors"
2020-11-03 02:01:20 +05:30
"fmt"
2022-09-30 15:25:19 +08:00
"io"
2021-02-08 23:38:06 +05:30
"net/http"
2022-09-30 15:25:19 +08:00
"os"
2021-02-18 01:00:41 +05:30
"path/filepath"
2021-02-08 23:38:06 +05:30
"strings"
2020-10-15 17:29:07 -07:00
2021-02-07 20:26:56 -08:00
"github.com/go-git/go-billy/v5"
2022-05-17 13:12:43 +02:00
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
2022-03-28 16:01:27 +02:00
"github.com/kyverno/kyverno/pkg/autogen"
2022-08-31 14:03:47 +08:00
"github.com/kyverno/kyverno/pkg/clients/dclient"
2020-10-15 17:29:07 -07:00
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
2022-04-01 11:56:16 +02:00
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
2022-08-24 15:08:24 +02:00
"golang.org/x/text/cases"
"golang.org/x/text/language"
2020-10-15 17:29:07 -07:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2020-11-10 02:37:55 +05:30
"k8s.io/apimachinery/pkg/runtime/schema"
2020-11-04 15:56:33 -08:00
"k8s.io/client-go/kubernetes/scheme"
2021-10-29 18:13:20 +02:00
"sigs.k8s.io/controller-runtime/pkg/log"
2021-02-10 06:46:02 +05:30
"sigs.k8s.io/yaml"
2020-10-15 17:29:07 -07:00
)
// GetResources gets matched resources by the given policies
// the resources are fetched from
// - local paths to resources, if given
// - the k8s cluster, if given
2022-05-17 16:40:51 +02:00
func GetResources ( policies [ ] kyvernov1 . PolicyInterface , resourcePaths [ ] string , dClient dclient . Interface , cluster bool , namespace string , policyReport bool ) ( [ ] * unstructured . Unstructured , error ) {
2020-10-21 20:05:05 +05:30
resources := make ( [ ] * unstructured . Unstructured , 0 )
2020-10-15 17:29:07 -07:00
var err error
2022-05-17 08:19:03 +02:00
resourceTypesMap := make ( map [ string ] bool )
2020-10-21 20:05:05 +05:30
var resourceTypes [ ] string
2020-10-30 16:38:19 +05:30
2020-10-21 20:05:05 +05:30
for _ , policy := range policies {
2022-03-28 16:01:27 +02:00
for _ , rule := range autogen . ComputeRules ( policy ) {
2022-03-16 00:50:33 -04:00
resourceTypesInRule := GetKindsFromRule ( rule )
2021-09-21 16:16:47 +05:30
for resourceKind := range resourceTypesInRule {
resourceTypesMap [ resourceKind ] = true
}
2020-10-15 17:29:07 -07:00
}
2020-10-21 20:05:05 +05:30
}
for kind := range resourceTypesMap {
resourceTypes = append ( resourceTypes , kind )
}
2020-10-15 17:29:07 -07:00
2020-10-21 20:05:05 +05:30
if cluster && dClient != nil {
2021-06-16 08:17:31 +05:30
resources , err = whenClusterIsTrue ( resourceTypes , dClient , namespace , resourcePaths , policyReport )
2020-10-15 17:29:07 -07:00
if err != nil {
2021-06-16 08:17:31 +05:30
return resources , err
2021-06-16 07:56:08 +05:30
}
2021-06-16 08:17:31 +05:30
} else if len ( resourcePaths ) > 0 {
resources , err = whenClusterIsFalse ( resourcePaths , policyReport )
if err != nil {
return resources , err
}
}
return resources , err
}
2021-06-16 07:56:08 +05:30
2022-05-17 16:40:51 +02:00
func whenClusterIsTrue ( resourceTypes [ ] string , dClient dclient . Interface , namespace string , resourcePaths [ ] string , policyReport bool ) ( [ ] * unstructured . Unstructured , error ) {
2021-06-16 08:17:31 +05:30
resources := make ( [ ] * unstructured . Unstructured , 0 )
resourceMap , err := getResourcesOfTypeFromCluster ( resourceTypes , dClient , namespace )
if err != nil {
return nil , err
}
2021-06-16 07:56:08 +05:30
2021-06-16 08:17:31 +05:30
if len ( resourcePaths ) == 0 {
for _ , rr := range resourceMap {
resources = append ( resources , rr )
2021-06-16 08:12:03 +05:30
}
2021-06-16 08:17:31 +05:30
} else {
2021-06-16 08:12:03 +05:30
for _ , resourcePath := range resourcePaths {
2021-06-16 08:17:31 +05:30
lenOfResource := len ( resources )
for rn , rr := range resourceMap {
s := strings . Split ( rn , "-" )
if s [ 2 ] == resourcePath {
resources = append ( resources , rr )
2020-11-20 12:27:02 +05:30
}
2020-11-11 15:27:55 +05:30
}
2021-06-16 07:56:08 +05:30
2021-06-16 08:17:31 +05:30
if lenOfResource >= len ( resources ) {
if policyReport {
log . Log . V ( 3 ) . Info ( fmt . Sprintf ( "%s not found in cluster" , resourcePath ) )
} else {
fmt . Printf ( "\n----------------------------------------------------------------------\nresource %s not found in cluster\n----------------------------------------------------------------------\n" , resourcePath )
}
2021-06-16 09:19:58 +05:30
return nil , fmt . Errorf ( "%s not found in cluster" , resourcePath )
2021-06-16 08:12:03 +05:30
}
2020-10-15 17:29:07 -07:00
}
}
2021-06-16 08:17:31 +05:30
return resources , nil
2020-10-15 17:29:07 -07:00
}
2021-02-07 20:26:56 -08:00
2021-06-16 08:17:31 +05:30
func whenClusterIsFalse ( resourcePaths [ ] string , policyReport bool ) ( [ ] * unstructured . Unstructured , error ) {
resources := make ( [ ] * unstructured . Unstructured , 0 )
for _ , resourcePath := range resourcePaths {
resourceBytes , err := getFileBytes ( resourcePath )
if err != nil {
if policyReport {
log . Log . V ( 3 ) . Info ( fmt . Sprintf ( "failed to load resources: %s." , resourcePath ) , "error" , err )
} else {
fmt . Printf ( "\n----------------------------------------------------------------------\nfailed to load resources: %s. \nerror: %s\n----------------------------------------------------------------------\n" , resourcePath , err )
}
continue
}
2021-06-16 08:12:03 +05:30
2021-06-16 08:17:31 +05:30
getResources , err := GetResource ( resourceBytes )
if err != nil {
return nil , err
}
2021-06-16 08:12:03 +05:30
2021-06-16 09:19:58 +05:30
resources = append ( resources , getResources ... )
2021-06-16 08:17:31 +05:30
}
return resources , nil
}
2021-06-16 08:12:03 +05:30
2021-02-01 16:22:41 +05:30
// GetResourcesWithTest with gets matched resources by the given policies
2022-05-17 13:12:43 +02:00
func GetResourcesWithTest ( fs billy . Filesystem , policies [ ] kyvernov1 . PolicyInterface , resourcePaths [ ] string , isGit bool , policyResourcePath string ) ( [ ] * unstructured . Unstructured , error ) {
2021-02-01 16:22:41 +05:30
resources := make ( [ ] * unstructured . Unstructured , 0 )
2022-05-17 08:19:03 +02:00
resourceTypesMap := make ( map [ string ] bool )
2021-02-01 16:22:41 +05:30
for _ , policy := range policies {
2022-03-28 16:01:27 +02:00
for _ , rule := range autogen . ComputeRules ( policy ) {
2021-02-01 16:22:41 +05:30
for _ , kind := range rule . MatchResources . Kinds {
resourceTypesMap [ kind ] = true
}
}
}
if len ( resourcePaths ) > 0 {
for _ , resourcePath := range resourcePaths {
var resourceBytes [ ] byte
var err error
if isGit {
2021-06-22 18:56:44 +05:30
filep , err := fs . Open ( filepath . Join ( policyResourcePath , resourcePath ) )
2021-02-01 16:22:41 +05:30
if err != nil {
fmt . Printf ( "Unable to open resource file: %s. error: %s" , resourcePath , err )
continue
}
2022-09-30 15:25:19 +08:00
resourceBytes , _ = io . ReadAll ( filep )
2021-02-01 16:22:41 +05:30
} else {
resourceBytes , err = getFileBytes ( resourcePath )
}
if err != nil {
fmt . Printf ( "\n----------------------------------------------------------------------\nfailed to load resources: %s. \nerror: %s\n----------------------------------------------------------------------\n" , resourcePath , err )
continue
}
getResources , err := GetResource ( resourceBytes )
if err != nil {
return nil , err
}
2022-05-07 22:14:57 +05:30
resources = append ( resources , getResources ... )
2021-02-01 16:22:41 +05:30
}
}
return resources , nil
}
2020-10-15 17:29:07 -07:00
// GetResource converts raw bytes to unstructured object
func GetResource ( resourceBytes [ ] byte ) ( [ ] * unstructured . Unstructured , error ) {
resources := make ( [ ] * unstructured . Unstructured , 0 )
var getErrString string
2022-04-01 11:56:16 +02:00
files , splitDocError := yamlutils . SplitDocuments ( resourceBytes )
2020-10-15 17:29:07 -07:00
if splitDocError != nil {
return nil , splitDocError
}
for _ , resourceYaml := range files {
resource , err := convertResourceToUnstructured ( resourceYaml )
if err != nil {
2021-04-29 23:11:15 +05:30
if strings . Contains ( err . Error ( ) , "Object 'Kind' is missing" ) {
log . Log . V ( 3 ) . Info ( "skipping resource as kind not found" )
continue
}
2020-10-15 17:29:07 -07:00
getErrString = getErrString + err . Error ( ) + "\n"
}
resources = append ( resources , resource )
}
if getErrString != "" {
return nil , errors . New ( getErrString )
}
return resources , nil
}
2022-05-17 16:40:51 +02:00
func getResourcesOfTypeFromCluster ( resourceTypes [ ] string , dClient dclient . Interface , namespace string ) ( map [ string ] * unstructured . Unstructured , error ) {
2021-06-15 23:35:22 +05:30
r := make ( map [ string ] * unstructured . Unstructured )
2020-10-16 19:56:32 +05:30
2020-10-15 17:29:07 -07:00
for _ , kind := range resourceTypes {
2022-11-29 14:59:40 +01:00
resourceList , err := dClient . ListResource ( context . TODO ( ) , "" , kind , namespace , nil )
2020-10-15 17:29:07 -07:00
if err != nil {
2020-11-18 11:08:24 +05:30
continue
2020-10-15 17:29:07 -07:00
}
2020-11-18 11:08:24 +05:30
2020-10-15 17:29:07 -07:00
version := resourceList . GetAPIVersion ( )
for _ , resource := range resourceList . Items {
2021-06-15 23:35:22 +05:30
key := kind + "-" + resource . GetNamespace ( ) + "-" + resource . GetName ( )
r [ key ] = resource . DeepCopy ( )
2020-10-15 17:29:07 -07:00
resource . SetGroupVersionKind ( schema . GroupVersionKind {
Group : "" ,
Version : version ,
Kind : kind ,
} )
}
}
2020-10-21 20:05:05 +05:30
return r , nil
2020-10-15 17:29:07 -07:00
}
func getFileBytes ( path string ) ( [ ] byte , error ) {
2021-02-08 23:38:06 +05:30
var (
file [ ] byte
err error
)
2021-10-29 16:06:03 +01:00
if IsHTTPRegex . MatchString ( path ) {
2021-10-14 00:05:13 +02:00
// We accept here that a random URL might be called based on user provided input.
2022-05-11 14:04:40 +02:00
req , err := http . NewRequestWithContext ( context . TODO ( ) , http . MethodGet , path , nil )
if err != nil {
return nil , err
}
resp , err := http . DefaultClient . Do ( req )
2021-02-08 23:38:06 +05:30
if err != nil {
return nil , err
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
return nil , err
}
2022-09-30 15:25:19 +08:00
file , err = io . ReadAll ( resp . Body )
2021-02-08 23:38:06 +05:30
if err != nil {
return nil , err
}
} else {
2021-10-13 10:48:45 -07:00
path = filepath . Clean ( path )
2021-10-14 00:05:13 +02:00
// We accept the risk of including a user provided file here.
2022-09-30 15:25:19 +08:00
file , err = os . ReadFile ( path ) // #nosec G304
2021-02-08 23:38:06 +05:30
if err != nil {
return nil , err
}
2020-10-15 17:29:07 -07:00
}
2021-02-08 23:38:06 +05:30
2020-10-15 17:29:07 -07:00
return file , err
}
func convertResourceToUnstructured ( resourceYaml [ ] byte ) ( * unstructured . Unstructured , error ) {
decode := scheme . Codecs . UniversalDeserializer ( ) . Decode
2021-03-30 00:43:26 +05:30
_ , metaData , decodeErr := decode ( resourceYaml , nil , nil )
if decodeErr != nil {
if ! strings . Contains ( decodeErr . Error ( ) , "no kind" ) {
return nil , decodeErr
}
2020-10-15 17:29:07 -07:00
}
2021-02-10 06:46:02 +05:30
resourceJSON , err := yaml . YAMLToJSON ( resourceYaml )
2020-10-15 17:29:07 -07:00
if err != nil {
return nil , err
}
resource , err := engineutils . ConvertToUnstructured ( resourceJSON )
if err != nil {
return nil , err
}
2021-03-30 00:43:26 +05:30
if decodeErr == nil {
resource . SetGroupVersionKind ( * metaData )
}
2020-10-15 17:29:07 -07:00
if resource . GetNamespace ( ) == "" {
resource . SetNamespace ( "default" )
}
return resource , nil
}
2021-09-08 13:07:34 +05:30
2021-10-01 14:16:33 +05:30
// GetPatchedResource converts raw bytes to unstructured object
2022-05-25 19:56:22 +05:30
func GetPatchedAndGeneratedResource ( resourceBytes [ ] byte ) ( unstructured . Unstructured , error ) {
getResource , err := GetResource ( resourceBytes )
2022-05-09 18:55:35 +02:00
if err != nil {
return unstructured . Unstructured { } , err
}
2022-05-25 19:56:22 +05:30
resource := * getResource [ 0 ]
return resource , nil
2021-10-01 14:16:33 +05:30
}
2022-03-16 00:50:33 -04:00
// GetKindsFromRule will return the kinds from policy match block
2022-05-17 13:12:43 +02:00
func GetKindsFromRule ( rule kyvernov1 . Rule ) map [ string ] bool {
2022-05-17 08:19:03 +02:00
resourceTypesMap := make ( map [ string ] bool )
2021-09-08 13:07:34 +05:30
for _ , kind := range rule . MatchResources . Kinds {
if strings . Contains ( kind , "/" ) {
lastElement := kind [ strings . LastIndex ( kind , "/" ) + 1 : ]
2022-08-24 15:08:24 +02:00
resourceTypesMap [ cases . Title ( language . Und , cases . NoLower ) . String ( lastElement ) ] = true
2021-09-08 13:07:34 +05:30
}
2022-08-24 15:08:24 +02:00
resourceTypesMap [ cases . Title ( language . Und , cases . NoLower ) . String ( kind ) ] = true
2021-09-08 13:07:34 +05:30
}
if rule . MatchResources . Any != nil {
for _ , resFilter := range rule . MatchResources . Any {
for _ , kind := range resFilter . ResourceDescription . Kinds {
if strings . Contains ( kind , "/" ) {
lastElement := kind [ strings . LastIndex ( kind , "/" ) + 1 : ]
2022-08-24 15:08:24 +02:00
resourceTypesMap [ cases . Title ( language . Und , cases . NoLower ) . String ( lastElement ) ] = true
2021-09-08 13:07:34 +05:30
}
resourceTypesMap [ kind ] = true
}
}
}
if rule . MatchResources . All != nil {
for _ , resFilter := range rule . MatchResources . All {
for _ , kind := range resFilter . ResourceDescription . Kinds {
if strings . Contains ( kind , "/" ) {
lastElement := kind [ strings . LastIndex ( kind , "/" ) + 1 : ]
2022-08-24 15:08:24 +02:00
resourceTypesMap [ cases . Title ( language . Und , cases . NoLower ) . String ( lastElement ) ] = true
2021-09-08 13:07:34 +05:30
}
2022-08-24 15:08:24 +02:00
resourceTypesMap [ cases . Title ( language . Und , cases . NoLower ) . String ( kind ) ] = true
2021-09-08 13:07:34 +05:30
}
}
}
return resourceTypesMap
}