package policy import ( "bytes" "context" "encoding/json" "path/filepath" "sync" "github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/v2/core/remotes" cerrderfs "github.com/containerd/errdefs" "github.com/docker/buildx/util/confutil" gwpb "github.com/moby/buildkit/frontend/gateway/pb" policyverifier "github.com/moby/policy-helpers" policyimage "github.com/moby/policy-helpers/image" policytypes "github.com/moby/policy-helpers/types" "github.com/opencontainers/go-digest" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) type PolicyVerifier interface { VerifyImage(context.Context, policyimage.ReferrersProvider, ocispecs.Descriptor, *ocispecs.Platform) (*policytypes.SignatureInfo, error) VerifyArtifact(context.Context, digest.Digest, []byte, ...policyverifier.ArtifactVerifyOpt) (*policytypes.SignatureInfo, error) } type PolicyVerifierProvider func() (PolicyVerifier, error) func SignatureVerifier(cfg *confutil.Config) PolicyVerifierProvider { if cfg == nil { return nil } var ( mu sync.Mutex v PolicyVerifier ) return func() (PolicyVerifier, error) { mu.Lock() defer mu.Unlock() if v != nil { return v, nil } root := cfg.Dir() confDir := filepath.Join(root, "policy") if err := cfg.MkdirAll("policy/tuf", 0o755); err != nil { return nil, errors.Wrapf(err, "failed to create policy verifier config dir") } nv, err := policyverifier.NewVerifier(policyverifier.Config{ StateDir: confDir, }) if err != nil { return nil, errors.Wrapf(err, "failed to create policy verifier") } v = nv return v, nil } } func parseSignatures(ctx context.Context, getVerifier PolicyVerifierProvider, ac *gwpb.AttestationChain, platform *ocispecs.Platform) ([]AttestationSignature, error) { if ac.Root == "" || ac.AttestationManifest == "" || len(ac.SignatureManifests) == 0 { return nil, nil } root, err := digest.Parse(ac.Root) if err != nil { return nil, err } att, err := digest.Parse(ac.AttestationManifest) if err != nil { return nil, err } sigs := make([]digest.Digest, 0, len(ac.SignatureManifests)) for _, sm := range ac.SignatureManifests { d, err := digest.Parse(sm) if err != nil { return nil, err } sigs = append(sigs, d) } acp := &acProvider{ blobs: ac.Blobs, signatures: sigs, att: att, } rootBlob, ok := ac.Blobs[root.String()] if !ok { return nil, errors.Errorf("root blob %s not found", root) } desc := toOCIDescriptor(rootBlob.Descriptor_) if desc.MediaType != ocispecs.MediaTypeImageIndex { return nil, nil } sc, err := policyimage.ResolveSignatureChain(ctx, acp, desc, platform) if err != nil { return nil, errors.Wrapf(err, "resolving signature chain for image %s", desc.Digest) } if sc.AttestationManifest == nil || sc.SignatureManifest == nil { return nil, nil } if sc.AttestationManifest.Digest != att { return nil, errors.Errorf("attestation manifest digest mismatch: expected %s, got %s", att, sc.AttestationManifest.Digest) } if getVerifier == nil { return nil, errors.New("policy verifier is not configured") } v, err := getVerifier() if err != nil { return nil, errors.Wrapf(err, "getting policy verifier") } siRaw, err := v.VerifyImage(ctx, acp, desc, platform) if err != nil { return nil, errors.Wrapf(err, "verifying image signatures") } return []AttestationSignature{toAttestationSignature(siRaw)}, nil } func toAttestationSignature(siRaw *policytypes.SignatureInfo) AttestationSignature { if siRaw == nil { return AttestationSignature{} } si := AttestationSignature{ raw: siRaw, Timestamps: siRaw.Timestamps, IsDHI: siRaw.IsDHI, DockerReference: siRaw.DockerReference, SignatureType: toSignatureType(siRaw.SignatureType), SignatureKind: toSignatureKind(siRaw.Kind), } if siRaw.Signer != nil { si.Signer = &SignerInfo{ CertificateIssuer: siRaw.Signer.CertificateIssuer, SubjectAlternativeName: siRaw.Signer.SubjectAlternativeName, Issuer: siRaw.Signer.Issuer, BuildSignerURI: siRaw.Signer.BuildSignerURI, BuildSignerDigest: siRaw.Signer.BuildSignerDigest, RunnerEnvironment: siRaw.Signer.RunnerEnvironment, SourceRepositoryURI: siRaw.Signer.SourceRepositoryURI, SourceRepositoryDigest: siRaw.Signer.SourceRepositoryDigest, SourceRepositoryRef: siRaw.Signer.SourceRepositoryRef, SourceRepositoryIdentifier: siRaw.Signer.SourceRepositoryIdentifier, SourceRepositoryOwnerURI: siRaw.Signer.SourceRepositoryOwnerURI, SourceRepositoryOwnerIdentifier: siRaw.Signer.SourceRepositoryOwnerIdentifier, BuildConfigURI: siRaw.Signer.BuildConfigURI, BuildConfigDigest: siRaw.Signer.BuildConfigDigest, BuildTrigger: siRaw.Signer.BuildTrigger, RunInvocationURI: siRaw.Signer.RunInvocationURI, SourceRepositoryVisibilityAtSigning: siRaw.Signer.SourceRepositoryVisibilityAtSigning, } } return si } type acProvider struct { blobs map[string]*gwpb.Blob signatures []digest.Digest att digest.Digest } var _ policyimage.ReferrersProvider = &acProvider{} func (p *acProvider) FetchReferrers(ctx context.Context, dgst digest.Digest, opts ...remotes.FetchReferrersOpt) ([]ocispecs.Descriptor, error) { if dgst != p.att { return nil, nil } out := make([]ocispecs.Descriptor, 0, len(p.signatures)) for _, d := range p.signatures { b, ok := p.blobs[d.String()] if !ok { continue } desc := toOCIDescriptor(b.Descriptor_) var mfst ocispecs.Manifest if err := json.Unmarshal(b.Data, &mfst); err != nil { return nil, errors.Wrapf(err, "unmarshal signature manifest %s", d) } desc.ArtifactType = mfst.ArtifactType // on image manifest assume legacy format if desc.ArtifactType == "" { desc.ArtifactType = policyimage.ArtifactTypeCosignSignature } out = append(out, desc) } return out, nil } func (p *acProvider) ReaderAt(ctx context.Context, desc ocispecs.Descriptor) (content.ReaderAt, error) { b, ok := p.blobs[desc.Digest.String()] if !ok { return nil, errors.WithStack(cerrderfs.ErrNotFound) } return &readerAt{buf: bytes.NewReader(b.Data)}, nil } type readerAt struct { buf *bytes.Reader } var _ content.ReaderAt = &readerAt{} func (r *readerAt) ReadAt(p []byte, off int64) (n int, err error) { return r.buf.ReadAt(p, off) } func (r *readerAt) Size() int64 { return int64(r.buf.Len()) } func (r *readerAt) Close() error { return nil } func toOCIDescriptor(d *gwpb.Descriptor) ocispecs.Descriptor { return ocispecs.Descriptor{ MediaType: d.MediaType, Digest: digest.Digest(d.Digest), Size: d.Size, } }