Skip to content

Commit

Permalink
Add support for validating HTTP by digest
Browse files Browse the repository at this point in the history
This is needed to ensure the integrity of http sources.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
  • Loading branch information
cpuguy83 committed Apr 18, 2024
1 parent b65dc7f commit 9411e94
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 4 deletions.
9 changes: 7 additions & 2 deletions docs/spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -547,15 +547,20 @@
"SourceHTTP": {
"properties": {
"url": {
"type": "string"
"type": "string",
"description": "URL is the URL to download the file from."
},
"digest": {
"type": "string",
"description": "Digest is the digest of the file to download.\nThis is used to verify the integrity of the file.\nForm: \u003calgorithm\u003e:\u003cdigest\u003e"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"url"
],
"description": "No longer supports `.git` URLs as git repos."
"description": "SourceHTTP is used to download a file from an HTTP(s) URL."
},
"SourceInline": {
"properties": {
Expand Down
3 changes: 3 additions & 0 deletions load.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ func (s *Source) validate(failContext ...string) (retErr error) {
count++
}
if s.HTTP != nil {
if err := s.HTTP.validate(); err != nil {
retErr = goerrors.Join(retErr, err)
}
count++
}
if s.Context != nil {
Expand Down
15 changes: 15 additions & 0 deletions source.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,25 @@ func (src *SourceDockerImage) AsState(name string, path string, sOpt SourceOpts,
func (src *SourceHTTP) AsState(name string, opts ...llb.ConstraintsOpt) (llb.State, error) {
httpOpts := []llb.HTTPOption{withConstraints(opts)}
httpOpts = append(httpOpts, llb.Filename(name))
if src.Digest != "" {
httpOpts = append(httpOpts, llb.Checksum(src.Digest))
}
st := llb.HTTP(src.URL, httpOpts...)
return st, nil
}

func (src *SourceHTTP) validate() error {
if src.URL == "" {
return errors.New("http source must have a URL")
}
if src.Digest != "" {
if err := src.Digest.Validate(); err != nil {
return errors.WithStack(err)
}
}
return nil
}

// InvalidSourceError is an error type returned when a source is invalid.
type InvalidSourceError struct {
Name string
Expand Down
21 changes: 21 additions & 0 deletions source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,27 @@ func TestSourceHTTP(t *testing.T) {
if op.Attrs[httpFilename] != "test" {
t.Errorf("expected http.filename %q, got %q", xFilename, op.Attrs[httpFilename])
}

t.Run("with digest", func(t *testing.T) {
dgst := digest.Canonical.FromBytes(nil)
src.HTTP.Digest = dgst

ops := getSourceOp(ctx, t, src)
op := ops[0].GetSource()

if len(op.Attrs) != 2 {
t.Errorf("expected 2 attribute, got %d", len(op.Attrs))
}

if op.Attrs[httpFilename] != "test" {
t.Errorf("expected http.filename %q, got %q", xFilename, op.Attrs[httpFilename])
}

const httpChecksum = "http.checksum"
if op.Attrs[httpChecksum] != dgst.String() {
t.Errorf("expected http.checksum %q, got %q", dgst.String(), op.Attrs[httpChecksum])
}
})
}

func toImageRef(ref string) string {
Expand Down
10 changes: 8 additions & 2 deletions spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"regexp"
"strings"
"time"

"github.com/opencontainers/go-digest"
)

// Spec is the specification for a package build.
Expand Down Expand Up @@ -173,10 +175,14 @@ type SourceGit struct {
KeepGitDir bool `yaml:"keepGitDir" json:"keepGitDir"`
}

// No longer supports `.git` URLs as git repos. That has to be done with
// `SourceGit`
// SourceHTTP is used to download a file from an HTTP(s) URL.
type SourceHTTP struct {
// URL is the URL to download the file from.
URL string `yaml:"url" json:"url"`
// Digest is the digest of the file to download.
// This is used to verify the integrity of the file.
// Form: <algorithm>:<digest>
Digest digest.Digest `yaml:"digest,omitempty" json:"digest,omitempty"`
}

// SourceContext is used to generate a source from a build context. The path to
Expand Down
45 changes: 45 additions & 0 deletions test/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package test

import (
"context"
"strings"
"testing"

"github.com/Azure/dalec"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
"github.com/opencontainers/go-digest"
)

func TestSourceCmd(t *testing.T) {
Expand Down Expand Up @@ -177,3 +179,46 @@ func TestSourceBuild(t *testing.T) {
})
})
}

func TestSourceHTTP(t *testing.T) {
t.Parallel()

url := "https://raw.githubusercontent.com/Azure/dalec/0ae22acf69ab6ef0a0503affed1a8952c9dd1384/README.md"
const badDigest = digest.Digest("sha256:000084c7170b4cfbad0690412259b5e252f84c0ccff79aaca023beb3f3ed0000")
const goodDigest = digest.Digest("sha256:b0fa84c7170b4cfbad0690412259b5e252f84c0ccff79aaca023beb3f3ed6380")

newSpec := func(url string, digest digest.Digest) *dalec.Spec {
return &dalec.Spec{
Sources: map[string]dalec.Source{
"test": {
HTTP: &dalec.SourceHTTP{
URL: url,
Digest: digest,
},
},
},
}
}

testEnv.RunTest(baseCtx, t, func(ctx context.Context, gwc gwclient.Client) (*gwclient.Result, error) {
bad := newSolveRequest(withBuildTarget("debug/sources"), withSpec(ctx, t, newSpec(url, badDigest)))
bad.Evaluate = true
_, err := gwc.Solve(ctx, bad)
if err == nil {
t.Fatal("expected digest mismatch, but received none")
}

if !strings.Contains(err.Error(), "digest mismatch") {
t.Fatalf("expected digest mismatch, got: %v", err)
}

good := newSolveRequest(withBuildTarget("debug/sources"), withSpec(ctx, t, newSpec(url, goodDigest)))
good.Evaluate = true
_, err = gwc.Solve(ctx, good)
if err != nil {
t.Fatal(err)
}

return gwclient.NewResult(), nil
})
}

0 comments on commit 9411e94

Please sign in to comment.