From 92221059a4420ea623463e17a9c303f8ec1ef6f4 Mon Sep 17 00:00:00 2001 From: Harsh Thakur Date: Wed, 6 Sep 2023 20:54:08 +0530 Subject: [PATCH 1/3] Add flags for copa to connect mTLS-over-TCP buildkitd Signed-off-by: Harsh Thakur --- pkg/buildkit/buildkit.go | 7 ++++++ pkg/buildkit/buildkit_test.go | 43 +++++++++++++++++++++++++++++++++-- pkg/buildkit/drivers.go | 29 ++++++++++++++++++++--- pkg/patch/cmd.go | 34 ++++++++++++++++++--------- pkg/patch/patch.go | 8 +++---- 5 files changed, 101 insertions(+), 20 deletions(-) diff --git a/pkg/buildkit/buildkit.go b/pkg/buildkit/buildkit.go index 8fbe2355..f9d0cfdf 100644 --- a/pkg/buildkit/buildkit.go +++ b/pkg/buildkit/buildkit.go @@ -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 diff --git a/pkg/buildkit/buildkit_test.go b/pkg/buildkit/buildkit_test.go index f926633b..bd5f91da 100644 --- a/pkg/buildkit/buildkit_test.go +++ b/pkg/buildkit/buildkit_test.go @@ -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() @@ -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() @@ -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() diff --git a/pkg/buildkit/drivers.go b/pkg/buildkit/drivers.go index 10e90b75..73f38b0e 100644 --- a/pkg/buildkit/drivers.go +++ b/pkg/buildkit/drivers.go @@ -27,17 +27,40 @@ var ( // 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{} + // addr := bkOpts.Addr + if bkOpts.CACertPath != "" { + opts = append(opts, client.WithServerConfig(getServerNameFromAddr(bkOpts.Addr), bkOpts.CACertPath)) + } + + if bkOpts.CertPath != "" || bkOpts.KeyPath != "" { + opts = append(opts, client.WithCredentials(bkOpts.CertPath, bkOpts.KeyPath)) + } + + return opts +} + +func getServerNameFromAddr(addr string) string { + u, err := url.Parse(addr) + if err != nil { + return "" + } + 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) { diff --git a/pkg/patch/cmd.go b/pkg/patch/cmd.go index e56b6ff7..7b0fcc4d 100644 --- a/pkg/patch/cmd.go +++ b/pkg/patch/cmd.go @@ -21,15 +21,18 @@ import ( ) type patchArgs struct { - appImage string - reportFile string - patchedTag string - workingFolder string - buildkitAddr string - timeout time.Duration - ignoreError bool - format string - output string + appImage string + reportFile string + patchedTag string + workingFolder string + buildkitAddr string + buildkitCACertPath string + buildkitClientCertPath string + buildkitClientKeyPath string + timeout time.Duration + ignoreError bool + format string + output string } func NewPatchCmd() *cobra.Command { @@ -39,16 +42,22 @@ func NewPatchCmd() *cobra.Command { 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.buildkitAddr, + CACertPath: ua.buildkitCACertPath, + CertPath: ua.buildkitClientCertPath, + KeyPath: ua.buildkitClientKeyPath, + } 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) }, } flags := patchCmd.Flags() @@ -57,6 +66,9 @@ func NewPatchCmd() *cobra.Command { 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.buildkitCACertPath, "cacert", "", "", "Absolute path to buildkitd CA certificate") + flags.StringVarP(&ua.buildkitClientCertPath, "cert", "", "", "Absolute path to buildkit client certificate") + flags.StringVarP(&ua.buildkitClientKeyPath, "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'") diff --git a/pkg/patch/patch.go b/pkg/patch/patch.go index 2ae07b68..07b7081b 100644 --- a/pkg/patch/patch.go +++ b/pkg/patch/patch.go @@ -29,13 +29,13 @@ const ( ) // 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 { 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) }() select { @@ -60,7 +60,7 @@ func removeIfNotDebug(workingFolder string) { } } -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 { imageName, err := ref.ParseNamed(image) if err != nil { return err @@ -113,7 +113,7 @@ func patchWithContext(ctx context.Context, buildkitAddr, image, reportFile, patc } log.Debugf("updates to apply: %v", updates) - client, err := buildkit.NewClient(ctx, buildkitAddr) + client, err := buildkit.NewClient(ctx, bkOpts) if err != nil { return err } From 697cb213b4debb5b1bf01272cbab054eafd6393b Mon Sep 17 00:00:00 2001 From: Harsh Thakur Date: Wed, 6 Sep 2023 22:33:09 +0530 Subject: [PATCH 2/3] remove unused comment Signed-off-by: Harsh Thakur --- pkg/buildkit/drivers.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/buildkit/drivers.go b/pkg/buildkit/drivers.go index 73f38b0e..5fa46ae6 100644 --- a/pkg/buildkit/drivers.go +++ b/pkg/buildkit/drivers.go @@ -41,7 +41,6 @@ func NewClient(ctx context.Context, bkOpts Opts) (*client.Client, error) { func getCredentialOptions(bkOpts Opts) []client.ClientOpt { opts := []client.ClientOpt{} - // addr := bkOpts.Addr if bkOpts.CACertPath != "" { opts = append(opts, client.WithServerConfig(getServerNameFromAddr(bkOpts.Addr), bkOpts.CACertPath)) } From e2ae0ba5f8e755148112900883c06293c8caa8c2 Mon Sep 17 00:00:00 2001 From: Harsh Thakur Date: Fri, 15 Sep 2023 18:55:59 +0530 Subject: [PATCH 3/3] have flag struct include bkOpts struct Signed-off-by: Harsh Thakur --- pkg/patch/cmd.go | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/pkg/patch/cmd.go b/pkg/patch/cmd.go index 7b0fcc4d..1e6a59bd 100644 --- a/pkg/patch/cmd.go +++ b/pkg/patch/cmd.go @@ -21,18 +21,15 @@ import ( ) type patchArgs struct { - appImage string - reportFile string - patchedTag string - workingFolder string - buildkitAddr string - buildkitCACertPath string - buildkitClientCertPath string - buildkitClientKeyPath string - timeout time.Duration - ignoreError bool - format string - output string + appImage string + reportFile string + patchedTag string + workingFolder string + timeout time.Duration + ignoreError bool + format string + output string + bkOpts buildkit.Opts } func NewPatchCmd() *cobra.Command { @@ -43,10 +40,10 @@ func NewPatchCmd() *cobra.Command { 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.buildkitAddr, - CACertPath: ua.buildkitCACertPath, - CertPath: ua.buildkitClientCertPath, - KeyPath: ua.buildkitClientKeyPath, + Addr: ua.bkOpts.Addr, + CACertPath: ua.bkOpts.CACertPath, + CertPath: ua.bkOpts.CertPath, + KeyPath: ua.bkOpts.KeyPath, } return Patch(context.Background(), ua.timeout, @@ -65,10 +62,10 @@ func NewPatchCmd() *cobra.Command { 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.buildkitCACertPath, "cacert", "", "", "Absolute path to buildkitd CA certificate") - flags.StringVarP(&ua.buildkitClientCertPath, "cert", "", "", "Absolute path to buildkit client certificate") - flags.StringVarP(&ua.buildkitClientKeyPath, "key", "", "", "Absolute path to buildkit client key") + 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'")