2023-02-07 16:51:25 +00:00
package internal
import (
"context"
2023-07-24 08:10:07 +00:00
"encoding/json"
2023-02-07 16:51:25 +00:00
"errors"
"fmt"
"net"
"strings"
2023-08-30 07:26:40 +00:00
"time"
2023-02-07 16:51:25 +00:00
"github.com/go-logr/logr"
2023-08-02 14:02:21 +00:00
"github.com/kyverno/kyverno/api/kyverno"
2023-02-07 16:51:25 +00:00
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
2023-10-29 23:59:53 +00:00
"github.com/kyverno/kyverno/ext/wildcard"
2023-02-07 16:51:25 +00:00
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/cosign"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/variables"
2023-02-20 16:26:10 +00:00
"github.com/kyverno/kyverno/pkg/images"
2023-08-06 19:54:52 +00:00
"github.com/kyverno/kyverno/pkg/imageverifycache"
2023-06-01 08:05:28 +00:00
"github.com/kyverno/kyverno/pkg/notary"
2023-02-07 16:51:25 +00:00
apiutils "github.com/kyverno/kyverno/pkg/utils/api"
"github.com/kyverno/kyverno/pkg/utils/jsonpointer"
"go.uber.org/multierr"
2023-06-08 10:23:20 +00:00
"gomodules.xyz/jsonpatch/v2"
2023-02-07 16:51:25 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type ImageVerifier struct {
2024-02-08 12:10:29 +00:00
logger logr . Logger
rclient engineapi . RegistryClient
ivCache imageverifycache . Client
policyContext engineapi . PolicyContext
rule kyvernov1 . Rule
ivm * engineapi . ImageVerificationMetadata
2023-02-07 16:51:25 +00:00
}
func NewImageVerifier (
logger logr . Logger ,
2023-06-16 13:37:08 +00:00
rclient engineapi . RegistryClient ,
2023-08-06 19:54:52 +00:00
ivCache imageverifycache . Client ,
2023-02-07 16:51:25 +00:00
policyContext engineapi . PolicyContext ,
2023-03-29 17:44:09 +00:00
rule kyvernov1 . Rule ,
2023-02-07 16:51:25 +00:00
ivm * engineapi . ImageVerificationMetadata ,
) * ImageVerifier {
return & ImageVerifier {
2024-02-08 12:10:29 +00:00
logger : logger ,
rclient : rclient ,
ivCache : ivCache ,
policyContext : policyContext ,
rule : rule ,
ivm : ivm ,
2023-02-07 16:51:25 +00:00
}
}
func HasImageVerifiedAnnotationChanged ( ctx engineapi . PolicyContext , log logr . Logger ) bool {
newResource := ctx . NewResource ( )
oldResource := ctx . OldResource ( )
2023-03-22 04:46:35 +00:00
if newResource . Object == nil || oldResource . Object == nil {
2023-02-07 16:51:25 +00:00
return false
}
2023-08-02 14:02:21 +00:00
newValue := newResource . GetAnnotations ( ) [ kyverno . AnnotationImageVerify ]
oldValue := oldResource . GetAnnotations ( ) [ kyverno . AnnotationImageVerify ]
2023-07-24 08:10:07 +00:00
if newValue == oldValue {
return false
}
2024-01-23 12:27:39 +00:00
var newValueObj , oldValueObj map [ string ] engineapi . ImageVerificationMetadataStatus
2023-07-24 08:10:07 +00:00
err := json . Unmarshal ( [ ] byte ( newValue ) , & newValueObj )
if err != nil {
log . Error ( err , "failed to parse new resource annotation." )
return true
}
err = json . Unmarshal ( [ ] byte ( oldValue ) , & oldValueObj )
if err != nil {
log . Error ( err , "failed to parse old resource annotation." )
return true
2023-02-07 16:51:25 +00:00
}
2023-07-24 08:10:07 +00:00
for img := range oldValueObj {
_ , found := newValueObj [ img ]
if found {
result := newValueObj [ img ] != oldValueObj [ img ]
if result {
2023-08-02 14:02:21 +00:00
log . V ( 2 ) . Info ( "annotation mismatch" , "oldValue" , oldValue , "newValue" , newValue , "key" , kyverno . AnnotationImageVerify )
2023-07-24 08:10:07 +00:00
return result
}
}
}
return false
2023-02-07 16:51:25 +00:00
}
2024-01-23 12:27:39 +00:00
func matchReferences ( imageReferences [ ] string , image string ) bool {
2023-02-07 16:51:25 +00:00
for _ , imageRef := range imageReferences {
if wildcard . Match ( imageRef , image ) {
return true
}
}
return false
}
2024-01-23 12:27:39 +00:00
func ruleStatusToImageVerificationStatus ( ruleStatus engineapi . RuleStatus ) engineapi . ImageVerificationMetadataStatus {
var imageVerificationResult engineapi . ImageVerificationMetadataStatus
switch ruleStatus {
case engineapi . RuleStatusPass :
imageVerificationResult = engineapi . ImageVerificationPass
case engineapi . RuleStatusSkip :
imageVerificationResult = engineapi . ImageVerificationSkip
case engineapi . RuleStatusWarn :
imageVerificationResult = engineapi . ImageVerificationSkip
case engineapi . RuleStatusFail :
imageVerificationResult = engineapi . ImageVerificationFail
default :
imageVerificationResult = engineapi . ImageVerificationFail
}
return imageVerificationResult
}
2023-02-07 16:51:25 +00:00
func isImageVerified ( resource unstructured . Unstructured , image string , log logr . Logger ) ( bool , error ) {
2023-03-22 04:46:35 +00:00
if resource . Object == nil {
2023-02-07 16:51:25 +00:00
return false , fmt . Errorf ( "nil resource" )
}
annotations := resource . GetAnnotations ( )
if len ( annotations ) == 0 {
return false , nil
}
2023-08-02 14:02:21 +00:00
data , ok := annotations [ kyverno . AnnotationImageVerify ]
2023-02-07 16:51:25 +00:00
if ! ok {
2023-08-02 14:02:21 +00:00
log . V ( 2 ) . Info ( "missing image metadata in annotation" , "key" , kyverno . AnnotationImageVerify )
2023-02-07 16:51:25 +00:00
return false , fmt . Errorf ( "image is not verified" )
}
ivm , err := engineapi . ParseImageMetadata ( data )
if err != nil {
log . Error ( err , "failed to parse image verification metadata" , "data" , data )
return false , fmt . Errorf ( "failed to parse image metadata: %w" , err )
}
return ivm . IsVerified ( image ) , nil
}
func ExpandStaticKeys ( attestorSet kyvernov1 . AttestorSet ) kyvernov1 . AttestorSet {
2024-05-20 09:16:35 +00:00
entries := make ( [ ] kyvernov1 . Attestor , 0 , len ( attestorSet . Entries ) )
2023-02-07 16:51:25 +00:00
for _ , e := range attestorSet . Entries {
if e . Keys != nil {
keys := splitPEM ( e . Keys . PublicKeys )
if len ( keys ) > 1 {
moreEntries := createStaticKeyAttestors ( keys )
entries = append ( entries , moreEntries ... )
continue
}
}
entries = append ( entries , e )
}
return kyvernov1 . AttestorSet {
Count : attestorSet . Count ,
Entries : entries ,
}
}
func splitPEM ( pem string ) [ ] string {
keys := strings . SplitAfter ( pem , "-----END PUBLIC KEY-----" )
if len ( keys ) < 1 {
return keys
}
return keys [ 0 : len ( keys ) - 1 ]
}
func createStaticKeyAttestors ( keys [ ] string ) [ ] kyvernov1 . Attestor {
2024-05-20 09:16:35 +00:00
attestors := make ( [ ] kyvernov1 . Attestor , 0 , len ( keys ) )
2023-02-07 16:51:25 +00:00
for _ , k := range keys {
a := kyvernov1 . Attestor {
Keys : & kyvernov1 . StaticKeyAttestor {
PublicKeys : k ,
} ,
}
attestors = append ( attestors , a )
}
return attestors
}
func buildStatementMap ( statements [ ] map [ string ] interface { } ) ( map [ string ] [ ] map [ string ] interface { } , [ ] string ) {
results := map [ string ] [ ] map [ string ] interface { } { }
2024-05-20 09:16:35 +00:00
predicateTypes := make ( [ ] string , 0 , len ( statements ) )
2023-02-07 16:51:25 +00:00
for _ , s := range statements {
2023-06-01 08:05:28 +00:00
predicateType := s [ "type" ] . ( string )
2023-02-07 16:51:25 +00:00
if results [ predicateType ] != nil {
results [ predicateType ] = append ( results [ predicateType ] , s )
} else {
results [ predicateType ] = [ ] map [ string ] interface { } { s }
}
predicateTypes = append ( predicateTypes , predicateType )
}
return results , predicateTypes
}
2023-05-13 08:56:54 +00:00
func makeAddDigestPatch ( imageInfo apiutils . ImageInfo , digest string ) jsonpatch . JsonPatchOperation {
return jsonpatch . JsonPatchOperation {
Operation : "replace" ,
Path : imageInfo . Pointer ,
Value : imageInfo . String ( ) + "@" + digest ,
}
2023-02-07 16:51:25 +00:00
}
func EvaluateConditions (
conditions [ ] kyvernov1 . AnyAllConditions ,
ctx enginecontext . Interface ,
s map [ string ] interface { } ,
log logr . Logger ,
2023-05-08 07:34:23 +00:00
) ( bool , string , error ) {
2023-02-07 16:51:25 +00:00
predicate , ok := s [ "predicate" ] . ( map [ string ] interface { } )
if ! ok {
2023-05-08 07:34:23 +00:00
return false , "" , fmt . Errorf ( "failed to extract predicate from statement: %v" , s )
2023-02-07 16:51:25 +00:00
}
if err := enginecontext . AddJSONObject ( ctx , predicate ) ; err != nil {
2023-05-08 07:34:23 +00:00
return false , "" , fmt . Errorf ( "failed to add Statement to the context %v: %w" , s , err )
2023-02-07 16:51:25 +00:00
}
c , err := variables . SubstituteAllInConditions ( log , ctx , conditions )
if err != nil {
2023-05-08 07:34:23 +00:00
return false , "" , fmt . Errorf ( "failed to substitute variables in attestation conditions: %w" , err )
2023-02-07 16:51:25 +00:00
}
2023-05-20 21:06:54 +00:00
return variables . EvaluateAnyAllConditions ( log , ctx , c )
2023-02-07 16:51:25 +00:00
}
// verify applies policy rules to each matching image. The policy rule results and annotation patches are
// added to tme imageVerifier `resp` and `ivm` fields.
func ( iv * ImageVerifier ) Verify (
ctx context . Context ,
imageVerify kyvernov1 . ImageVerification ,
matchedImageInfos [ ] apiutils . ImageInfo ,
cfg config . Configuration ,
2023-06-05 16:41:46 +00:00
) ( [ ] jsonpatch . JsonPatchOperation , [ ] * engineapi . RuleResponse ) {
2023-02-07 16:51:25 +00:00
var responses [ ] * engineapi . RuleResponse
2023-06-05 16:41:46 +00:00
var patches [ ] jsonpatch . JsonPatchOperation
2023-02-07 16:51:25 +00:00
// for backward compatibility
imageVerify = * imageVerify . Convert ( )
for _ , imageInfo := range matchedImageInfos {
image := imageInfo . String ( )
if HasImageVerifiedAnnotationChanged ( iv . policyContext , iv . logger ) {
2023-08-02 14:02:21 +00:00
msg := kyverno . AnnotationImageVerify + " annotation cannot be changed"
2023-02-07 16:51:25 +00:00
iv . logger . Info ( "image verification error" , "reason" , msg )
2023-04-05 10:35:38 +00:00
responses = append ( responses , engineapi . RuleFail ( iv . rule . Name , engineapi . ImageVerify , msg ) )
2023-02-07 16:51:25 +00:00
continue
}
pointer := jsonpointer . ParsePath ( imageInfo . Pointer ) . JMESPath ( )
changed , err := iv . policyContext . JSONContext ( ) . HasChanged ( pointer )
if err == nil && ! changed {
iv . logger . V ( 4 ) . Info ( "no change in image, skipping check" , "image" , image )
2024-01-23 12:27:39 +00:00
iv . ivm . Add ( image , engineapi . ImageVerificationPass )
2023-02-07 16:51:25 +00:00
continue
}
verified , err := isImageVerified ( iv . policyContext . NewResource ( ) , image , iv . logger )
if err == nil && verified {
iv . logger . Info ( "image was previously verified, skipping check" , "image" , image )
2024-01-23 12:27:39 +00:00
iv . ivm . Add ( image , engineapi . ImageVerificationPass )
2023-02-07 16:51:25 +00:00
continue
}
2023-08-30 07:26:40 +00:00
start := time . Now ( )
2023-09-22 10:40:16 +00:00
isInCache := false
if iv . ivCache != nil {
found , err := iv . ivCache . Get ( ctx , iv . policyContext . Policy ( ) , iv . rule . Name , image )
if err != nil {
iv . logger . Error ( err , "error occurred during cache get" )
} else {
isInCache = found
}
2023-08-30 07:26:40 +00:00
}
2023-02-07 16:51:25 +00:00
2023-08-30 07:26:40 +00:00
var ruleResp * engineapi . RuleResponse
var digest string
2023-09-22 10:40:16 +00:00
if isInCache {
2023-08-30 07:26:40 +00:00
iv . logger . V ( 2 ) . Info ( "cache entry found" , "namespace" , iv . policyContext . Policy ( ) . GetNamespace ( ) , "policy" , iv . policyContext . Policy ( ) . GetName ( ) , "ruleName" , iv . rule . Name , "imageRef" , image )
ruleResp = engineapi . RulePass ( iv . rule . Name , engineapi . ImageVerify , "verified from cache" )
digest = imageInfo . Digest
} else {
iv . logger . V ( 2 ) . Info ( "cache entry not found" , "namespace" , iv . policyContext . Policy ( ) . GetNamespace ( ) , "policy" , iv . policyContext . Policy ( ) . GetName ( ) , "ruleName" , iv . rule . Name , "imageRef" , image )
ruleResp , digest = iv . verifyImage ( ctx , imageVerify , imageInfo , cfg )
if ruleResp != nil && ruleResp . Status ( ) == engineapi . RuleStatusPass {
2023-09-22 10:40:16 +00:00
if iv . ivCache != nil {
setted , err := iv . ivCache . Set ( ctx , iv . policyContext . Policy ( ) , iv . rule . Name , image )
if err != nil {
iv . logger . Error ( err , "error occurred during cache set" )
} else {
if setted {
iv . logger . V ( 4 ) . Info ( "successfully set cache" , "namespace" , iv . policyContext . Policy ( ) . GetNamespace ( ) , "policy" , iv . policyContext . Policy ( ) . GetName ( ) , "ruleName" , iv . rule . Name , "imageRef" , image )
}
2023-08-30 07:26:40 +00:00
}
}
}
}
iv . logger . V ( 4 ) . Info ( "time taken by the image verify operation" , "duration" , time . Since ( start ) )
2023-02-07 16:51:25 +00:00
if imageVerify . MutateDigest {
patch , retrievedDigest , err := iv . handleMutateDigest ( ctx , digest , imageInfo )
if err != nil {
2023-04-05 10:35:38 +00:00
responses = append ( responses , engineapi . RuleError ( iv . rule . Name , engineapi . ImageVerify , "failed to update digest" , err ) )
2023-02-07 16:51:25 +00:00
} else if patch != nil {
if ruleResp == nil {
2023-04-05 10:35:38 +00:00
ruleResp = engineapi . RulePass ( iv . rule . Name , engineapi . ImageVerify , "mutated image digest" )
2023-02-07 16:51:25 +00:00
}
2023-06-05 16:41:46 +00:00
patches = append ( patches , * patch )
2023-02-07 16:51:25 +00:00
imageInfo . Digest = retrievedDigest
image = imageInfo . String ( )
}
}
if ruleResp != nil {
if len ( imageVerify . Attestors ) > 0 || len ( imageVerify . Attestations ) > 0 {
2024-01-23 12:27:39 +00:00
iv . ivm . Add ( image , ruleStatusToImageVerificationStatus ( ruleResp . Status ( ) ) )
2023-02-07 16:51:25 +00:00
}
responses = append ( responses , ruleResp )
}
}
2023-06-05 16:41:46 +00:00
return patches , responses
2023-02-07 16:51:25 +00:00
}
func ( iv * ImageVerifier ) verifyImage (
ctx context . Context ,
imageVerify kyvernov1 . ImageVerification ,
imageInfo apiutils . ImageInfo ,
cfg config . Configuration ,
) ( * engineapi . RuleResponse , string ) {
if len ( imageVerify . Attestors ) <= 0 && len ( imageVerify . Attestations ) <= 0 {
return nil , ""
}
image := imageInfo . String ( )
2023-06-01 08:05:28 +00:00
for _ , att := range imageVerify . Attestations {
if att . Type == "" && att . PredicateType != "" {
att . Type = att . PredicateType
}
}
2023-02-07 16:51:25 +00:00
iv . logger . V ( 2 ) . Info ( "verifying image signatures" , "image" , image , "attestors" , len ( imageVerify . Attestors ) , "attestations" , len ( imageVerify . Attestations ) )
if err := iv . policyContext . JSONContext ( ) . AddImageInfo ( imageInfo , cfg ) ; err != nil {
iv . logger . Error ( err , "failed to add image to context" )
2023-04-05 10:35:38 +00:00
return engineapi . RuleError ( iv . rule . Name , engineapi . ImageVerify , fmt . Sprintf ( "failed to add image to context %s" , image ) , err ) , ""
2023-02-07 16:51:25 +00:00
}
if len ( imageVerify . Attestors ) > 0 {
2024-01-23 12:27:39 +00:00
if ! matchReferences ( imageVerify . ImageReferences , image ) {
return engineapi . RuleSkip ( iv . rule . Name , engineapi . ImageVerify , fmt . Sprintf ( "skipping image reference image %s, policy %s ruleName %s" , image , iv . policyContext . Policy ( ) . GetName ( ) , iv . rule . Name ) ) , ""
}
if matchReferences ( imageVerify . SkipImageReferences , image ) {
iv . logger . Info ( "skipping image reference" , "image" , image , "policy" , iv . policyContext . Policy ( ) . GetName ( ) , "ruleName" , iv . rule . Name )
iv . ivm . Add ( image , engineapi . ImageVerificationSkip )
return engineapi . RuleSkip ( iv . rule . Name , engineapi . ImageVerify , fmt . Sprintf ( "skipping image reference image %s, policy %s ruleName %s" , image , iv . policyContext . Policy ( ) . GetName ( ) , iv . rule . Name ) ) . WithEmitWarning ( true ) , ""
2023-02-07 16:51:25 +00:00
}
2024-05-29 23:29:24 +00:00
ruleResp , cosignResp := iv . verifyAttestors ( ctx , imageVerify . Attestors , imageVerify , imageInfo )
2023-04-05 10:35:38 +00:00
if ruleResp . Status ( ) != engineapi . RuleStatusPass {
2023-02-07 16:51:25 +00:00
return ruleResp , ""
}
if imageInfo . Digest == "" {
imageInfo . Digest = cosignResp . Digest
}
if len ( imageVerify . Attestations ) == 0 {
return ruleResp , cosignResp . Digest
}
}
2023-05-16 05:30:07 +00:00
2023-02-07 16:51:25 +00:00
return iv . verifyAttestations ( ctx , imageVerify , imageInfo )
}
func ( iv * ImageVerifier ) verifyAttestors (
ctx context . Context ,
attestors [ ] kyvernov1 . AttestorSet ,
imageVerify kyvernov1 . ImageVerification ,
imageInfo apiutils . ImageInfo ,
2023-02-20 16:26:10 +00:00
) ( * engineapi . RuleResponse , * images . Response ) {
var cosignResponse * images . Response
2023-02-07 16:51:25 +00:00
image := imageInfo . String ( )
for i , attestorSet := range attestors {
var err error
path := fmt . Sprintf ( ".attestors[%d]" , i )
iv . logger . V ( 4 ) . Info ( "verifying attestors" , "path" , path )
cosignResponse , err = iv . verifyAttestorSet ( ctx , attestorSet , imageVerify , imageInfo , path )
if err != nil {
iv . logger . Error ( err , "failed to verify image" )
return iv . handleRegistryErrors ( image , err ) , nil
}
}
if cosignResponse == nil {
2023-04-05 10:35:38 +00:00
return engineapi . RuleError ( iv . rule . Name , engineapi . ImageVerify , "invalid response" , fmt . Errorf ( "nil" ) ) , nil
2023-02-07 16:51:25 +00:00
}
msg := fmt . Sprintf ( "verified image signatures for %s" , image )
2023-04-05 10:35:38 +00:00
return engineapi . RulePass ( iv . rule . Name , engineapi . ImageVerify , msg ) , cosignResponse
2023-02-07 16:51:25 +00:00
}
// handle registry network errors as a rule error (instead of a policy failure)
func ( iv * ImageVerifier ) handleRegistryErrors ( image string , err error ) * engineapi . RuleResponse {
msg := fmt . Sprintf ( "failed to verify image %s: %s" , image , err . Error ( ) )
var netErr * net . OpError
if errors . As ( err , & netErr ) {
2023-04-05 10:35:38 +00:00
return engineapi . RuleError ( iv . rule . Name , engineapi . ImageVerify , fmt . Sprintf ( "failed to verify image %s" , image ) , err )
2023-02-07 16:51:25 +00:00
}
2023-04-05 10:35:38 +00:00
return engineapi . RuleFail ( iv . rule . Name , engineapi . ImageVerify , msg )
2023-02-07 16:51:25 +00:00
}
func ( iv * ImageVerifier ) verifyAttestations (
ctx context . Context ,
imageVerify kyvernov1 . ImageVerification ,
imageInfo apiutils . ImageInfo ,
) ( * engineapi . RuleResponse , string ) {
image := imageInfo . String ( )
for i , attestation := range imageVerify . Attestations {
2024-01-22 06:49:22 +00:00
var errorList [ ] error
2023-02-07 16:51:25 +00:00
path := fmt . Sprintf ( ".attestations[%d]" , i )
2023-06-01 08:05:28 +00:00
iv . logger . V ( 2 ) . Info ( fmt . Sprintf ( "attestation %+v" , attestation ) )
if attestation . Type == "" && attestation . PredicateType == "" {
return engineapi . RuleFail ( iv . rule . Name , engineapi . ImageVerify , path + ": missing type" ) , ""
}
if attestation . Type == "" && attestation . PredicateType != "" {
attestation . Type = attestation . PredicateType
2023-02-07 16:51:25 +00:00
}
if len ( attestation . Attestors ) == 0 {
// add an empty attestor to allow fetching and checking attestations
attestation . Attestors = [ ] kyvernov1 . AttestorSet { { Entries : [ ] kyvernov1 . Attestor { { } } } }
}
for j , attestor := range attestation . Attestors {
attestorPath := fmt . Sprintf ( "%s.attestors[%d]" , path , j )
requiredCount := attestor . RequiredCount ( )
verifiedCount := 0
for _ , a := range attestor . Entries {
2024-01-22 06:49:22 +00:00
var attestationError error
2023-02-07 16:51:25 +00:00
entryPath := fmt . Sprintf ( "%s.entries[%d]" , attestorPath , i )
2023-02-20 16:26:10 +00:00
v , opts , subPath := iv . buildVerifier ( a , imageVerify , image , & imageVerify . Attestations [ i ] )
cosignResp , err := v . FetchAttestations ( ctx , * opts )
2023-02-07 16:51:25 +00:00
if err != nil {
iv . logger . Error ( err , "failed to fetch attestations" )
2024-01-22 06:49:22 +00:00
errorList = append ( errorList , err )
continue
2023-02-07 16:51:25 +00:00
}
if imageInfo . Digest == "" {
imageInfo . Digest = cosignResp . Digest
image = imageInfo . String ( )
}
attestationError = iv . verifyAttestation ( cosignResp . Statements , attestation , imageInfo )
2024-01-22 06:49:22 +00:00
if attestationError == nil {
verifiedCount ++
if verifiedCount >= requiredCount {
iv . logger . V ( 2 ) . Info ( "image attestations verification succeeded" , "verifiedCount" , verifiedCount , "requiredCount" , requiredCount )
break
}
} else {
attestationError = fmt . Errorf ( "%s: %w" , entryPath + subPath , attestationError )
iv . logger . Error ( attestationError , "image attestation verification failed" )
errorList = append ( errorList , attestationError )
2023-02-07 16:51:25 +00:00
}
}
2024-01-22 06:49:22 +00:00
err := multierr . Combine ( errorList ... )
errMsg := "attestations verification failed"
if err != nil {
errMsg = err . Error ( )
}
2023-02-07 16:51:25 +00:00
if verifiedCount < requiredCount {
2024-01-22 06:49:22 +00:00
msg := fmt . Sprintf ( "image attestations verification failed, verifiedCount: %v, requiredCount: %v, error: %s" , verifiedCount , requiredCount , errMsg )
2023-04-05 10:35:38 +00:00
return engineapi . RuleFail ( iv . rule . Name , engineapi . ImageVerify , msg ) , ""
2023-02-07 16:51:25 +00:00
}
}
2023-06-01 08:05:28 +00:00
iv . logger . V ( 4 ) . Info ( "attestation checks passed" , "path" , path , "image" , imageInfo . String ( ) , "type" , attestation . Type )
2023-02-07 16:51:25 +00:00
}
msg := fmt . Sprintf ( "verified image attestations for %s" , image )
iv . logger . V ( 2 ) . Info ( msg )
2023-04-05 10:35:38 +00:00
return engineapi . RulePass ( iv . rule . Name , engineapi . ImageVerify , msg ) , imageInfo . Digest
2023-02-07 16:51:25 +00:00
}
func ( iv * ImageVerifier ) verifyAttestorSet (
ctx context . Context ,
attestorSet kyvernov1 . AttestorSet ,
imageVerify kyvernov1 . ImageVerification ,
imageInfo apiutils . ImageInfo ,
path string ,
2023-02-20 16:26:10 +00:00
) ( * images . Response , error ) {
2023-02-07 16:51:25 +00:00
var errorList [ ] error
verifiedCount := 0
attestorSet = ExpandStaticKeys ( attestorSet )
requiredCount := attestorSet . RequiredCount ( )
image := imageInfo . String ( )
for i , a := range attestorSet . Entries {
var entryError error
2023-02-20 16:26:10 +00:00
var cosignResp * images . Response
2023-02-07 16:51:25 +00:00
attestorPath := fmt . Sprintf ( "%s.entries[%d]" , path , i )
iv . logger . V ( 4 ) . Info ( "verifying attestorSet" , "path" , attestorPath )
if a . Attestor != nil {
nestedAttestorSet , err := kyvernov1 . AttestorSetUnmarshal ( a . Attestor )
if err != nil {
entryError = fmt . Errorf ( "failed to unmarshal nested attestor %s: %w" , attestorPath , err )
} else {
attestorPath += ".attestor"
cosignResp , entryError = iv . verifyAttestorSet ( ctx , * nestedAttestorSet , imageVerify , imageInfo , attestorPath )
}
} else {
2023-02-20 16:26:10 +00:00
v , opts , subPath := iv . buildVerifier ( a , imageVerify , image , nil )
cosignResp , entryError = v . VerifySignature ( ctx , * opts )
2023-02-07 16:51:25 +00:00
if entryError != nil {
entryError = fmt . Errorf ( "%s: %w" , attestorPath + subPath , entryError )
}
}
if entryError == nil {
verifiedCount ++
if verifiedCount >= requiredCount {
iv . logger . V ( 2 ) . Info ( "image attestors verification succeeded" , "verifiedCount" , verifiedCount , "requiredCount" , requiredCount )
return cosignResp , nil
}
} else {
errorList = append ( errorList , entryError )
}
}
err := multierr . Combine ( errorList ... )
iv . logger . Info ( "image attestors verification failed" , "verifiedCount" , verifiedCount , "requiredCount" , requiredCount , "errors" , err . Error ( ) )
return nil , err
}
2023-02-20 16:26:10 +00:00
func ( iv * ImageVerifier ) buildVerifier (
attestor kyvernov1 . Attestor ,
imageVerify kyvernov1 . ImageVerification ,
image string ,
attestation * kyvernov1 . Attestation ,
) ( images . ImageVerifier , * images . Options , string ) {
switch imageVerify . Type {
2023-06-01 08:05:28 +00:00
case kyvernov1 . Notary :
2024-05-29 23:29:24 +00:00
return iv . buildNotaryVerifier ( attestor , image , attestation )
2023-02-20 16:26:10 +00:00
default :
return iv . buildCosignVerifier ( attestor , imageVerify , image , attestation )
}
}
func ( iv * ImageVerifier ) buildCosignVerifier (
attestor kyvernov1 . Attestor ,
imageVerify kyvernov1 . ImageVerification ,
image string ,
attestation * kyvernov1 . Attestation ,
) ( images . ImageVerifier , * images . Options , string ) {
2023-02-07 16:51:25 +00:00
path := ""
2023-02-20 16:26:10 +00:00
opts := & images . Options {
2023-06-14 16:19:38 +00:00
ImageRef : image ,
2024-02-08 12:10:29 +00:00
Repository : imageVerify . Repository ,
2023-06-14 16:19:38 +00:00
Annotations : imageVerify . Annotations ,
Client : iv . rclient ,
2023-02-07 16:51:25 +00:00
}
2023-02-20 16:26:10 +00:00
2023-02-07 16:51:25 +00:00
if imageVerify . Roots != "" {
opts . Roots = imageVerify . Roots
}
2023-02-20 16:26:10 +00:00
2023-02-07 16:51:25 +00:00
if attestation != nil {
opts . PredicateType = attestation . PredicateType
2023-06-01 08:05:28 +00:00
opts . Type = attestation . Type
2023-08-15 14:25:55 +00:00
opts . IgnoreSCT = true // TODO: Add option to allow SCT when attestors are not provided
2023-06-01 08:05:28 +00:00
if attestation . PredicateType != "" && attestation . Type == "" {
iv . logger . Info ( "predicate type has been deprecated, please use type instead" )
opts . Type = attestation . PredicateType
}
2023-02-07 16:51:25 +00:00
opts . FetchAttestations = true
}
2023-02-20 16:26:10 +00:00
2023-02-07 16:51:25 +00:00
if attestor . Keys != nil {
path = path + ".keys"
if attestor . Keys . PublicKeys != "" {
opts . Key = attestor . Keys . PublicKeys
} else if attestor . Keys . Secret != nil {
2023-02-20 16:26:10 +00:00
opts . Key = fmt . Sprintf ( "k8s://%s/%s" , attestor . Keys . Secret . Namespace , attestor . Keys . Secret . Name )
2023-02-07 16:51:25 +00:00
} else if attestor . Keys . KMS != "" {
opts . Key = attestor . Keys . KMS
}
if attestor . Keys . Rekor != nil {
opts . RekorURL = attestor . Keys . Rekor . URL
2023-08-15 14:25:55 +00:00
opts . RekorPubKey = attestor . Keys . Rekor . RekorPubKey
opts . IgnoreTlog = attestor . Keys . Rekor . IgnoreTlog
} else {
opts . RekorURL = "https://rekor.sigstore.dev"
opts . IgnoreTlog = false
2023-02-07 16:51:25 +00:00
}
2023-08-30 06:39:49 +00:00
if attestor . Keys . CTLog != nil {
opts . IgnoreSCT = attestor . Keys . CTLog . IgnoreSCT
2023-09-14 04:18:44 +00:00
opts . CTLogsPubKey = attestor . Keys . CTLog . CTLogPubKey
2024-03-30 20:50:07 +00:00
opts . TSACertChain = attestor . Keys . CTLog . TSACertChain
2023-08-30 06:39:49 +00:00
} else {
opts . IgnoreSCT = false
}
2023-09-14 04:18:44 +00:00
2023-02-07 16:51:25 +00:00
opts . SignatureAlgorithm = attestor . Keys . SignatureAlgorithm
} else if attestor . Certificates != nil {
path = path + ".certificates"
opts . Cert = attestor . Certificates . Certificate
opts . CertChain = attestor . Certificates . CertificateChain
if attestor . Certificates . Rekor != nil {
opts . RekorURL = attestor . Certificates . Rekor . URL
2024-03-29 01:17:24 +00:00
opts . RekorPubKey = attestor . Certificates . Rekor . RekorPubKey
opts . IgnoreTlog = attestor . Certificates . Rekor . IgnoreTlog
} else {
opts . RekorURL = "https://rekor.sigstore.dev"
opts . IgnoreTlog = false
}
if attestor . Certificates . CTLog != nil {
opts . IgnoreSCT = attestor . Certificates . CTLog . IgnoreSCT
opts . CTLogsPubKey = attestor . Certificates . CTLog . CTLogPubKey
2024-03-30 20:50:07 +00:00
opts . TSACertChain = attestor . Certificates . CTLog . TSACertChain
2024-03-29 01:17:24 +00:00
} else {
opts . IgnoreSCT = false
2023-02-07 16:51:25 +00:00
}
} else if attestor . Keyless != nil {
path = path + ".keyless"
if attestor . Keyless . Rekor != nil {
opts . RekorURL = attestor . Keyless . Rekor . URL
2023-08-15 14:25:55 +00:00
opts . RekorPubKey = attestor . Keyless . Rekor . RekorPubKey
opts . IgnoreTlog = attestor . Keyless . Rekor . IgnoreTlog
} else {
opts . RekorURL = "https://rekor.sigstore.dev"
opts . IgnoreTlog = false
2023-02-07 16:51:25 +00:00
}
2023-02-20 16:26:10 +00:00
2023-08-30 06:39:49 +00:00
if attestor . Keyless . CTLog != nil {
opts . IgnoreSCT = attestor . Keyless . CTLog . IgnoreSCT
2023-09-14 04:18:44 +00:00
opts . CTLogsPubKey = attestor . Keyless . CTLog . CTLogPubKey
2024-03-30 20:50:07 +00:00
opts . TSACertChain = attestor . Keyless . CTLog . TSACertChain
2023-08-30 06:39:49 +00:00
} else {
opts . IgnoreSCT = false
}
2023-02-07 16:51:25 +00:00
opts . Roots = attestor . Keyless . Roots
opts . Issuer = attestor . Keyless . Issuer
opts . Subject = attestor . Keyless . Subject
opts . AdditionalExtensions = attestor . Keyless . AdditionalExtensions
}
2023-02-20 16:26:10 +00:00
2023-02-07 16:51:25 +00:00
if attestor . Repository != "" {
opts . Repository = attestor . Repository
}
2023-02-20 16:26:10 +00:00
2023-02-07 16:51:25 +00:00
if attestor . Annotations != nil {
opts . Annotations = attestor . Annotations
}
2023-02-20 16:26:10 +00:00
2024-03-29 01:17:24 +00:00
iv . logger . V ( 4 ) . Info ( "cosign verifier built" , "ignoreTlog" , opts . IgnoreTlog , "ignoreSCT" , opts . IgnoreSCT )
2023-02-20 16:26:10 +00:00
return cosign . NewVerifier ( ) , opts , path
}
2023-06-01 08:05:28 +00:00
func ( iv * ImageVerifier ) buildNotaryVerifier (
2023-02-20 16:26:10 +00:00
attestor kyvernov1 . Attestor ,
image string ,
2023-06-01 08:05:28 +00:00
attestation * kyvernov1 . Attestation ,
2023-02-20 16:26:10 +00:00
) ( images . ImageVerifier , * images . Options , string ) {
path := ""
opts := & images . Options {
2023-06-14 16:19:38 +00:00
ImageRef : image ,
Cert : attestor . Certificates . Certificate ,
CertChain : attestor . Certificates . CertificateChain ,
Client : iv . rclient ,
2023-02-20 16:26:10 +00:00
}
2023-06-01 08:05:28 +00:00
if attestation != nil {
opts . Type = attestation . Type
opts . PredicateType = attestation . PredicateType
if attestation . PredicateType != "" && attestation . Type == "" {
iv . logger . Info ( "predicate type has been deprecated, please use type instead" )
opts . Type = attestation . PredicateType
}
opts . FetchAttestations = true
}
if attestor . Repository != "" {
opts . Repository = attestor . Repository
}
if attestor . Annotations != nil {
opts . Annotations = attestor . Annotations
}
return notary . NewVerifier ( ) , opts , path
2023-02-07 16:51:25 +00:00
}
func ( iv * ImageVerifier ) verifyAttestation ( statements [ ] map [ string ] interface { } , attestation kyvernov1 . Attestation , imageInfo apiutils . ImageInfo ) error {
2023-06-01 08:05:28 +00:00
if attestation . Type == "" && attestation . PredicateType == "" {
return fmt . Errorf ( "a type is required" )
2023-02-07 16:51:25 +00:00
}
image := imageInfo . String ( )
statementsByPredicate , types := buildStatementMap ( statements )
iv . logger . V ( 4 ) . Info ( "checking attestations" , "predicates" , types , "image" , image )
2023-06-01 08:05:28 +00:00
statements = statementsByPredicate [ attestation . Type ]
2023-02-07 16:51:25 +00:00
if statements == nil {
2023-06-01 08:05:28 +00:00
iv . logger . Info ( "no attestations found for predicate" , "type" , attestation . Type , "predicates" , types , "image" , imageInfo . String ( ) )
return fmt . Errorf ( "attestions not found for predicate type %s" , attestation . Type )
2023-02-07 16:51:25 +00:00
}
for _ , s := range statements {
iv . logger . Info ( "checking attestation" , "predicates" , types , "image" , imageInfo . String ( ) )
2023-05-08 07:34:23 +00:00
val , msg , err := iv . checkAttestations ( attestation , s )
2023-02-07 16:51:25 +00:00
if err != nil {
return fmt . Errorf ( "failed to check attestations: %w" , err )
}
if ! val {
2023-06-01 08:05:28 +00:00
return fmt . Errorf ( "attestation checks failed for %s and predicate %s: %s" , imageInfo . String ( ) , attestation . Type , msg )
2023-02-07 16:51:25 +00:00
}
}
return nil
}
2023-05-08 07:34:23 +00:00
func ( iv * ImageVerifier ) checkAttestations ( a kyvernov1 . Attestation , s map [ string ] interface { } ) ( bool , string , error ) {
2023-02-07 16:51:25 +00:00
if len ( a . Conditions ) == 0 {
2023-05-08 07:34:23 +00:00
return true , "" , nil
2023-02-07 16:51:25 +00:00
}
iv . policyContext . JSONContext ( ) . Checkpoint ( )
defer iv . policyContext . JSONContext ( ) . Restore ( )
return EvaluateConditions ( a . Conditions , iv . policyContext . JSONContext ( ) , s , iv . logger )
}
2023-05-13 08:56:54 +00:00
func ( iv * ImageVerifier ) handleMutateDigest ( ctx context . Context , digest string , imageInfo apiutils . ImageInfo ) ( * jsonpatch . JsonPatchOperation , string , error ) {
2023-02-07 16:51:25 +00:00
if imageInfo . Digest != "" {
return nil , "" , nil
}
if digest == "" {
desc , err := iv . rclient . FetchImageDescriptor ( ctx , imageInfo . String ( ) )
if err != nil {
return nil , "" , err
}
digest = desc . Digest . String ( )
}
2023-05-13 08:56:54 +00:00
patch := makeAddDigestPatch ( imageInfo , digest )
iv . logger . V ( 4 ) . Info ( "adding digest patch" , "image" , imageInfo . String ( ) , "patch" , patch . Json ( ) )
return & patch , digest , nil
2023-02-07 16:51:25 +00:00
}