From fe875836c86a40d9ce27bac99ede4a495931f3bd Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Fri, 27 Feb 2026 12:06:58 -0800 Subject: [PATCH] policy: add HTTP PGP signature verification builtin 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 --- go.mod | 2 +- policy/funcs.go | 143 ++++++++++++++++++++++++- policy/funcs_http_pgp_test.go | 183 ++++++++++++++++++++++++++++++++ policy/resolve_test.go | 39 +++++++ policy/types.go | 8 +- policy/utils_test.go | 7 +- policy/validate.go | 34 +++++- util/sourcemeta/convert.go | 25 +++++ util/sourcemeta/convert_test.go | 43 ++++++++ 9 files changed, 468 insertions(+), 16 deletions(-) create mode 100644 policy/funcs_http_pgp_test.go create mode 100644 util/sourcemeta/convert_test.go diff --git a/go.mod b/go.mod index dde099306fb1..6fcfad669508 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.25.5 require ( github.com/Masterminds/semver/v3 v3.4.0 github.com/Microsoft/go-winio v0.6.2 + github.com/ProtonMail/go-crypto v1.3.0 github.com/aws/aws-sdk-go-v2/config v1.32.7 github.com/compose-spec/compose-go/v2 v2.9.1 github.com/containerd/console v1.0.5 @@ -78,7 +79,6 @@ require ( require ( github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect - github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/agnivade/levenshtein v1.2.1 // indirect github.com/apparentlymart/go-cidr v1.0.1 // indirect diff --git a/policy/funcs.go b/policy/funcs.go index 4d5d46525fdf..3593504661ee 100644 --- a/policy/funcs.go +++ b/policy/funcs.go @@ -3,11 +3,13 @@ package policy import ( "bytes" "context" + "crypto" "encoding/json" "fmt" "io" "maps" "net/url" + "slices" "strings" "github.com/distribution/reference" @@ -15,8 +17,10 @@ import ( 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" @@ -26,11 +30,12 @@ import ( ) const ( - funcLoadJSON = "load_json" - funcVerifyGitSignature = "verify_git_signature" - funcPinImage = "pin_image" - funcArtifactAttestation = "artifact_attestation" - funcGithubAttestation = "github_attestation" + 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() { @@ -69,6 +74,27 @@ func (p *Policy) initBuiltinFuncs() { }, }) + 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( @@ -482,6 +508,113 @@ func (p *Policy) builtinVerifyGitSignatureImpl(_ rego.BuiltinContext, a1, a2 *as 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") diff --git a/policy/funcs_http_pgp_test.go b/policy/funcs_http_pgp_test.go new file mode 100644 index 000000000000..0916a34daade --- /dev/null +++ b/policy/funcs_http_pgp_test.go @@ -0,0 +1,183 @@ +package policy + +import ( + "bytes" + "crypto" + "encoding/hex" + "io/fs" + "testing" + "testing/fstest" + + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/moby/buildkit/util/pgpsign" + "github.com/open-policy-agent/opa/v1/ast" + "github.com/open-policy-agent/opa/v1/rego" + digest "github.com/opencontainers/go-digest" + "github.com/stretchr/testify/require" +) + +func TestBuiltinVerifyHTTPPGPSignatureImpl(t *testing.T) { + const ( + sigPath = "sig.asc" + keyPath = "pubkey.asc" + ) + payload := []byte("buildx-http-payload") + sigData, pubKeyData, checksumDigest, suffix := createDetachedPGPFixture(t, payload) + + newPolicy := func(sig []byte, pub []byte) *Policy { + return NewPolicy(Opt{ + FS: func() (fs.StatFS, func() error, error) { + return fstest.MapFS{ + sigPath: &fstest.MapFile{Data: sig}, + keyPath: &fstest.MapFile{Data: pub}, + }, func() error { return nil }, nil + }, + }) + } + + t.Run("success", func(t *testing.T) { + st := &state{ + Input: Input{ + HTTP: &HTTP{ + checksumResponseForSignature: &httpChecksumResponseForSignature{ + Digest: checksumDigest.String(), + Suffix: append([]byte(nil), suffix...), + }, + }, + }, + } + p := newPolicy(sigData, pubKeyData) + httpVal, err := ast.InterfaceToValue(st.Input.HTTP) + require.NoError(t, err) + + got, err := p.builtinVerifyHTTPPGPSignatureImpl( + rego.BuiltinContext{Context: t.Context()}, + ast.NewTerm(httpVal), + ast.StringTerm(sigPath), + ast.StringTerm(keyPath), + st, + ) + require.NoError(t, err) + require.Equal(t, ast.BooleanTerm(true), got) + require.Nil(t, st.checksumNeededForSignature) + }) + + t.Run("verify-failure-returns-false", func(t *testing.T) { + encoded := checksumDigest.Encoded() + require.NotEmpty(t, encoded) + flipped := "0" + encoded[1:] + badDigest := digest.NewDigestFromEncoded(checksumDigest.Algorithm(), flipped) + + st := &state{ + Input: Input{ + HTTP: &HTTP{ + checksumResponseForSignature: &httpChecksumResponseForSignature{ + Digest: badDigest.String(), + Suffix: append([]byte(nil), suffix...), + }, + }, + }, + } + p := newPolicy(sigData, pubKeyData) + httpVal, err := ast.InterfaceToValue(st.Input.HTTP) + require.NoError(t, err) + + got, err := p.builtinVerifyHTTPPGPSignatureImpl( + rego.BuiltinContext{Context: t.Context()}, + ast.NewTerm(httpVal), + ast.StringTerm(sigPath), + ast.StringTerm(keyPath), + st, + ) + require.NoError(t, err) + require.Equal(t, ast.BooleanTerm(false), got) + }) + + t.Run("missing-checksum-response-returns-false-and-adds-unknown", func(t *testing.T) { + st := &state{ + Input: Input{ + HTTP: &HTTP{}, + }, + } + p := newPolicy(sigData, pubKeyData) + httpVal, err := ast.InterfaceToValue(st.Input.HTTP) + require.NoError(t, err) + + got, err := p.builtinVerifyHTTPPGPSignatureImpl( + rego.BuiltinContext{Context: t.Context()}, + ast.NewTerm(httpVal), + ast.StringTerm(sigPath), + ast.StringTerm(keyPath), + st, + ) + require.NoError(t, err) + require.Equal(t, ast.BooleanTerm(false), got) + require.Contains(t, st.Unknowns, funcVerifyHTTPPGPSignature) + require.NotNil(t, st.checksumNeededForSignature) + }) + + t.Run("invalid-signature-errors", func(t *testing.T) { + p := newPolicy([]byte("not-a-signature"), pubKeyData) + st := &state{Input: Input{HTTP: &HTTP{}}} + httpVal, err := ast.InterfaceToValue(st.Input.HTTP) + require.NoError(t, err) + + got, err := p.builtinVerifyHTTPPGPSignatureImpl( + rego.BuiltinContext{Context: t.Context()}, + ast.NewTerm(httpVal), + ast.StringTerm(sigPath), + ast.StringTerm(keyPath), + st, + ) + require.Nil(t, got) + require.ErrorContains(t, err, "verify_http_pgp_signature: failed to parse detached signature") + }) +} + +func createDetachedPGPFixture(t *testing.T, payload []byte) ([]byte, []byte, digest.Digest, []byte) { + t.Helper() + + entity, err := openpgp.NewEntity("buildx", "", "buildx@example.com", &packet.Config{ + DefaultHash: crypto.SHA256, + RSABits: 2048, + }) + require.NoError(t, err) + + var sigBuf bytes.Buffer + err = openpgp.ArmoredDetachSign(&sigBuf, entity, bytes.NewReader(payload), &packet.Config{ + DefaultHash: crypto.SHA256, + }) + require.NoError(t, err) + sigData := sigBuf.Bytes() + + var pubBuf bytes.Buffer + aw, err := armor.Encode(&pubBuf, openpgp.PublicKeyType, nil) + require.NoError(t, err) + err = entity.Serialize(aw) + require.NoError(t, err) + err = aw.Close() + require.NoError(t, err) + pubKeyData := pubBuf.Bytes() + + sig, _, err := pgpsign.ParseArmoredDetachedSignature(sigData) + require.NoError(t, err) + + h := sig.Hash.New() + _, err = h.Write(payload) + require.NoError(t, err) + _, err = h.Write(sig.HashSuffix) + require.NoError(t, err) + sum := h.Sum(nil) + + dAlgo := digest.SHA256 + switch sig.Hash { + case crypto.SHA384: + dAlgo = digest.SHA384 + case crypto.SHA512: + dAlgo = digest.SHA512 + } + + return sigData, pubKeyData, digest.NewDigestFromEncoded(dAlgo, hex.EncodeToString(sum)), append([]byte(nil), sig.HashSuffix...) +} diff --git a/policy/resolve_test.go b/policy/resolve_test.go index 59e94b133695..8a4f3d688a4e 100644 --- a/policy/resolve_test.go +++ b/policy/resolve_test.go @@ -6,7 +6,9 @@ import ( slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" "github.com/moby/buildkit/client/llb/sourceresolver" + gwpb "github.com/moby/buildkit/frontend/gateway/pb" "github.com/moby/buildkit/solver/pb" + "github.com/moby/buildkit/sourcepolicy/policysession" "github.com/opencontainers/go-digest" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/require" @@ -66,3 +68,40 @@ func TestResolveInputUnknownsResolvesMaterialField(t *testing.T) { require.Nil(t, next) require.True(t, inp.Image.Provenance.Materials[0].Image.HasProvenance) } + +func TestResolveInputUnknownsHTTPChecksumResponseRequest(t *testing.T) { + inp := Input{ + HTTP: &HTTP{ + URL: "https://example.com/file.tgz", + Schema: "https", + Host: "example.com", + Path: "/file.tgz", + Query: map[string][]string{}, + }, + } + checksumReq := &gwpb.ChecksumRequest{ + Algo: gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA384, + Suffix: []byte{0xaa, 0xbb, 0xcc}, + } + + p := NewPolicy(Opt{}) + retry, next, err := p.resolveUnknowns( + context.Background(), + &inp, + &policysession.CheckPolicyRequest{ + Source: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{Identifier: "https://example.com/file.tgz"}, + }, + }, + nil, + nil, + &state{checksumNeededForSignature: checksumReq}, + ) + require.NoError(t, err) + require.False(t, retry) + require.NotNil(t, next) + require.NotNil(t, next.HTTP) + require.NotNil(t, next.HTTP.ChecksumRequest) + require.Equal(t, checksumReq.Algo, next.HTTP.ChecksumRequest.Algo) + require.Equal(t, checksumReq.Suffix, next.HTTP.ChecksumRequest.Suffix) +} diff --git a/policy/types.go b/policy/types.go index fe1d8664d050..d7f80a8f4f86 100644 --- a/policy/types.go +++ b/policy/types.go @@ -41,11 +41,13 @@ type HTTP struct { Checksum string `json:"checksum,omitempty"` - Signature *PGPSignature `json:"signature,omitempty"` - AttestationBundle *AttestationBundle `json:"attestationBundle,omitempty"` + checksumResponseForSignature *httpChecksumResponseForSignature `json:"-"` } -type AttestationBundle struct{} +type httpChecksumResponseForSignature struct { + Digest string `json:"-"` + Suffix []byte `json:"-"` +} type Git struct { Schema string `json:"schema,omitempty"` diff --git a/policy/utils_test.go b/policy/utils_test.go index 3f7ffb2509d6..b48daa3cab64 100644 --- a/policy/utils_test.go +++ b/policy/utils_test.go @@ -111,9 +111,10 @@ func TestRuntimeUnknownInputRefs(t *testing.T) { st := &state{ Unknowns: map[string]struct{}{ - funcVerifyGitSignature: {}, - funcArtifactAttestation: {}, - funcGithubAttestation: {}, + funcVerifyGitSignature: {}, + funcArtifactAttestation: {}, + funcGithubAttestation: {}, + funcVerifyHTTPPGPSignature: {}, }, } require.Equal(t, []string{"git.commit", "http.checksum"}, runtimeUnknownInputRefs(st)) diff --git a/policy/validate.go b/policy/validate.go index 69599cb59d5f..37d1470f8adc 100644 --- a/policy/validate.go +++ b/policy/validate.go @@ -45,7 +45,8 @@ type state struct { Input Input Unknowns map[string]struct{} - ImagePins map[digest.Digest]struct{} + ImagePins map[digest.Digest]struct{} + checksumNeededForSignature *gwpb.ChecksumRequest } func (s *state) addUnknown(key string) { @@ -274,7 +275,7 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy unk := collectUnknowns(pq.Support, unknowns) unk = append(unk, runtimeUnknownInputRefs(st)...) - retry, next, err := p.resolveUnknowns(ctx, &inp, req, platform, unk) + retry, next, err := p.resolveUnknowns(ctx, &inp, req, platform, unk, st) if err != nil { return nil, nil, err } @@ -292,7 +293,7 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy return nil, nil, err } - retry, next, err := p.resolveUnknowns(ctx, &inp, req, platform, runtimeUnknownInputRefs(st)) + retry, next, err := p.resolveUnknowns(ctx, &inp, req, platform, runtimeUnknownInputRefs(st), st) if err != nil { return nil, nil, err } @@ -372,11 +373,29 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy return nil, nil, errors.Errorf("maximum attempts reached for resolving policy metadata") } -func (p *Policy) resolveUnknowns(ctx context.Context, input *Input, req *policysession.CheckPolicyRequest, defaultPlatform *ocispecs.Platform, unk []string) (bool, *gwpb.ResolveSourceMetaRequest, error) { +func (p *Policy) resolveUnknowns(ctx context.Context, input *Input, req *policysession.CheckPolicyRequest, defaultPlatform *ocispecs.Platform, unk []string, st *state) (bool, *gwpb.ResolveSourceMetaRequest, error) { var resolver SourceMetadataResolver if p.opt.SourceResolver != nil { resolver = p.opt.SourceResolver } + + if st != nil && st.checksumNeededForSignature != nil { + next := &gwpb.ResolveSourceMetaRequest{ + Source: req.Source.Source, + } + if req.Platform != nil { + next.Platform = req.Platform + } + if err := AddUnknownsWithLogger(p.opt.Log, next, normalizeNodeUnknowns(unk)); err != nil { + return false, nil, err + } + next.HTTP = &gwpb.ResolveSourceHTTPRequest{ + ChecksumRequest: st.checksumNeededForSignature.CloneVT(), + } + p.log(logrus.InfoLevel, "policy decision for source %s: resolve missing fields %+v", sourceName(req), summarizeUnknownsForLog(unk)) + return false, next, nil + } + retry, next, err := ResolveInputUnknowns(ctx, input, req.Source.Source, unk, req.Platform, defaultPlatform, resolver, p.opt.VerifierProvider, p.opt.Log) if err != nil { return false, nil, err @@ -387,6 +406,7 @@ func (p *Policy) resolveUnknowns(ctx context.Context, input *Input, req *policys } return retry, nil, nil } + func platformFromReq(req *policysession.CheckPolicyRequest) (*ocispecs.Platform, error) { if req.Platform != nil { platformStr := req.Platform.OS + "/" + req.Platform.Architecture @@ -446,6 +466,12 @@ func sourceToInput(ctx context.Context, getVerifier PolicyVerifierProvider, src } if src.HTTP != nil { inp.HTTP.Checksum = src.HTTP.Checksum + if src.HTTP.ChecksumResponse != nil { + inp.HTTP.checksumResponseForSignature = &httpChecksumResponseForSignature{ + Digest: src.HTTP.ChecksumResponse.Digest, + Suffix: slices.Clone(src.HTTP.ChecksumResponse.Suffix), + } + } } if inp.HTTP.Checksum == "" { unknowns = append(unknowns, "input.http.checksum") diff --git a/util/sourcemeta/convert.go b/util/sourcemeta/convert.go index 1bc16d8a8c82..3b8d84a48597 100644 --- a/util/sourcemeta/convert.go +++ b/util/sourcemeta/convert.go @@ -33,6 +33,14 @@ func ToResolverOpt(req *gwpb.ResolveSourceMetaRequest, defaultPlatform *ocispecs if req != nil && req.Git != nil { opt.GitOpt = &sourceresolver.ResolveGitOpt{ReturnObject: req.Git.ReturnObject} } + if req != nil && req.HTTP != nil && req.HTTP.ChecksumRequest != nil { + opt.HTTPOpt = &sourceresolver.ResolveHTTPOpt{ + ChecksumReq: &sourceresolver.ResolveHTTPChecksumRequest{ + Algo: toResolverChecksumAlgo(req.HTTP.ChecksumRequest.Algo), + Suffix: append([]byte(nil), req.HTTP.ChecksumRequest.Suffix...), + }, + } + } return opt } @@ -64,10 +72,27 @@ func ToGatewayMetaResponse(resp *sourceresolver.MetaResponse) *gwpb.ResolveSourc Filename: resp.HTTP.Filename, LastModified: lastModified, } + if resp.HTTP.ChecksumResponse != nil { + out.HTTP.ChecksumResponse = &gwpb.ChecksumResponse{ + Digest: resp.HTTP.ChecksumResponse.Digest, + Suffix: append([]byte(nil), resp.HTTP.ChecksumResponse.Suffix...), + } + } } return out } +func toResolverChecksumAlgo(in gwpb.ChecksumRequest_ChecksumAlgo) sourceresolver.ResolveHTTPChecksumAlgo { + switch in { + case gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA384: + return sourceresolver.ResolveHTTPChecksumAlgoSHA384 + case gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA512: + return sourceresolver.ResolveHTTPChecksumAlgoSHA512 + default: + return sourceresolver.ResolveHTTPChecksumAlgoSHA256 + } +} + func toGatewayDescriptor(desc ocispecs.Descriptor) *gwpb.Descriptor { return &gwpb.Descriptor{ MediaType: desc.MediaType, diff --git a/util/sourcemeta/convert_test.go b/util/sourcemeta/convert_test.go new file mode 100644 index 000000000000..614acb743bd3 --- /dev/null +++ b/util/sourcemeta/convert_test.go @@ -0,0 +1,43 @@ +package sourcemeta + +import ( + "testing" + + "github.com/moby/buildkit/client/llb/sourceresolver" + gwpb "github.com/moby/buildkit/frontend/gateway/pb" + "github.com/moby/buildkit/solver/pb" + "github.com/stretchr/testify/require" +) + +func TestToResolverOptHTTPChecksumRequest(t *testing.T) { + opt := ToResolverOpt(&gwpb.ResolveSourceMetaRequest{ + Source: &pb.SourceOp{Identifier: "https://example.com/a"}, + HTTP: &gwpb.ResolveSourceHTTPRequest{ + ChecksumRequest: &gwpb.ChecksumRequest{ + Algo: gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA512, + Suffix: []byte{0x01, 0x02}, + }, + }, + }, nil) + require.NotNil(t, opt.HTTPOpt) + require.NotNil(t, opt.HTTPOpt.ChecksumReq) + require.Equal(t, sourceresolver.ResolveHTTPChecksumAlgoSHA512, opt.HTTPOpt.ChecksumReq.Algo) + require.Equal(t, []byte{0x01, 0x02}, opt.HTTPOpt.ChecksumReq.Suffix) +} + +func TestToGatewayMetaResponseHTTPChecksumResponse(t *testing.T) { + out := ToGatewayMetaResponse(&sourceresolver.MetaResponse{ + Op: &pb.SourceOp{Identifier: "https://example.com/a"}, + HTTP: &sourceresolver.ResolveHTTPResponse{ + Digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ChecksumResponse: &sourceresolver.ResolveHTTPChecksumResponse{ + Digest: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + Suffix: []byte{0x03, 0x04}, + }, + }, + }) + require.NotNil(t, out.HTTP) + require.NotNil(t, out.HTTP.ChecksumResponse) + require.Equal(t, "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", out.HTTP.ChecksumResponse.Digest) + require.Equal(t, []byte{0x03, 0x04}, out.HTTP.ChecksumResponse.Suffix) +}