Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Package Signing #214

Merged
merged 28 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2895f61
Add fields to struct that will enable package signing
pmengelbert Apr 1, 2024
d2fd240
Begin implementing forwarding for signing
pmengelbert Apr 4, 2024
bcd6850
Clean up signing forwarding code
pmengelbert Apr 4, 2024
7e93589
Add minimal signer
pmengelbert Apr 8, 2024
d7c202d
test
pmengelbert Apr 11, 2024
0425ba5
save
pmengelbert Apr 12, 2024
9cc00c8
Working signing setup
pmengelbert Apr 12, 2024
5747f74
Wire up secrets for signing image
pmengelbert Apr 15, 2024
f2f89ef
Set up test for signer
pmengelbert Apr 17, 2024
baa9342
Signing for windows and linux
pmengelbert Apr 17, 2024
e31d6f2
Set up test for signer forwarding
pmengelbert Apr 19, 2024
fdb5098
Use "context" as key for frontend input
pmengelbert Apr 29, 2024
ceb78f5
Remove holdovers from debugging
pmengelbert Apr 29, 2024
6c494ea
Slight changes to please the linter
pmengelbert Apr 29, 2024
4671480
Add changes to signing image before removal
pmengelbert Apr 30, 2024
4a654fe
Remove signer image from repo
pmengelbert Apr 30, 2024
68dd065
Add documentation on signing.
pmengelbert Apr 30, 2024
9fd9ee0
Delegate search for artifacts to signer
pmengelbert Apr 30, 2024
5b4e7e4
Move signing.md to `website/docs`
pmengelbert Apr 30, 2024
cc1dee2
Rename short variable
pmengelbert Apr 30, 2024
5b02001
Fix broken link in docs
pmengelbert Apr 30, 2024
accc429
Add signing entry to website config
pmengelbert Apr 30, 2024
db83add
Tidy up signing test code
pmengelbert May 1, 2024
2ea0cdb
Add `package_config` to root of Spec
pmengelbert May 1, 2024
fa20d32
Test specifying signer in root level PackageConfig
pmengelbert May 1, 2024
afb3fc4
Run `go generate`
pmengelbert May 1, 2024
fbc82f1
Make linux distro test generic
pmengelbert May 1, 2024
1af3b0f
Finish fixing tests
pmengelbert May 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,17 @@
"type": "object",
"description": "ImageConfig is the configuration for the output image."
},
"PackageConfig": {
"properties": {
"signer": {
"$ref": "#/$defs/Frontend",
"description": "Signer is the configuration to use for signing packages"
}
},
"additionalProperties": false,
"type": "object",
"description": "PackageConfig encapsulates the configuration for artifact targets"
},
"PackageDependencies": {
"properties": {
"build": {
Expand Down Expand Up @@ -791,6 +802,10 @@
"$ref": "#/$defs/PackageDependencies",
"description": "Dependencies are the different dependencies that need to be specified in the package.\nDependencies are overwritten if specified in the target map for the requested distro."
},
"package_config": {
"$ref": "#/$defs/PackageConfig",
"description": "PackageConfig is the configuration to use for artifact targets, such as\nrpms, debs, or zip files containing Windows binaries"
},
"image": {
"$ref": "#/$defs/ImageConfig",
"description": "Image is the image configuration when the target output is a container image.\nThis is overwritten if specified in the target map for the requested distro."
Expand Down Expand Up @@ -858,6 +873,10 @@
},
"type": "array",
"description": "Tests are the list of tests to run which are specific to the target.\nTests are appended to the list of tests in the main [Spec]"
},
"package_config": {
"$ref": "#/$defs/PackageConfig",
"description": "PackageConfig is the configuration to use for artifact targets, such as\nrpms, debs, or zip files containing Windows binaries"
}
},
"additionalProperties": false,
Expand Down
10 changes: 10 additions & 0 deletions frontend/mariner2/handle_rpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ func handleRPM(ctx context.Context, client gwclient.Client) (*gwclient.Result, e
return nil, nil, err
}

if signer, ok := spec.GetSigner(targetKey); ok {
signed, err := frontend.ForwardToSigner(ctx, client, platform, signer, st)
if err != nil {
return nil, nil, err
}

st = signed
}

def, err := st.Marshal(ctx, pg)
if err != nil {
return nil, nil, fmt.Errorf("error marshalling llb: %w", err)
Expand All @@ -64,6 +73,7 @@ func handleRPM(ctx context.Context, client gwclient.Client) (*gwclient.Result, e
if err != nil {
return nil, nil, err
}

return ref, nil, nil
})
}
Expand Down
65 changes: 65 additions & 0 deletions frontend/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package frontend

