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"
2023-07-03 15:22:05 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/values"
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"
2022-12-09 22:15:23 +05:30
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
2022-04-01 11:56:16 +02:00
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
2023-07-28 03:32:30 +03:00
"github.com/kyverno/kyverno/pkg/validatingadmissionpolicy"
2023-05-10 11:12:53 +03:00
"k8s.io/api/admissionregistration/v1alpha1"
2023-03-22 17:04:32 +01:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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-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-12-09 22:15:23 +05:30
func GetResources (
2023-05-10 11:12:53 +03:00
policies [ ] kyvernov1 . PolicyInterface , validatingAdmissionPolicies [ ] v1alpha1 . ValidatingAdmissionPolicy , resourcePaths [ ] string , dClient dclient . Interface , cluster bool ,
2022-12-09 22:15:23 +05:30
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
2020-10-30 16:38:19 +05:30
2022-12-09 22:15:23 +05:30
if cluster && dClient != nil {
2023-05-10 11:12:53 +03:00
if len ( policies ) > 0 {
matchedResources := & KyvernoResources {
policies : policies ,
2021-09-21 16:16:47 +05:30
}
2020-10-21 20:05:05 +05:30
2023-05-10 11:12:53 +03:00
resources , err = matchedResources . FetchResourcesFromPolicy ( resourcePaths , dClient , namespace , policyReport )
if err != nil {
return resources , err
}
2022-12-09 22:15:23 +05:30
}
2020-10-15 17:29:07 -07:00
2023-05-10 11:12:53 +03:00
if len ( validatingAdmissionPolicies ) > 0 {
matchedResources := & ValidatingAdmissionResources {
policies : validatingAdmissionPolicies ,
}
resources , err = matchedResources . FetchResourcesFromPolicy ( resourcePaths , dClient , namespace , policyReport )
if err != nil {
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
2023-07-03 15:22:05 +02:00
func whenClusterIsTrue ( resourceTypes [ ] schema . GroupVersionKind , subresourceMap map [ schema . GroupVersionKind ] values . Subresource , 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 )
2022-12-09 22:15:23 +05:30
resourceMap , err := getResourcesOfTypeFromCluster ( resourceTypes , subresourceMap , dClient , namespace )
2021-06-16 08:17:31 +05:30
if err != nil {
return nil , err
}
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 08:17:31 +05:30
if lenOfResource >= len ( resources ) {
if policyReport {
2023-04-18 14:08:17 +02:00
log . V ( 3 ) . Info ( fmt . Sprintf ( "%s not found in cluster" , resourcePath ) )
2021-06-16 08:17:31 +05:30
} 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 {
2023-04-18 14:08:17 +02:00
log . V ( 3 ) . Info ( fmt . Sprintf ( "failed to load resources: %s." , resourcePath ) , "error" , err )
2021-06-16 08:17:31 +05:30
} 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" ) {
2023-04-18 14:08:17 +02:00
log . V ( 3 ) . Info ( "skipping resource as kind not found" )
2021-04-29 23:11:15 +05:30
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
}
2023-07-03 15:22:05 +02:00
func getResourcesOfTypeFromCluster ( resourceTypes [ ] schema . GroupVersionKind , subresourceMap map [ schema . GroupVersionKind ] values . Subresource , 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-15 17:29:07 -07:00
for _ , kind := range resourceTypes {
2022-12-09 22:15:23 +05:30
resourceList , err := dClient . ListResource ( context . TODO ( ) , kind . GroupVersion ( ) . String ( ) , kind . 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
}
2022-12-09 22:15:23 +05:30
gvk := resourceList . GroupVersionKind ( )
2020-10-15 17:29:07 -07:00
for _ , resource := range resourceList . Items {
2022-12-09 22:15:23 +05:30
key := kind . Kind + "-" + resource . GetNamespace ( ) + "-" + resource . GetName ( )
resource . SetGroupVersionKind ( schema . GroupVersionKind {
Group : gvk . Group ,
Version : gvk . Version ,
Kind : kind . Kind ,
} )
2021-06-15 23:35:22 +05:30
r [ key ] = resource . DeepCopy ( )
2022-12-09 22:15:23 +05:30
}
}
for _ , subresource := range subresourceMap {
parentGV := schema . GroupVersion { Group : subresource . ParentResource . Group , Version : subresource . ParentResource . Version }
resourceList , err := dClient . ListResource ( context . TODO ( ) , parentGV . String ( ) , subresource . ParentResource . Kind , namespace , nil )
if err != nil {
continue
}
parentResourceNames := make ( [ ] string , 0 )
for _ , resource := range resourceList . Items {
parentResourceNames = append ( parentResourceNames , resource . GetName ( ) )
}
for _ , parentResourceName := range parentResourceNames {
subresourceName := strings . Split ( subresource . APIResource . Name , "/" ) [ 1 ]
resource , err := dClient . GetResource ( context . TODO ( ) , parentGV . String ( ) , subresource . ParentResource . Kind , namespace , parentResourceName , subresourceName )
if err != nil {
fmt . Printf ( "Error: %s" , err . Error ( ) )
continue
}
key := subresource . APIResource . Kind + "-" + resource . GetNamespace ( ) + "-" + resource . GetName ( )
2020-10-15 17:29:07 -07:00
resource . SetGroupVersionKind ( schema . GroupVersionKind {
2022-12-09 22:15:23 +05:30
Group : subresource . APIResource . Group ,
Version : subresource . APIResource . Version ,
Kind : subresource . APIResource . Kind ,
2020-10-15 17:29:07 -07:00
} )
2022-12-09 22:15:23 +05:30
r [ key ] = resource . DeepCopy ( )
2020-10-15 17:29:07 -07:00
}
}
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
}
2023-01-03 13:02:15 +01:00
resource , err := kubeutils . BytesToUnstructured ( resourceJSON )
2020-10-15 17:29:07 -07:00
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
2022-12-09 22:15:23 +05:30
// GetPatchedAndGeneratedResource 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
}
2023-06-13 17:12:13 +08:00
if len ( getResource ) > 0 && getResource [ 0 ] != nil {
resource := * getResource [ 0 ]
return resource , nil
}
return unstructured . Unstructured { } , err
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
2023-07-03 15:22:05 +02:00
func GetKindsFromRule ( rule kyvernov1 . Rule , client dclient . Interface ) ( map [ schema . GroupVersionKind ] bool , map [ schema . GroupVersionKind ] values . Subresource ) {
2022-12-09 22:15:23 +05:30
resourceTypesMap := make ( map [ schema . GroupVersionKind ] bool )
2023-07-03 15:22:05 +02:00
subresourceMap := make ( map [ schema . GroupVersionKind ] values . Subresource )
2021-09-08 13:07:34 +05:30
for _ , kind := range rule . MatchResources . Kinds {
2022-12-09 22:15:23 +05:30
addGVKToResourceTypesMap ( kind , resourceTypesMap , subresourceMap , client )
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 {
2022-12-09 22:15:23 +05:30
addGVKToResourceTypesMap ( kind , resourceTypesMap , subresourceMap , client )
2021-09-08 13:07:34 +05:30
}
}
}
if rule . MatchResources . All != nil {
for _ , resFilter := range rule . MatchResources . All {
for _ , kind := range resFilter . ResourceDescription . Kinds {
2022-12-09 22:15:23 +05:30
addGVKToResourceTypesMap ( kind , resourceTypesMap , subresourceMap , client )
2021-09-08 13:07:34 +05:30
}
}
}
2022-12-09 22:15:23 +05:30
return resourceTypesMap , subresourceMap
}
2023-07-28 03:32:30 +03:00
func getKindsFromValidatingAdmissionPolicy ( policy v1alpha1 . ValidatingAdmissionPolicy , client dclient . Interface ) ( map [ schema . GroupVersionKind ] bool , map [ schema . GroupVersionKind ] values . Subresource ) {
2023-05-10 11:12:53 +03:00
resourceTypesMap := make ( map [ schema . GroupVersionKind ] bool )
2023-07-03 15:22:05 +02:00
subresourceMap := make ( map [ schema . GroupVersionKind ] values . Subresource )
2023-05-10 11:12:53 +03:00
2023-07-28 03:32:30 +03:00
kinds := validatingadmissionpolicy . GetKinds ( policy )
for _ , kind := range kinds {
addGVKToResourceTypesMap ( kind , resourceTypesMap , subresourceMap , client )
2023-05-10 11:12:53 +03:00
}
2023-07-28 03:32:30 +03:00
return resourceTypesMap , subresourceMap
2023-05-10 11:12:53 +03:00
}
2023-07-03 15:22:05 +02:00
func addGVKToResourceTypesMap ( kind string , resourceTypesMap map [ schema . GroupVersionKind ] bool , subresourceMap map [ schema . GroupVersionKind ] values . Subresource , client dclient . Interface ) {
2023-03-22 17:04:32 +01:00
group , version , kind , subresource := kubeutils . ParseKindSelector ( kind )
gvrss , err := client . Discovery ( ) . FindResources ( group , version , kind , subresource )
2022-12-09 22:15:23 +05:30
if err != nil {
2023-04-18 14:08:17 +02:00
log . Info ( "failed to find resource" , "kind" , kind , "error" , err )
2022-12-09 22:15:23 +05:30
return
}
2023-03-22 17:04:32 +01:00
for parent , child := range gvrss {
// The resource is not a subresource
if parent . SubResource == "" {
resourceTypesMap [ parent . GroupVersionKind ( ) ] = true
} else {
gvk := schema . GroupVersionKind {
Group : child . Group , Version : child . Version , Kind : child . Kind ,
}
2023-07-03 15:22:05 +02:00
subresourceMap [ gvk ] = values . Subresource {
2023-03-22 17:04:32 +01:00
APIResource : child ,
ParentResource : metav1 . APIResource {
Group : parent . Group ,
Version : parent . Version ,
Kind : parent . Kind ,
Name : parent . Resource ,
} ,
}
2022-12-09 22:15:23 +05:30
}
}
2021-09-08 13:07:34 +05:30
}