Skip to content

Commit

Permalink
feat: mTLS-over-TCP buildkit (#284)
Browse files Browse the repository at this point in the history
Signed-off-by: Harsh Thakur <harshthakur9030@gmail.com>
Co-authored-by: Xander Grzywinski <xandergrzyw@gmail.com>
Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 18, 2023
1 parent 6a8afc2 commit a4c4abe
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 13 deletions.
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 @@ 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{}
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) {
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 @@ type patchArgs struct {
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 @@ 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.bkOpts.Addr,
CACertPath: ua.bkOpts.CACertPath,
CertPath: ua.bkOpts.CertPath,
KeyPath: ua.bkOpts.KeyPath,
}
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()
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 @@ 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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit a4c4abe

Please sign in to comment.