Add verify_http_pgp_signature Rego builtin for HTTP sources using pgpsign with checksum-request/response flow through policy resolution. Wire sourcemeta HTTP checksum request/response conversion and add tests. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
694 lines
19 KiB
Go
694 lines
19 KiB
Go
package policy
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"maps"
|
|
"net/url"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/distribution/reference"
|
|
"github.com/golang/snappy"
|
|
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
|
|
"github.com/moby/buildkit/client/llb"
|
|
gwclient "github.com/moby/buildkit/frontend/gateway/client"
|
|
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
|
"github.com/moby/buildkit/solver/pb"
|
|
"github.com/moby/buildkit/util/gitutil/gitsign"
|
|
"github.com/moby/buildkit/util/pgpsign"
|
|
"github.com/open-policy-agent/opa/v1/ast"
|
|
"github.com/open-policy-agent/opa/v1/rego"
|
|
"github.com/open-policy-agent/opa/v1/types"
|
|
"github.com/opencontainers/go-digest"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
funcLoadJSON = "load_json"
|
|
funcVerifyGitSignature = "verify_git_signature"
|
|
funcVerifyHTTPPGPSignature = "verify_http_pgp_signature"
|
|
funcPinImage = "pin_image"
|
|
funcArtifactAttestation = "artifact_attestation"
|
|
funcGithubAttestation = "github_attestation"
|
|
)
|
|
|
|
func (p *Policy) initBuiltinFuncs() {
|
|
builtinLoadJSON := ®o.Function{
|
|
Name: funcLoadJSON,
|
|
Decl: types.NewFunction(
|
|
types.Args(
|
|
types.S,
|
|
),
|
|
types.A,
|
|
),
|
|
Memoize: true,
|
|
}
|
|
p.funcs = append(p.funcs, fun{
|
|
decl: builtinLoadJSON,
|
|
impl: funcNoInput(rego.Function1(builtinLoadJSON, p.builtinLoadJSONImpl)),
|
|
})
|
|
|
|
verifyGitSignature := ®o.Function{
|
|
Name: funcVerifyGitSignature,
|
|
Decl: types.NewFunction(
|
|
types.Args(
|
|
types.A,
|
|
types.S,
|
|
),
|
|
types.B,
|
|
),
|
|
Memoize: false, // TODO:optimize
|
|
}
|
|
p.funcs = append(p.funcs, fun{
|
|
decl: verifyGitSignature,
|
|
impl: func(s *state) func(*rego.Rego) {
|
|
return rego.Function2(verifyGitSignature, func(bctx rego.BuiltinContext, a1 *ast.Term, a2 *ast.Term) (*ast.Term, error) {
|
|
return p.builtinVerifyGitSignatureImpl(bctx, a1, a2, s)
|
|
})
|
|
},
|
|
})
|
|
|
|
verifyHTTPPGPSignature := ®o.Function{
|
|
Name: funcVerifyHTTPPGPSignature,
|
|
Decl: types.NewFunction(
|
|
types.Args(
|
|
types.A,
|
|
types.S,
|
|
types.S,
|
|
),
|
|
types.B,
|
|
),
|
|
Memoize: false, // TODO:optimize
|
|
}
|
|
p.funcs = append(p.funcs, fun{
|
|
decl: verifyHTTPPGPSignature,
|
|
impl: func(s *state) func(*rego.Rego) {
|
|
return rego.Function3(verifyHTTPPGPSignature, func(bctx rego.BuiltinContext, a1 *ast.Term, a2 *ast.Term, a3 *ast.Term) (*ast.Term, error) {
|
|
return p.builtinVerifyHTTPPGPSignatureImpl(bctx, a1, a2, a3, s)
|
|
})
|
|
},
|
|
})
|
|
|
|
pinImageDigest := ®o.Function{
|
|
Name: funcPinImage,
|
|
Decl: types.NewFunction(
|
|
types.Args(
|
|
types.A,
|
|
types.S,
|
|
),
|
|
types.B,
|
|
),
|
|
Memoize: false, // TODO: optimize
|
|
}
|
|
|
|
p.funcs = append(p.funcs, fun{
|
|
decl: pinImageDigest,
|
|
impl: func(s *state) func(*rego.Rego) {
|
|
return rego.Function2(pinImageDigest, func(bctx rego.BuiltinContext, a1 *ast.Term, a2 *ast.Term) (*ast.Term, error) {
|
|
return p.builtinPinImageImpl(bctx, a1, a2, s)
|
|
})
|
|
},
|
|
})
|
|
|
|
artifactAttestation := ®o.Function{
|
|
Name: funcArtifactAttestation,
|
|
Decl: types.NewFunction(
|
|
types.Args(
|
|
types.A,
|
|
types.S,
|
|
),
|
|
types.A,
|
|
),
|
|
Memoize: false,
|
|
}
|
|
p.funcs = append(p.funcs, fun{
|
|
decl: artifactAttestation,
|
|
impl: func(s *state) func(*rego.Rego) {
|
|
return rego.Function2(artifactAttestation, func(bctx rego.BuiltinContext, a1 *ast.Term, a2 *ast.Term) (*ast.Term, error) {
|
|
return p.builtinArtifactAttestationImpl(bctx, a1, a2, s)
|
|
})
|
|
},
|
|
})
|
|
|
|
githubAttestation := ®o.Function{
|
|
Name: funcGithubAttestation,
|
|
Decl: types.NewFunction(
|
|
types.Args(
|
|
types.A,
|
|
types.S,
|
|
),
|
|
types.A,
|
|
),
|
|
Memoize: false,
|
|
}
|
|
p.funcs = append(p.funcs, fun{
|
|
decl: githubAttestation,
|
|
impl: func(s *state) func(*rego.Rego) {
|
|
return rego.Function2(githubAttestation, func(bctx rego.BuiltinContext, a1 *ast.Term, a2 *ast.Term) (*ast.Term, error) {
|
|
return p.builtinGithubAttestationImpl(bctx, a1, a2, s)
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
func (p *Policy) builtinGithubAttestationImpl(bctx rego.BuiltinContext, a1, a2 *ast.Term, s *state) (*ast.Term, error) {
|
|
inp := s.Input
|
|
if inp.HTTP == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
obja, ok := a1.Value.(ast.Object)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected object, got %T", funcGithubAttestation, a1.Value)
|
|
}
|
|
|
|
httpValue, err := ast.InterfaceToValue(inp.HTTP)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: failed converting object to interface", funcGithubAttestation)
|
|
}
|
|
|
|
if obja.Compare(httpValue) != 0 {
|
|
return nil, errors.Errorf("%s: first argument is not the same as input http", funcGithubAttestation)
|
|
}
|
|
|
|
repo, ok := a2.Value.(ast.String)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected repository name string, got %T", funcGithubAttestation, a2.Value)
|
|
}
|
|
|
|
if inp.HTTP.Checksum == "" {
|
|
s.addUnknown(funcGithubAttestation)
|
|
return nil, nil
|
|
}
|
|
|
|
if p.opt.SourceResolver == nil {
|
|
return nil, errors.Errorf("%s: source resolver is not configured", funcGithubAttestation)
|
|
}
|
|
if p.opt.VerifierProvider == nil {
|
|
return nil, errors.Errorf("%s: policy verifier is not configured", funcGithubAttestation)
|
|
}
|
|
|
|
dgst, err := digest.Parse(inp.HTTP.Checksum)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: invalid checksum", funcGithubAttestation)
|
|
}
|
|
|
|
v, err := p.opt.VerifierProvider()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: getting policy verifier", funcGithubAttestation)
|
|
}
|
|
|
|
bundles, err := p.readGitHubAttestationBundles(bctx.Context, string(repo), dgst)
|
|
if err != nil {
|
|
p.log(logrus.InfoLevel, "%s: failed reading bundles for %s@%s: %v", funcGithubAttestation, repo, dgst, err)
|
|
return nil, nil
|
|
}
|
|
if len(bundles) == 0 {
|
|
p.log(logrus.InfoLevel, "%s: no bundle found for %s@%s", funcGithubAttestation, repo, dgst)
|
|
return nil, nil
|
|
}
|
|
|
|
for _, bundleBytes := range bundles {
|
|
siRaw, err := v.VerifyArtifact(bctx.Context, dgst, bundleBytes)
|
|
if err != nil {
|
|
p.log(logrus.InfoLevel, "%s: failed verifying bundle for %s@%s: %v", funcGithubAttestation, repo, dgst, err)
|
|
continue
|
|
}
|
|
si := toAttestationSignature(siRaw)
|
|
astVal, err := ast.InterfaceToValue(si)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: failed converting verification result", funcGithubAttestation)
|
|
}
|
|
return ast.NewTerm(astVal), nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (p *Policy) readGitHubAttestationBundles(ctx context.Context, repo string, dgst digest.Digest) ([][]byte, error) {
|
|
const (
|
|
attestationFilename = "attestation.json"
|
|
bundleFilename = "bundle.json"
|
|
)
|
|
|
|
u := fmt.Sprintf(
|
|
"https://api.github.com/repos/%s/attestations/%s?predicate_type=%s",
|
|
repo,
|
|
dgst.String(),
|
|
url.QueryEscape(slsa1.PredicateSLSAProvenance),
|
|
)
|
|
st := llb.HTTP(
|
|
u,
|
|
llb.Filename(attestationFilename),
|
|
llb.WithCustomNamef("[policy] fetch GitHub attestation %s@%s", repo, dgst.String()),
|
|
llb.Header(llb.HTTPHeader{
|
|
Accept: "application/vnd.github+json",
|
|
}),
|
|
)
|
|
ref, err := p.opt.SourceResolver.ResolveState(ctx, st)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "resolve GitHub attestation request")
|
|
}
|
|
|
|
raw, err := ref.ReadFile(ctx, gwclient.ReadRequest{Filename: attestationFilename})
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "read GitHub attestation response")
|
|
}
|
|
|
|
bundles, bundleURLs := githubAttestationBundlesFromResponse(raw)
|
|
p.log(logrus.InfoLevel, "%s: fetched %d inline bundles and %d bundle URLs from %s", funcGithubAttestation, len(bundles), len(bundleURLs), u)
|
|
for _, bu := range bundleURLs {
|
|
// bundle_url is a signed URL to the bundle payload.
|
|
st := llb.HTTP(
|
|
bu,
|
|
llb.Filename(bundleFilename),
|
|
llb.WithCustomNamef("[policy] fetch GitHub attestation bundle %s", stripRawQuery(bu)),
|
|
)
|
|
ref, err := p.opt.SourceResolver.ResolveState(ctx, st)
|
|
if err != nil {
|
|
p.log(logrus.InfoLevel, "%s: failed fetching bundle_url %s: %v", funcGithubAttestation, bu, err)
|
|
continue
|
|
}
|
|
bundleRaw, err := ref.ReadFile(ctx, gwclient.ReadRequest{Filename: bundleFilename})
|
|
if err != nil {
|
|
p.log(logrus.InfoLevel, "%s: failed reading bundle_url %s: %v", funcGithubAttestation, bu, err)
|
|
continue
|
|
}
|
|
bundleRaw = bytes.TrimSpace(bundleRaw)
|
|
if len(bundleRaw) == 0 || bytes.Equal(bundleRaw, []byte("null")) {
|
|
continue
|
|
}
|
|
if shouldDecodeSnappyBundleURL(bu) {
|
|
decoded, err := snappy.Decode(nil, bundleRaw)
|
|
if err != nil {
|
|
p.log(logrus.InfoLevel, "%s: failed decoding snappy bundle_url %s: %v", funcGithubAttestation, bu, err)
|
|
continue
|
|
}
|
|
bundleRaw = decoded
|
|
}
|
|
bundles = append(bundles, bundleRaw)
|
|
}
|
|
return bundles, nil
|
|
}
|
|
|
|
func githubAttestationBundlesFromResponse(raw []byte) ([][]byte, []string) {
|
|
t := bytes.TrimSpace(raw)
|
|
if len(t) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var parsed struct {
|
|
Attestations []struct {
|
|
Bundle json.RawMessage `json:"bundle"`
|
|
BundleURL string `json:"bundle_url"`
|
|
} `json:"attestations"`
|
|
}
|
|
if err := json.Unmarshal(t, &parsed); err != nil {
|
|
return nil, nil
|
|
}
|
|
out := make([][]byte, 0, len(parsed.Attestations))
|
|
bundleURLs := make([]string, 0, len(parsed.Attestations))
|
|
appendCandidate := func(dt []byte) {
|
|
dt = bytes.TrimSpace(dt)
|
|
if len(dt) == 0 || bytes.Equal(dt, []byte("null")) {
|
|
return
|
|
}
|
|
out = append(out, dt)
|
|
}
|
|
for _, a := range parsed.Attestations {
|
|
appendCandidate(a.Bundle)
|
|
if a.BundleURL != "" {
|
|
bundleURLs = append(bundleURLs, a.BundleURL)
|
|
}
|
|
}
|
|
return out, bundleURLs
|
|
}
|
|
|
|
func shouldDecodeSnappyBundleURL(rawURL string) bool {
|
|
u, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return strings.HasSuffix(u.Path, ".json.sn")
|
|
}
|
|
|
|
func stripRawQuery(rawURL string) string {
|
|
u, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return rawURL
|
|
}
|
|
u.RawQuery = ""
|
|
return u.String()
|
|
}
|
|
|
|
func (p *Policy) builtinArtifactAttestationImpl(bctx rego.BuiltinContext, a1, a2 *ast.Term, s *state) (*ast.Term, error) {
|
|
inp := s.Input
|
|
if inp.HTTP == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
obja, ok := a1.Value.(ast.Object)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected object, got %T", funcArtifactAttestation, a1.Value)
|
|
}
|
|
|
|
httpValue, err := ast.InterfaceToValue(inp.HTTP)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: failed converting object to interface", funcArtifactAttestation)
|
|
}
|
|
|
|
if obja.Compare(httpValue) != 0 {
|
|
return nil, errors.Errorf("%s: first argument is not the same as input http", funcArtifactAttestation)
|
|
}
|
|
|
|
path, ok := a2.Value.(ast.String)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected string path, got %T", funcArtifactAttestation, a2.Value)
|
|
}
|
|
|
|
if inp.HTTP.Checksum == "" {
|
|
s.addUnknown(funcArtifactAttestation)
|
|
return nil, nil
|
|
}
|
|
|
|
dgst, err := digest.Parse(inp.HTTP.Checksum)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: invalid checksum", funcArtifactAttestation)
|
|
}
|
|
|
|
bundleBytes, err := p.readFile(string(path), 8*1024*1024)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if p.opt.VerifierProvider == nil {
|
|
return nil, errors.Errorf("%s: policy verifier is not configured", funcArtifactAttestation)
|
|
}
|
|
v, err := p.opt.VerifierProvider()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: getting policy verifier", funcArtifactAttestation)
|
|
}
|
|
|
|
siRaw, err := v.VerifyArtifact(bctx.Context, dgst, bundleBytes)
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
|
|
si := toAttestationSignature(siRaw)
|
|
astVal, err := ast.InterfaceToValue(si)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: failed converting verification result", funcArtifactAttestation)
|
|
}
|
|
|
|
return ast.NewTerm(astVal), nil
|
|
}
|
|
|
|
func (p *Policy) builtinPinImageImpl(_ rego.BuiltinContext, a1, a2 *ast.Term, s *state) (*ast.Term, error) {
|
|
inp := s.Input
|
|
if inp.Image == nil {
|
|
return ast.BooleanTerm(false), nil
|
|
}
|
|
|
|
obja, ok := a1.Value.(ast.Object)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected object, got %T", funcPinImage, a1.Value)
|
|
}
|
|
|
|
imageValue, err := ast.InterfaceToValue(inp.Image)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: failed converting object to interface", funcPinImage)
|
|
}
|
|
|
|
if obja.Compare(imageValue) != 0 {
|
|
return nil, errors.Errorf("%s: first argument is not the same as input image", funcPinImage)
|
|
}
|
|
|
|
dgstStr, ok := a2.Value.(ast.String)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected string path, got %T", funcPinImage, a2.Value)
|
|
}
|
|
|
|
dgst, err := digest.Parse(string(dgstStr))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: invalid digest", funcPinImage)
|
|
}
|
|
|
|
if inp.Image.Checksum == string(dgst) {
|
|
return ast.BooleanTerm(true), nil
|
|
}
|
|
|
|
if s.ImagePins == nil {
|
|
s.ImagePins = make(map[digest.Digest]struct{})
|
|
}
|
|
s.ImagePins[dgst] = struct{}{}
|
|
|
|
return ast.BooleanTerm(true), nil
|
|
}
|
|
|
|
func (p *Policy) builtinVerifyGitSignatureImpl(_ rego.BuiltinContext, a1, a2 *ast.Term, s *state) (*ast.Term, error) {
|
|
inp := s.Input
|
|
if inp.Git == nil {
|
|
return ast.BooleanTerm(false), nil
|
|
}
|
|
|
|
if inp.Git.Commit == nil {
|
|
s.addUnknown(funcVerifyGitSignature)
|
|
return ast.BooleanTerm(false), nil
|
|
}
|
|
|
|
obja, ok := a1.Value.(ast.Object)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected object, got %T", funcVerifyGitSignature, a1.Value)
|
|
}
|
|
|
|
commitValue, err := ast.InterfaceToValue(inp.Git.Commit)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: failed converting object to interface", funcVerifyGitSignature)
|
|
}
|
|
isCommit := obja.Compare(commitValue) == 0
|
|
|
|
var isTag bool
|
|
if !isCommit && inp.Git.Tag != nil {
|
|
tagValue, err := ast.InterfaceToValue(inp.Git.Tag)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: failed converting object to interface", funcVerifyGitSignature)
|
|
}
|
|
isTag = obja.Compare(tagValue) == 0
|
|
}
|
|
|
|
if !isCommit && !isTag {
|
|
return nil, errors.Errorf("%s: object is neither commit nor tag", funcVerifyGitSignature)
|
|
}
|
|
|
|
path, ok := a2.Value.(ast.String)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected string path, got %T", funcVerifyGitSignature, a2.Value)
|
|
}
|
|
|
|
pubkey, err := p.readFile(string(path), 128*1024)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
obj := inp.Git.Commit.obj
|
|
if isTag {
|
|
obj = inp.Git.Tag.obj
|
|
}
|
|
|
|
if err := gitsign.VerifySignature(obj, pubkey, nil); err != nil {
|
|
return nil, errors.Wrapf(err, "%s: verification failes", funcVerifyGitSignature)
|
|
}
|
|
|
|
return ast.BooleanTerm(true), nil
|
|
}
|
|
|
|
func (p *Policy) builtinVerifyHTTPPGPSignatureImpl(_ rego.BuiltinContext, a1, a2, a3 *ast.Term, s *state) (*ast.Term, error) {
|
|
inp := s.Input
|
|
if inp.HTTP == nil {
|
|
return ast.BooleanTerm(false), nil
|
|
}
|
|
|
|
obja, ok := a1.Value.(ast.Object)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected object, got %T", funcVerifyHTTPPGPSignature, a1.Value)
|
|
}
|
|
|
|
httpValue, err := ast.InterfaceToValue(inp.HTTP)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: failed converting object to interface", funcVerifyHTTPPGPSignature)
|
|
}
|
|
|
|
if obja.Compare(httpValue) != 0 {
|
|
return nil, errors.Errorf("%s: first argument is not the same as input http", funcVerifyHTTPPGPSignature)
|
|
}
|
|
|
|
sigPath, ok := a2.Value.(ast.String)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected string signature path, got %T", funcVerifyHTTPPGPSignature, a2.Value)
|
|
}
|
|
pubKeyPath, ok := a3.Value.(ast.String)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected string pubkey path, got %T", funcVerifyHTTPPGPSignature, a3.Value)
|
|
}
|
|
|
|
signatureData, err := p.readFile(string(sigPath), 512*1024)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pubKeyData, err := p.readFile(string(pubKeyPath), 512*1024)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sig, _, err := pgpsign.ParseArmoredDetachedSignature(signatureData)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: failed to parse detached signature", funcVerifyHTTPPGPSignature)
|
|
}
|
|
keyring, err := pgpsign.ReadAllArmoredKeyRings(pubKeyData)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: failed to read armored keyring", funcVerifyHTTPPGPSignature)
|
|
}
|
|
|
|
algo, err := toPBChecksumAlgo(sig.Hash)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: unsupported signature hash", funcVerifyHTTPPGPSignature)
|
|
}
|
|
suffix := slices.Clone(sig.HashSuffix)
|
|
checksumReq := &gwpb.ChecksumRequest{Algo: algo, Suffix: suffix}
|
|
|
|
resp := inp.HTTP.checksumResponseForSignature
|
|
if resp == nil || resp.Digest == "" {
|
|
s.checksumNeededForSignature = checksumReq
|
|
s.addUnknown(funcVerifyHTTPPGPSignature)
|
|
return ast.BooleanTerm(false), nil
|
|
}
|
|
if !bytes.Equal(resp.Suffix, suffix) {
|
|
s.checksumNeededForSignature = checksumReq
|
|
s.addUnknown(funcVerifyHTTPPGPSignature)
|
|
return ast.BooleanTerm(false), nil
|
|
}
|
|
dgst, err := digest.Parse(resp.Digest)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: invalid checksum digest", funcVerifyHTTPPGPSignature)
|
|
}
|
|
if !checksumAlgoMatches(algo, dgst.Algorithm()) {
|
|
s.checksumNeededForSignature = checksumReq
|
|
s.addUnknown(funcVerifyHTTPPGPSignature)
|
|
return ast.BooleanTerm(false), nil
|
|
}
|
|
|
|
if err := pgpsign.VerifySignatureWithDigest(sig, keyring, dgst); err != nil {
|
|
return ast.BooleanTerm(false), nil
|
|
}
|
|
return ast.BooleanTerm(true), nil
|
|
}
|
|
|
|
func toPBChecksumAlgo(hash crypto.Hash) (gwpb.ChecksumRequest_ChecksumAlgo, error) {
|
|
switch hash {
|
|
case crypto.SHA256:
|
|
return gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA256, nil
|
|
case crypto.SHA384:
|
|
return gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA384, nil
|
|
case crypto.SHA512:
|
|
return gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA512, nil
|
|
default:
|
|
return gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA256, errors.Errorf("unsupported signature hash algorithm %v", hash)
|
|
}
|
|
}
|
|
|
|
func checksumAlgoMatches(algo gwpb.ChecksumRequest_ChecksumAlgo, digestAlgo digest.Algorithm) bool {
|
|
switch algo {
|
|
case gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA256:
|
|
return digestAlgo == digest.SHA256
|
|
case gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA384:
|
|
return digestAlgo == digest.SHA384
|
|
case gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA512:
|
|
return digestAlgo == digest.SHA512
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (p *Policy) readFile(path string, limit int64) ([]byte, error) {
|
|
if p.opt.FS == nil {
|
|
return nil, errors.Errorf("no policy FS defined for reading context files")
|
|
}
|
|
root, closeFS, err := p.opt.FS()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get policy FS for reading context files")
|
|
}
|
|
if closeFS != nil {
|
|
defer closeFS()
|
|
}
|
|
|
|
f, err := root.Open(path)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed opening file %q", path)
|
|
}
|
|
defer f.Close()
|
|
|
|
rdr := io.LimitReader(f, limit)
|
|
data, err := io.ReadAll(rdr)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed reading %q", path)
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func (p *Policy) builtinLoadJSONImpl(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) {
|
|
path, ok := a.Value.(ast.String)
|
|
if !ok {
|
|
return nil, errors.Errorf("%s: expected string path, got %T", funcLoadJSON, a.Value)
|
|
}
|
|
|
|
data, err := p.readFile(string(path), 4*1024*1024)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var v any
|
|
if err := json.Unmarshal(data, &v); err != nil {
|
|
return nil, errors.Wrapf(err, "%s: invalid JSON in %q", funcLoadJSON, path)
|
|
}
|
|
|
|
astVal, err := ast.InterfaceToValue(v)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "%s: failed converting JSON from %q", funcLoadJSON, path)
|
|
}
|
|
|
|
return ast.NewTerm(astVal), nil
|
|
}
|
|
|
|
func addPinToImage(src *pb.SourceOp, dgst digest.Digest) (*pb.SourceOp, error) {
|
|
id, ok := strings.CutPrefix(src.Identifier, "docker-image://")
|
|
if !ok {
|
|
return nil, errors.Errorf("cannot pin non-image source: %q", src.Identifier)
|
|
}
|
|
|
|
ref, err := reference.ParseNormalizedNamed(id)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed parsing image reference %q", id)
|
|
}
|
|
|
|
newRef, err := reference.WithDigest(ref, dgst)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed adding digest to image reference %q", id)
|
|
}
|
|
attrs := maps.Clone(src.Attrs)
|
|
if attrs == nil {
|
|
attrs = make(map[string]string)
|
|
}
|
|
attrs["image.checksum"] = dgst.String()
|
|
|
|
return &pb.SourceOp{
|
|
Identifier: "docker-image://" + newRef.String(),
|
|
Attrs: attrs,
|
|
}, nil
|
|
}
|