zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/extensions/imagetrust/image_trust.go (about) 1 //go:build imagetrust 2 // +build imagetrust 3 4 package imagetrust 5 6 import ( 7 "context" 8 "time" 9 10 "github.com/aws/aws-sdk-go-v2/aws" 11 "github.com/aws/aws-sdk-go-v2/aws/transport/http" 12 "github.com/aws/aws-sdk-go-v2/config" 13 "github.com/aws/aws-sdk-go-v2/service/secretsmanager" 14 "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" 15 aws1 "github.com/aws/aws-sdk-go/aws" 16 "github.com/aws/aws-sdk-go/aws/endpoints" 17 "github.com/aws/aws-sdk-go/aws/session" 18 smanager "github.com/aws/aws-sdk-go/service/secretsmanager" 19 "github.com/aws/aws-secretsmanager-caching-go/secretcache" 20 smithy "github.com/aws/smithy-go" 21 godigest "github.com/opencontainers/go-digest" 22 ispec "github.com/opencontainers/image-spec/specs-go/v1" 23 24 zerr "zotregistry.io/zot/errors" 25 zcommon "zotregistry.io/zot/pkg/common" 26 "zotregistry.io/zot/pkg/log" 27 mTypes "zotregistry.io/zot/pkg/meta/types" 28 "zotregistry.io/zot/pkg/scheduler" 29 ) 30 31 const ( 32 defaultDirPerms = 0o700 33 defaultFilePerms = 0o644 34 ) 35 36 type ImageTrustStore struct { 37 CosignStorage publicKeyStorage 38 NotationStorage certificateStorage 39 } 40 41 type SecretsManagerClient interface { 42 CreateSecret(ctx context.Context, params *secretsmanager.CreateSecretInput, 43 optFns ...func(*secretsmanager.Options)) (*secretsmanager.CreateSecretOutput, error) 44 DeleteSecret(ctx context.Context, params *secretsmanager.DeleteSecretInput, 45 optFns ...func(*secretsmanager.Options)) (*secretsmanager.DeleteSecretOutput, error) 46 ListSecrets(ctx context.Context, params *secretsmanager.ListSecretsInput, 47 optFns ...func(*secretsmanager.Options)) (*secretsmanager.ListSecretsOutput, error) 48 } 49 50 type SecretsManagerCache interface { 51 GetSecretString(secretID string) (string, error) 52 } 53 54 func NewLocalImageTrustStore(rootDir string) (*ImageTrustStore, error) { 55 publicKeyStorage, err := NewPublicKeyLocalStorage(rootDir) 56 if err != nil { 57 return nil, err 58 } 59 60 certStorage, err := NewCertificateLocalStorage(rootDir) 61 if err != nil { 62 return nil, err 63 } 64 65 return &ImageTrustStore{ 66 CosignStorage: publicKeyStorage, 67 NotationStorage: certStorage, 68 }, nil 69 } 70 71 func NewAWSImageTrustStore(region, endpoint string) (*ImageTrustStore, error) { 72 secretsManagerClient, err := GetSecretsManagerClient(region, endpoint) 73 if err != nil { 74 return nil, err 75 } 76 77 secretsManagerCache := GetSecretsManagerRetrieval(region, endpoint) 78 79 publicKeyStorage := NewPublicKeyAWSStorage(secretsManagerClient, secretsManagerCache) 80 81 certStorage, err := NewCertificateAWSStorage(secretsManagerClient, secretsManagerCache) 82 if err != nil { 83 return nil, err 84 } 85 86 return &ImageTrustStore{ 87 CosignStorage: publicKeyStorage, 88 NotationStorage: certStorage, 89 }, nil 90 } 91 92 func GetSecretsManagerClient(region, endpoint string) (*secretsmanager.Client, error) { 93 customResolver := aws.EndpointResolverWithOptionsFunc( 94 func(service, region string, options ...interface{}) (aws.Endpoint, error) { 95 return aws.Endpoint{ 96 PartitionID: "aws", 97 URL: endpoint, 98 SigningRegion: region, 99 }, nil 100 }) 101 102 // Using the SDK's default configuration, loading additional config 103 // and credentials values from the environment variables, shared 104 // credentials, and shared configuration files 105 cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region), 106 config.WithEndpointResolverWithOptions(customResolver)) 107 if err != nil { 108 return nil, err 109 } 110 111 return secretsmanager.NewFromConfig(cfg), nil 112 } 113 114 func GetSecretsManagerRetrieval(region, endpoint string) *secretcache.Cache { 115 endpointFunc := func(service, region string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) { 116 return endpoints.ResolvedEndpoint{ 117 PartitionID: "aws", 118 URL: endpoint, 119 SigningRegion: region, 120 }, nil 121 } 122 customResolver := endpoints.ResolverFunc(endpointFunc) 123 124 cfg := aws1.NewConfig().WithRegion(region).WithEndpointResolver(customResolver) 125 126 newSession := session.Must(session.NewSession()) 127 128 client := smanager.New(newSession, cfg) 129 // Create a custom CacheConfig struct 130 config := secretcache.CacheConfig{ 131 MaxCacheSize: secretcache.DefaultMaxCacheSize, 132 VersionStage: secretcache.DefaultVersionStage, 133 CacheItemTTL: secretcache.DefaultCacheItemTTL, 134 } 135 136 // Instantiate the cache 137 cache, _ := secretcache.New( 138 func(c *secretcache.Cache) { c.CacheConfig = config }, 139 func(c *secretcache.Cache) { c.Client = client }, 140 ) 141 142 return cache 143 } 144 145 func IsResourceExistsException(err error) bool { 146 if opErr, ok := err.(*smithy.OperationError); ok { //nolint: errorlint 147 if resErr, ok := opErr.Err.(*http.ResponseError); ok { //nolint: errorlint 148 if _, ok := resErr.Err.(*types.ResourceExistsException); ok { //nolint: errorlint 149 return true 150 } 151 } 152 153 return false 154 } 155 156 return false 157 } 158 159 func (imgTrustStore *ImageTrustStore) VerifySignature( 160 signatureType string, rawSignature []byte, sigKey string, manifestDigest godigest.Digest, imageMeta mTypes.ImageMeta, 161 repo string, 162 ) (string, time.Time, bool, error) { 163 desc := ispec.Descriptor{ 164 MediaType: imageMeta.MediaType, 165 Digest: imageMeta.Digest, 166 Size: imageMeta.Size, 167 } 168 169 if manifestDigest.String() == "" { 170 return "", time.Time{}, false, zerr.ErrBadManifestDigest 171 } 172 173 switch signatureType { 174 case zcommon.CosignSignature: 175 author, isValid, err := VerifyCosignSignature(imgTrustStore.CosignStorage, repo, manifestDigest, sigKey, rawSignature) 176 177 return author, time.Time{}, isValid, err 178 case zcommon.NotationSignature: 179 return VerifyNotationSignature(imgTrustStore.NotationStorage, desc, manifestDigest.String(), rawSignature, sigKey) 180 default: 181 return "", time.Time{}, false, zerr.ErrInvalidSignatureType 182 } 183 } 184 185 func NewTaskGenerator(metaDB mTypes.MetaDB, log log.Logger) scheduler.TaskGenerator { 186 return &sigValidityTaskGenerator{ 187 repos: []mTypes.RepoMeta{}, 188 metaDB: metaDB, 189 repoIndex: -1, 190 log: log, 191 } 192 } 193 194 type sigValidityTaskGenerator struct { 195 repos []mTypes.RepoMeta 196 metaDB mTypes.MetaDB 197 repoIndex int 198 done bool 199 log log.Logger 200 } 201 202 func (gen *sigValidityTaskGenerator) Next() (scheduler.Task, error) { 203 if len(gen.repos) == 0 { 204 ctx := context.Background() 205 206 repos, err := gen.metaDB.GetMultipleRepoMeta(ctx, func(repoMeta mTypes.RepoMeta) bool { 207 return true 208 }) 209 if err != nil { 210 return nil, err 211 } 212 213 gen.repos = repos 214 } 215 216 gen.repoIndex++ 217 218 if gen.repoIndex >= len(gen.repos) { 219 gen.done = true 220 221 gen.log.Info().Msg("finished generating tasks for updating signatures validity") 222 223 return nil, nil 224 } 225 226 return NewValidityTask(gen.metaDB, gen.repos[gen.repoIndex], gen.log), nil 227 } 228 229 func (gen *sigValidityTaskGenerator) IsDone() bool { 230 return gen.done 231 } 232 233 func (gen *sigValidityTaskGenerator) IsReady() bool { 234 return true 235 } 236 237 func (gen *sigValidityTaskGenerator) Reset() { 238 gen.done = false 239 gen.repoIndex = -1 240 gen.repos = []mTypes.RepoMeta{} 241 242 gen.log.Info().Msg("finished resetting task generator for updating signatures validity") 243 } 244 245 type validityTask struct { 246 metaDB mTypes.MetaDB 247 repo mTypes.RepoMeta 248 log log.Logger 249 } 250 251 func NewValidityTask(metaDB mTypes.MetaDB, repo mTypes.RepoMeta, log log.Logger) *validityTask { 252 return &validityTask{metaDB, repo, log} 253 } 254 255 func (validityT *validityTask) DoWork(ctx context.Context) error { 256 validityT.log.Info().Msg("update signatures validity") 257 258 for signedManifest, sigs := range validityT.repo.Signatures { 259 if zcommon.IsContextDone(ctx) { 260 return ctx.Err() 261 } 262 263 if len(sigs[zcommon.CosignSignature]) != 0 || len(sigs[zcommon.NotationSignature]) != 0 { 264 err := validityT.metaDB.UpdateSignaturesValidity(ctx, validityT.repo.Name, godigest.Digest(signedManifest)) 265 if err != nil { 266 validityT.log.Info().Msg("error while verifying signatures") 267 268 return err 269 } 270 } 271 } 272 273 validityT.log.Info().Msg("update signatures validity completed") 274 275 return nil 276 }