2023-03-31 08:41:48 +02:00
package mutation
import (
"context"
2024-07-25 20:36:19 +03:00
"strings"
2023-03-31 08:41:48 +02:00
2023-05-31 09:27:35 +02:00
json_patch "github.com/evanphx/json-patch/v5"
2023-03-31 08:41:48 +02:00
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
2024-06-24 23:36:55 +07:00
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
2023-03-31 08:41:48 +02:00
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/internal"
2023-05-31 09:27:35 +02:00
"github.com/kyverno/kyverno/pkg/engine/mutate/patch"
2023-03-31 08:41:48 +02:00
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/variables"
2023-08-07 01:24:52 +05:30
"github.com/kyverno/kyverno/pkg/imageverifycache"
2023-04-03 21:58:58 +02:00
apiutils "github.com/kyverno/kyverno/pkg/utils/api"
2023-05-31 09:27:35 +02:00
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
2023-06-08 12:23:20 +02:00
"gomodules.xyz/jsonpatch/v2"
2023-03-31 08:41:48 +02:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2023-11-13 17:43:25 +02:00
"k8s.io/client-go/tools/cache"
2023-03-31 08:41:48 +02:00
)
type mutateImageHandler struct {
2024-02-08 13:10:29 +01:00
configuration config . Configuration
rclientFactory engineapi . RegistryClientFactory
ivCache imageverifycache . Client
ivm * engineapi . ImageVerificationMetadata
images [ ] apiutils . ImageInfo
2023-03-31 08:41:48 +02:00
}
func NewMutateImageHandler (
2023-04-03 21:58:58 +02:00
policyContext engineapi . PolicyContext ,
resource unstructured . Unstructured ,
rule kyvernov1 . Rule ,
2023-03-31 08:41:48 +02:00
configuration config . Configuration ,
2023-06-16 19:07:08 +05:30
rclientFactory engineapi . RegistryClientFactory ,
2023-08-07 01:24:52 +05:30
ivCache imageverifycache . Client ,
2023-03-31 08:41:48 +02:00
ivm * engineapi . ImageVerificationMetadata ,
2023-04-03 21:58:58 +02:00
) ( handlers . Handler , error ) {
if len ( rule . VerifyImages ) == 0 {
return nil , nil
}
ruleImages , _ , err := engineutils . ExtractMatchingImages ( resource , policyContext . JSONContext ( ) , rule , configuration )
if err != nil {
return nil , err
}
if len ( ruleImages ) == 0 {
return nil , nil
}
2023-03-31 08:41:48 +02:00
return mutateImageHandler {
2024-02-08 13:10:29 +01:00
configuration : configuration ,
rclientFactory : rclientFactory ,
ivm : ivm ,
ivCache : ivCache ,
images : ruleImages ,
2023-04-03 21:58:58 +02:00
} , nil
2023-03-31 08:41:48 +02:00
}
func ( h mutateImageHandler ) Process (
ctx context . Context ,
logger logr . Logger ,
policyContext engineapi . PolicyContext ,
resource unstructured . Unstructured ,
rule kyvernov1 . Rule ,
2023-04-03 06:57:48 +02:00
contextLoader engineapi . EngineContextLoader ,
2024-06-24 23:36:55 +07:00
exceptions [ ] * kyvernov2 . PolicyException ,
2023-03-31 08:41:48 +02:00
) ( unstructured . Unstructured , [ ] engineapi . RuleResponse ) {
2024-07-25 20:36:19 +03:00
// check if there are policy exceptions that match the incoming resource
matchedExceptions := engineutils . MatchesException ( exceptions , policyContext , logger )
if len ( matchedExceptions ) > 0 {
var keys [ ] string
for i , exception := range matchedExceptions {
key , err := cache . MetaNamespaceKeyFunc ( & matchedExceptions [ i ] )
if err != nil {
logger . Error ( err , "failed to compute policy exception key" , "namespace" , exception . GetNamespace ( ) , "name" , exception . GetName ( ) )
return resource , handlers . WithError ( rule , engineapi . Mutation , "failed to compute exception key" , err )
}
keys = append ( keys , key )
2023-11-13 17:43:25 +02:00
}
2024-07-25 20:36:19 +03:00
logger . V ( 3 ) . Info ( "policy rule is skipped due to policy exceptions" , "exceptions" , keys )
return resource , handlers . WithResponses (
2024-09-03 23:06:07 +05:30
engineapi . RuleSkip ( rule . Name , engineapi . Mutation , "rule is skipped due to policy exceptions" + strings . Join ( keys , ", " ) , rule . ReportProperties ) . WithExceptions ( matchedExceptions ) ,
2024-07-25 20:36:19 +03:00
)
2023-11-13 17:43:25 +02:00
}
2023-03-31 08:41:48 +02:00
jsonContext := policyContext . JSONContext ( )
ruleCopy , err := substituteVariables ( rule , jsonContext , logger )
if err != nil {
2023-04-05 12:35:38 +02:00
return resource , handlers . WithResponses (
2024-09-03 23:06:07 +05:30
engineapi . RuleError ( rule . Name , engineapi . ImageVerify , "failed to substitute variables" , err , rule . ReportProperties ) ,
2023-03-31 08:41:48 +02:00
)
}
var engineResponses [ ] * engineapi . RuleResponse
2023-05-31 09:27:35 +02:00
var patches [ ] jsonpatch . JsonPatchOperation
2023-06-05 18:41:46 +02:00
for _ , imageVerify := range ruleCopy . VerifyImages {
2023-06-16 19:07:08 +05:30
rclient , err := h . rclientFactory . GetClient ( ctx , imageVerify . ImageRegistryCredentials )
if err != nil {
return resource , handlers . WithResponses (
2024-09-03 23:06:07 +05:30
engineapi . RuleError ( rule . Name , engineapi . ImageVerify , "failed to fetch secrets" , err , rule . ReportProperties ) ,
2023-06-16 19:07:08 +05:30
)
}
2024-02-08 13:10:29 +01:00
iv := internal . NewImageVerifier ( logger , rclient , h . ivCache , policyContext , * ruleCopy , h . ivm )
2023-06-05 18:41:46 +02:00
patch , ruleResponse := iv . Verify ( ctx , imageVerify , h . images , h . configuration )
patches = append ( patches , patch ... )
engineResponses = append ( engineResponses , ruleResponse ... )
2023-05-31 09:27:35 +02:00
}
if len ( patches ) != 0 {
patch := jsonutils . JoinPatches ( patch . ConvertPatches ( patches ... ) ... )
decoded , err := json_patch . DecodePatch ( patch )
if err != nil {
return resource , handlers . WithResponses (
2024-09-03 23:06:07 +05:30
engineapi . RuleError ( rule . Name , engineapi . ImageVerify , "failed to decode patch" , err , rule . ReportProperties ) ,
2023-05-31 09:27:35 +02:00
)
}
options := & json_patch . ApplyOptions { SupportNegativeIndices : true , AllowMissingPathOnRemove : true , EnsurePathExistsOnAdd : true }
resourceBytes , err := resource . MarshalJSON ( )
if err != nil {
return resource , handlers . WithResponses (
2024-09-03 23:06:07 +05:30
engineapi . RuleError ( rule . Name , engineapi . ImageVerify , "failed to marshal resource" , err , rule . ReportProperties ) ,
2023-05-31 09:27:35 +02:00
)
}
patchedResourceBytes , err := decoded . ApplyWithOptions ( resourceBytes , options )
if err != nil {
return resource , handlers . WithResponses (
2024-09-03 23:06:07 +05:30
engineapi . RuleError ( rule . Name , engineapi . ImageVerify , "failed to apply patch" , err , rule . ReportProperties ) ,
2023-05-31 09:27:35 +02:00
)
}
if err := resource . UnmarshalJSON ( patchedResourceBytes ) ; err != nil {
return resource , handlers . WithResponses (
2024-09-03 23:06:07 +05:30
engineapi . RuleError ( rule . Name , engineapi . ImageVerify , "failed to unmarshal resource" , err , rule . ReportProperties ) ,
2023-05-31 09:27:35 +02:00
)
}
}
2023-04-05 12:35:38 +02:00
return resource , handlers . WithResponses ( engineResponses ... )
2023-03-31 08:41:48 +02:00
}
func substituteVariables ( rule kyvernov1 . Rule , ctx enginecontext . EvalInterface , logger logr . Logger ) ( * kyvernov1 . Rule , error ) {
// remove attestations as variables are not substituted in them
ruleCopy := * rule . DeepCopy ( )
for i := range ruleCopy . VerifyImages {
2023-06-01 13:35:28 +05:30
for j := range ruleCopy . VerifyImages [ i ] . Attestations {
ruleCopy . VerifyImages [ i ] . Attestations [ j ] . Conditions = nil
}
2023-03-31 08:41:48 +02:00
}
var err error
ruleCopy , err = variables . SubstituteAllInRule ( logger , ctx , ruleCopy )
if err != nil {
return nil , err
}
// replace attestations
2023-06-01 13:35:28 +05:30
for i := range ruleCopy . VerifyImages {
for j := range ruleCopy . VerifyImages [ i ] . Attestations {
ruleCopy . VerifyImages [ i ] . Attestations [ j ] . Conditions = rule . VerifyImages [ i ] . Attestations [ j ] . Conditions
}
2023-03-31 08:41:48 +02:00
}
return & ruleCopy , nil
}