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