import (
"context"
"fmt"

"github.com/Azure/dalec"
"github.com/goccy/go-yaml"
Expand All @@ -10,6 +11,7 @@ import (
"github.com/moby/buildkit/frontend/dockerui"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/solver/pb"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -116,3 +118,66 @@ func marshalDockerfile(ctx context.Context, dt []byte, opts ...llb.ConstraintsOp
st := llb.Scratch().File(llb.Mkfile(dockerui.DefaultDockerfileName, 0600, dt), opts...)
return st.Marshal(ctx)
}

func ForwardToSigner(ctx context.Context, client gwclient.Client, platform *ocispecs.Platform, cfg *dalec.Frontend, s llb.State) (llb.State, error) {
const (
sourceKey = "source"
contextKey = "context"
inputKey = "input"
)

opts := client.BuildOpts().Opts

req, err := newSolveRequest(toFrontend(cfg))
if err != nil {
return llb.Scratch(), err
}

for k, v := range opts {
if k == "source" || k == "cmdline" {
continue
}
req.FrontendOpt[k] = v
}

inputs, err := client.Inputs(ctx)
if err != nil {
return llb.Scratch(), err
}

m := make(map[string]*pb.Definition)

for k, st := range inputs {
def, err := st.Marshal(ctx)
if err != nil {
return llb.Scratch(), err
}
m[k] = def.ToPB()
}
req.FrontendInputs = m

stateDef, err := s.Marshal(ctx)
if err != nil {
return llb.Scratch(), err
}

req.FrontendOpt[contextKey] = compound(inputKey, contextKey)
req.FrontendInputs[contextKey] = stateDef.ToPB()
req.FrontendOpt["dalec.target"] = opts["dalec.target"]
pmengelbert marked this conversation as resolved.
Show resolved Hide resolved

res, err := client.Solve(ctx, req)
if err != nil {
return llb.Scratch(), err
}

ref, err := res.SingleRef()
if err != nil {
return llb.Scratch(), err
}

return ref.ToState()
}

func compound(k, v string) string {
return fmt.Sprintf("%s:%s", k, v)
}
9 changes: 9 additions & 0 deletions frontend/windows/handle_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ func handleContainer(ctx context.Context, client gwclient.Client) (*gwclient.Res
return nil, nil, fmt.Errorf("unable to build binary %w", err)
}

if signer, ok := spec.GetSigner(targetKey); ok {
signed, err := frontend.ForwardToSigner(ctx, client, platform, signer, bin)
if err != nil {
return nil, nil, err
}

bin = signed
}

baseImgName := getBaseOutputImage(spec, targetKey, defaultBaseImage)
baseImage := llb.Image(baseImgName, llb.Platform(targetPlatform))

Expand Down
9 changes: 9 additions & 0 deletions frontend/windows/handle_zip.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ func handleZip(ctx context.Context, client gwclient.Client) (*gwclient.Result, e
return nil, nil, fmt.Errorf("unable to build binaries: %w", err)
}

if signer, ok := spec.GetSigner(targetKey); ok {
signed, err := frontend.ForwardToSigner(ctx, client, platform, signer, bin)
if err != nil {
return nil, nil, err
}

bin = signed
}

st := getZipLLB(worker, spec.Name, bin)
if err != nil {
return nil, nil, err
Expand Down
18 changes: 18 additions & 0 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,21 @@ func (s *Spec) GetSymlinks(target string) map[string]SymlinkTarget {

return lm
}

func (s *Spec) GetSigner(targetKey string) (*Frontend, bool) {
if s.Targets != nil {
if t, ok := s.Targets[targetKey]; ok && hasValidSigner(t.PackageConfig) {
return t.PackageConfig.Signer, true
}
}

if hasValidSigner(s.PackageConfig) {
return s.PackageConfig.Signer, true
}

return nil, false
}

func hasValidSigner(pc *PackageConfig) bool {
return pc != nil && pc.Signer != nil && pc.Signer.Image != ""
}
13 changes: 13 additions & 0 deletions spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ type Spec struct {
// Dependencies are the different dependencies that need to be specified in the package.
// Dependencies are overwritten if specified in the target map for the requested distro.
Dependencies *PackageDependencies `yaml:"dependencies,omitempty" json:"dependencies,omitempty"`
// PackageConfig is the configuration to use for artifact targets, such as
// rpms, debs, or zip files containing Windows binaries
PackageConfig *PackageConfig `yaml:"package_config,omitempty" json:"package_config,omitempty"`
// Image is the image configuration when the target output is a container image.
// This is overwritten if specified in the target map for the requested distro.
Image *ImageConfig `yaml:"image,omitempty" json:"image,omitempty"`
Expand Down Expand Up @@ -402,6 +405,16 @@ type Target struct {
// Tests are the list of tests to run which are specific to the target.
// Tests are appended to the list of tests in the main [Spec]
Tests []*TestSpec `yaml:"tests,omitempty" json:"tests,omitempty"`

// PackageConfig is the configuration to use for artifact targets, such as
// rpms, debs, or zip files containing Windows binaries
PackageConfig *PackageConfig `yaml:"package_config,omitempty" json:"package_config,omitempty"`
}

// PackageConfig encapsulates the configuration for artifact targets
type PackageConfig struct {
// Signer is the configuration to use for signing packages
Signer *Frontend `yaml:"signer,omitempty" json:"signer,omitempty"`
}

// TestSpec is used to execute tests against a container with the package installed in it.
Expand Down
66 changes: 66 additions & 0 deletions test/fixtures/signer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package fixtures

import (
"context"
"encoding/json"

"github.com/Azure/dalec"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
"github.com/moby/buildkit/frontend/dockerui"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
)

func PhonySigner(ctx context.Context, gwc gwclient.Client) (*gwclient.Result, error) {
dc, err := dockerui.NewClient(gwc)
if err != nil {
return nil, err
}

bctx, err := dc.MainContext(ctx)
if err != nil {
return nil, err
}

st := llb.Image("golang:1.21", llb.WithMetaResolver(gwc)).
Run(
llb.Args([]string{"go", "build", "-o=/build/out", "./test/fixtures/signer"}),
llb.AddEnv("CGO_ENABLED", "0"),
goModCache,
goBuildCache,
llb.Dir("/build/src"),
llb.AddMount("/build/src", *bctx, llb.Readonly),
).
AddMount("/build/out", llb.Scratch())

cfg := dalec.DockerImageSpec{
Config: dalec.DockerImageConfig{
ImageConfig: ocispecs.ImageConfig{
Entrypoint: []string{"/signer"},
},
},
}
injectFrontendLabels(&cfg)

dt, err := json.Marshal(cfg)
if err != nil {
return nil, err
}

def, err := st.Marshal(ctx)
if err != nil {
return nil, err
}

res, err := gwc.Solve(ctx, gwclient.SolveRequest{
Definition: def.ToPB(),
})
if err != nil {
return nil, err
}

res.AddMeta(exptypes.ExporterImageConfigKey, dt)

return res, nil
}
89 changes: 89 additions & 0 deletions test/fixtures/signer/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package main

import (
"context"
_ "embed"
"encoding/json"
"fmt"
"os"
"strings"

"github.com/Azure/dalec/frontend"
"github.com/moby/buildkit/client/llb"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/frontend/gateway/grpcclient"
"github.com/moby/buildkit/util/appcontext"
"github.com/moby/buildkit/util/bklog"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/grpclog"
)

func main() {
bklog.L.Logger.SetOutput(os.Stderr)
grpclog.SetLoggerV2(grpclog.NewLoggerV2WithVerbosity(bklog.L.WriterLevel(logrus.InfoLevel), bklog.L.WriterLevel(logrus.WarnLevel), bklog.L.WriterLevel(logrus.ErrorLevel), 1))

ctx := appcontext.Context()

if err := grpcclient.RunFromEnvironment(ctx, func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
bopts := c.BuildOpts().Opts
target := bopts["dalec.target"]

inputs, err := c.Inputs(ctx)
if err != nil {
return nil, err
}

type config struct {
OS string
}

cfg := config{}

switch target {
case "windowscross", "windows":
cfg.OS = "windows"
default:
cfg.OS = "linux"
}

curFrontend, ok := c.(frontend.CurrentFrontend)
if !ok {
return nil, fmt.Errorf("cast to currentFrontend failed")
}

basePtr, err := curFrontend.CurrentFrontend()
if err != nil || basePtr == nil {
if err == nil {
err = fmt.Errorf("base frontend ptr was nil")
}
return nil, err
}

inputId := strings.TrimPrefix(bopts["context"], "input:")
_, ok = inputs[inputId]
if !ok {
return nil, fmt.Errorf("no artifact state provided to signer")
}

configBytes, err := json.Marshal(&cfg)
if err != nil {
return nil, err
}

output := llb.Scratch().
File(llb.Mkfile("/target", 0o600, []byte(target))).
File(llb.Mkfile("/config.json", 0o600, configBytes))

def, err := output.Marshal(ctx)
if err != nil {
return nil, err
}

return c.Solve(ctx, gwclient.SolveRequest{
Definition: def.ToPB(),
})
}); err != nil {
bklog.L.WithError(err).Fatal("error running frontend")
os.Exit(137)
}
}
3 changes: 2 additions & 1 deletion test/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import (
)

const (
phonyRef = "dalec/integration/frontend/phony"
phonyRef = "dalec/integration/frontend/phony"
phonySignerRef = "dalec/integration/signer/phony"
)

func startTestSpan(ctx context.Context, t *testing.T) context.Context {
Expand Down
Loading