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

feat: mTLS-over-TCP buildkit #284

Merged
merged 7 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions pkg/buildkit/buildkit.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ type Config struct {
ImageState llb.State
}

type Opts struct {
Addr string
CACertPath string
CertPath string
KeyPath string
}

func dockerLoad(ctx context.Context, pipeR io.Reader) error {
cmd := exec.CommandContext(ctx, "docker", "load")
cmd.Stdin = pipeR
Expand Down
43 changes: 41 additions & 2 deletions pkg/buildkit/buildkit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,39 @@ func checkMissingCapsError(t *testing.T, err error, caps ...apicaps.CapID) {
}
}

func TestGetServerNameFromAddr(t *testing.T) {
testCases := []struct {
name string
addr string
want string
}{
{
name: "hostname",
addr: "tcp://hostname:1234",
want: "hostname",
},
{
name: "IP address",
addr: "tcp://127.0.0.1:1234",
want: "127.0.0.1",
},
{
name: "invalid URL",
addr: "hostname:1234",
want: "",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := getServerNameFromAddr(tc.addr)
if got != tc.want {
t.Errorf("getServerNameFromAddr(%q) = %q, want %q", tc.addr, got, tc.want)
}
})
}
}

func TestNewClient(t *testing.T) {
ctx := context.Background()

Expand All @@ -151,7 +184,10 @@ func TestNewClient(t *testing.T) {
t.Parallel()
addr := newMockBuildkitAPI(t)
ctxT, cancel := context.WithTimeout(ctx, time.Second)
client, err := NewClient(ctxT, "unix://"+addr)
bkOpts := Opts{
Addr: "unix://" + addr,
}
client, err := NewClient(ctxT, bkOpts)
cancel()
assert.NoError(t, err)
defer client.Close()
Expand All @@ -167,7 +203,10 @@ func TestNewClient(t *testing.T) {

ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
client, err := NewClient(ctxT, "unix://"+addr)
bkOpts := Opts{
Addr: "unix://" + addr,
}
client, err := NewClient(ctxT, bkOpts)
assert.NoError(t, err)
defer client.Close()

Expand Down
28 changes: 25 additions & 3 deletions pkg/buildkit/drivers.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,39 @@

// NewClient returns a new buildkit client with the given addr.
// If addr is empty it will first try to connect to docker's buildkit instance and then fallback to DefaultAddr.
func NewClient(ctx context.Context, addr string) (*client.Client, error) {
if addr == "" {
func NewClient(ctx context.Context, bkOpts Opts) (*client.Client, error) {
if bkOpts.Addr == "" {
return autoClient(ctx)
}
client, err := client.New(ctx, addr)
opts := getCredentialOptions(bkOpts)
client, err := client.New(ctx, bkOpts.Addr, opts...)
if err != nil {
return nil, err
}
return client, nil
}

func getCredentialOptions(bkOpts Opts) []client.ClientOpt {
opts := []client.ClientOpt{}
if bkOpts.CACertPath != "" {
opts = append(opts, client.WithServerConfig(getServerNameFromAddr(bkOpts.Addr), bkOpts.CACertPath))
}

Check warning on line 46 in pkg/buildkit/drivers.go

View check run for this annotation

Codecov / codecov/patch

pkg/buildkit/drivers.go#L45-L46

Added lines #L45 - L46 were not covered by tests

if bkOpts.CertPath != "" || bkOpts.KeyPath != "" {
opts = append(opts, client.WithCredentials(bkOpts.CertPath, bkOpts.KeyPath))
}

Check warning on line 50 in pkg/buildkit/drivers.go

View check run for this annotation

Codecov / codecov/patch

pkg/buildkit/drivers.go#L49-L50

Added lines #L49 - L50 were not covered by tests

return opts
}

func getServerNameFromAddr(addr string) string {
u, err := url.Parse(addr)
if err != nil {
return ""
}

Check warning on line 59 in pkg/buildkit/drivers.go

View check run for this annotation

Codecov / codecov/patch

pkg/buildkit/drivers.go#L58-L59

Added lines #L58 - L59 were not covered by tests
return u.Hostname()
}

// ValidateClient checks to ensure the connected buildkit instance supports the features required by copa.
func ValidateClient(ctx context.Context, c *client.Client) error {
_, err := c.Build(ctx, client.SolveOpt{}, "", func(ctx context.Context, client gateway.Client) (*gateway.Result, error) {
Expand Down
17 changes: 13 additions & 4 deletions pkg/patch/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
reportFile string
patchedTag string
workingFolder string
buildkitAddr string
timeout time.Duration
ignoreError bool
format string
output string
bkOpts buildkit.Opts
}

func NewPatchCmd() *cobra.Command {
Expand All @@ -39,24 +39,33 @@
Short: "Patch container images with upgrade packages specified by a vulnerability report",
Example: "copa patch -i images/python:3.7-alpine -r trivy.json -t 3.7-alpine-patched",
RunE: func(cmd *cobra.Command, args []string) error {
bkopts := buildkit.Opts{
Addr: ua.bkOpts.Addr,
CACertPath: ua.bkOpts.CACertPath,
CertPath: ua.bkOpts.CertPath,
KeyPath: ua.bkOpts.KeyPath,
}

Check warning on line 47 in pkg/patch/cmd.go

View check run for this annotation

Codecov / codecov/patch

pkg/patch/cmd.go#L42-L47

Added lines #L42 - L47 were not covered by tests
return Patch(context.Background(),
ua.timeout,
ua.buildkitAddr,
ua.appImage,
ua.reportFile,
ua.patchedTag,
ua.workingFolder,
ua.format,
ua.output,
ua.ignoreError)
ua.ignoreError,
bkopts)

Check warning on line 57 in pkg/patch/cmd.go

View check run for this annotation

Codecov / codecov/patch

pkg/patch/cmd.go#L56-L57

Added lines #L56 - L57 were not covered by tests
},
}
flags := patchCmd.Flags()
flags.StringVarP(&ua.appImage, "image", "i", "", "Application image name and tag to patch")
flags.StringVarP(&ua.reportFile, "report", "r", "", "Vulnerability report file path")
flags.StringVarP(&ua.patchedTag, "tag", "t", "", "Tag for the patched image")
flags.StringVarP(&ua.workingFolder, "working-folder", "w", "", "Working folder, defaults to system temp folder")
flags.StringVarP(&ua.buildkitAddr, "addr", "a", "", "Address of buildkitd service, defaults to local docker daemon with fallback to "+buildkit.DefaultAddr)
flags.StringVarP(&ua.bkOpts.Addr, "addr", "a", "", "Address of buildkitd service, defaults to local docker daemon with fallback to "+buildkit.DefaultAddr)
flags.StringVarP(&ua.bkOpts.CACertPath, "cacert", "", "", "Absolute path to buildkitd CA certificate")
flags.StringVarP(&ua.bkOpts.CertPath, "cert", "", "", "Absolute path to buildkit client certificate")
flags.StringVarP(&ua.bkOpts.KeyPath, "key", "", "", "Absolute path to buildkit client key")
flags.DurationVar(&ua.timeout, "timeout", 5*time.Minute, "Timeout for the operation, defaults to '5m'")
flags.BoolVar(&ua.ignoreError, "ignore-errors", false, "Ignore errors and continue patching")
flags.StringVarP(&ua.format, "format", "f", "openvex", "Output format, defaults to 'openvex'")
Expand Down
8 changes: 4 additions & 4 deletions pkg/patch/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@
)

// Patch command applies package updates to an OCI image given a vulnerability report.
func Patch(ctx context.Context, timeout time.Duration, buildkitAddr, image, reportFile, patchedTag, workingFolder, format, output string, ignoreError bool) error {
func Patch(ctx context.Context, timeout time.Duration, image, reportFile, patchedTag, workingFolder, format, output string, ignoreError bool, bkOpts buildkit.Opts) error {

Check warning on line 32 in pkg/patch/patch.go

View check run for this annotation

Codecov / codecov/patch

pkg/patch/patch.go#L32

Added line #L32 was not covered by tests
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

ch := make(chan error)
go func() {
ch <- patchWithContext(timeoutCtx, buildkitAddr, image, reportFile, patchedTag, workingFolder, format, output, ignoreError)
ch <- patchWithContext(timeoutCtx, image, reportFile, patchedTag, workingFolder, format, output, ignoreError, bkOpts)

Check warning on line 38 in pkg/patch/patch.go

View check run for this annotation

Codecov / codecov/patch

pkg/patch/patch.go#L38

Added line #L38 was not covered by tests
}()

select {
Expand All @@ -60,7 +60,7 @@
}
}

func patchWithContext(ctx context.Context, buildkitAddr, image, reportFile, patchedTag, workingFolder, format, output string, ignoreError bool) error {
func patchWithContext(ctx context.Context, image, reportFile, patchedTag, workingFolder, format, output string, ignoreError bool, bkOpts buildkit.Opts) error {

Check warning on line 63 in pkg/patch/patch.go

View check run for this annotation

Codecov / codecov/patch

pkg/patch/patch.go#L63

Added line #L63 was not covered by tests
imageName, err := ref.ParseNamed(image)
if err != nil {
return err
Expand Down Expand Up @@ -113,7 +113,7 @@
}
log.Debugf("updates to apply: %v", updates)

client, err := buildkit.NewClient(ctx, buildkitAddr)
client, err := buildkit.NewClient(ctx, bkOpts)

Check warning on line 116 in pkg/patch/patch.go

View check run for this annotation

Codecov / codecov/patch

pkg/patch/patch.go#L116

Added line #L116 was not covered by tests
if err != nil {
return err
}
Expand Down
Loading