(ctx context.Context, digest string)
| 166 | } |
| 167 | |
| 168 | func (b *Backend) Describe(ctx context.Context, digest string) (*pb.CASResource, error) { |
| 169 | key, err := b.keyFor(ctx, digest) |
| 170 | if err != nil { |
| 171 | return nil, err |
| 172 | } |
| 173 | resp, err := b.s3Client.HeadObject(ctx, &s3.HeadObjectInput{ |
| 174 | Bucket: aws.String(b.creds.AccessPointARN), |
| 175 | Key: aws.String(key), |
| 176 | ChecksumMode: s3types.ChecksumModeEnabled, |
| 177 | }) |
| 178 | if err != nil { |
| 179 | var apiErr smithy.APIError |
| 180 | if errors.As(err, &apiErr) && apiErr.ErrorCode() == "NotFound" { |
| 181 | return nil, backend.NewErrNotFound("artifact") |
| 182 | } |
| 183 | return nil, fmt.Errorf("reading from access point: %w", err) |
| 184 | } |
| 185 | |
| 186 | // Integrity check: when S3 returned a checksum, make sure the digest |
| 187 | // the caller asked for matches the server's recorded value. |
| 188 | if resp.ChecksumSHA256 != nil && *resp.ChecksumSHA256 != hexSha256ToBinaryB64(digest) { |
| 189 | return nil, fmt.Errorf("failed to validate integrity of object, got=%s, want=%s", |
| 190 | *resp.ChecksumSHA256, hexSha256ToBinaryB64(digest)) |
| 191 | } |
| 192 | |
| 193 | author, ok := resp.Metadata[annotationNameAuthor] |
| 194 | if !ok || author != backend.AuthorAnnotation { |
| 195 | return nil, errors.New("asset not uploaded by Chainloop") |
| 196 | } |
| 197 | fileName, ok := resp.Metadata[annotationNameFilename] |
| 198 | if !ok { |
| 199 | return nil, errors.New("couldn't find file metadata") |
| 200 | } |
| 201 | |
| 202 | var size int64 |
| 203 | if resp.ContentLength != nil { |
| 204 | size = *resp.ContentLength |
| 205 | } |
| 206 | return &pb.CASResource{FileName: fileName, Size: size, Digest: digest}, nil |
| 207 | } |
| 208 | |
| 209 | func (b *Backend) Download(ctx context.Context, w io.Writer, digest string) error { |
| 210 | exists, err := b.Exists(ctx, digest) |
no test coverage detected