Skip to content

Commit

Permalink
custom proxy certificate (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakecoffman authored Oct 3, 2022
1 parent b7ed930 commit 8292839
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
run: go build -v ./...

- name: Test
run: go test -v ./...
run: go test ./...

lint:
runs-on: ubuntu-latest
Expand Down
19 changes: 10 additions & 9 deletions cmd/dependabot/internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ import (
)

var (
file string
cache string
debugging bool
extraHosts []string
output string
pullImages bool
volumes []string
timeout time.Duration
tempDir string
file string
cache string
debugging bool
proxyCertPath string
extraHosts []string
output string
pullImages bool
volumes []string
timeout time.Duration
tempDir string
)

// rootCmd represents the base command when called without any subcommands
Expand Down
24 changes: 13 additions & 11 deletions cmd/dependabot/internal/cmd/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,18 @@ var testCmd = &cobra.Command{
processInput(&scenario.Input)

if err := infra.Run(infra.RunParams{
CacheDir: cache,
Creds: scenario.Input.Credentials,
Debug: debugging,
Expected: scenario.Output,
ExtraHosts: extraHosts,
Job: &scenario.Input.Job,
Output: output,
PullImages: pullImages,
TempDir: tempDir,
Timeout: timeout,
Volumes: volumes,
CacheDir: cache,
Creds: scenario.Input.Credentials,
Debug: debugging,
Expected: scenario.Output,
ExtraHosts: extraHosts,
Job: &scenario.Input.Job,
Output: output,
ProxyCertPath: proxyCertPath,
PullImages: pullImages,
TempDir: tempDir,
Timeout: timeout,
Volumes: volumes,
}); err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -81,6 +82,7 @@ func init() {

testCmd.Flags().StringVarP(&output, "output", "o", "", "write scenario to file")
testCmd.Flags().StringVar(&cache, "cache", "", "cache import/export directory")
testCmd.Flags().StringVar(&proxyCertPath, "proxy-cert", "", "path to a certificate the proxy will trust")
testCmd.Flags().BoolVar(&pullImages, "pull", true, "pull the image if it isn't present")
testCmd.Flags().BoolVar(&debugging, "debug", false, "run an interactive shell inside the updater")
testCmd.Flags().StringArrayVarP(&volumes, "volume", "v", nil, "mount volumes in Docker")
Expand Down
24 changes: 13 additions & 11 deletions cmd/dependabot/internal/cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,18 @@ var updateCmd = &cobra.Command{
processInput(input)

if err := infra.Run(infra.RunParams{
CacheDir: cache,
Creds: input.Credentials,
Debug: debugging,
Expected: nil, // update subcommand doesn't use expectations
ExtraHosts: extraHosts,
Job: &input.Job,
Output: output,
PullImages: pullImages,
TempDir: tempDir,
Timeout: timeout,
Volumes: volumes,
CacheDir: cache,
Creds: input.Credentials,
Debug: debugging,
Expected: nil, // update subcommand doesn't use expectations
ExtraHosts: extraHosts,
Job: &input.Job,
Output: output,
ProxyCertPath: proxyCertPath,
PullImages: pullImages,
TempDir: tempDir,
Timeout: timeout,
Volumes: volumes,
}); err != nil {
log.Fatalf("failed to run updater: %v", err)
}
Expand Down Expand Up @@ -190,6 +191,7 @@ func init() {

updateCmd.Flags().StringVarP(&output, "output", "o", "", "write scenario to file")
updateCmd.Flags().StringVar(&cache, "cache", "", "cache import/export directory")
updateCmd.Flags().StringVar(&proxyCertPath, "proxy-cert", "", "path to a certificate the proxy will trust")
updateCmd.Flags().BoolVar(&pullImages, "pull", true, "pull the image if it isn't present")
updateCmd.Flags().BoolVar(&debugging, "debug", false, "run an interactive shell inside the updater")
updateCmd.Flags().StringArrayVarP(&volumes, "volume", "v", nil, "mount volumes in Docker")
Expand Down
4 changes: 2 additions & 2 deletions internal/infra/cadetails.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const (
keyExpiryYears = 2
)

var certSubject = pkix.Name{
var CertSubject = pkix.Name{
CommonName: "Dependabot Internal CA",
OrganizationalUnit: []string{"Dependabot"},
Organization: []string{"GitHub Inc."},
Expand Down Expand Up @@ -60,7 +60,7 @@ func generateCert(key *rsa.PrivateKey) (string, error) {

template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: certSubject,
Subject: CertSubject,
NotBefore: notBefore,
NotAfter: notAfter,
SignatureAlgorithm: x509.SHA256WithRSA,
Expand Down
25 changes: 25 additions & 0 deletions internal/infra/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"fmt"
"math/rand"
"os"
"path"
"path/filepath"
"strings"
"time"

"github.com/docker/docker/api/types"
Expand All @@ -17,6 +19,8 @@ import (
"github.com/docker/docker/pkg/stdcopy"
)

const proxyCertPath = "/usr/local/share/ca-certificates/custom-ca-cert.crt"

func init() {
// needed for namesgenerator.GetRandomName
rand.Seed(time.Now().UnixNano())
Expand Down Expand Up @@ -71,6 +75,24 @@ func NewProxy(ctx context.Context, cli *client.Client, params *RunParams, nets .
},
}
hostCfg.ExtraHosts = append(hostCfg.ExtraHosts, params.ExtraHosts...)
if params.ProxyCertPath != "" {
if !strings.HasPrefix(params.ProxyCertPath, "/") {
// needs to be absolute, assume it is relative to the working directory
var dir string
dir, err = os.Getwd()
if err != nil {
return nil, fmt.Errorf("couldn't get working directory: %w", err)
}
params.ProxyCertPath = path.Join(dir, params.ProxyCertPath)
}
hostCfg.Mounts = append(hostCfg.Mounts, mount.Mount{
Type: mount.TypeBind,
Source: params.ProxyCertPath,
Target: proxyCertPath,
ReadOnly: true,
})
}
hostCfg.ExtraHosts = append(hostCfg.ExtraHosts, params.ExtraHosts...)
if params.CacheDir != "" {
_ = os.MkdirAll(params.CacheDir, 0744)
cacheDir, _ := filepath.Abs(params.CacheDir)
Expand All @@ -86,6 +108,9 @@ func NewProxy(ctx context.Context, cli *client.Client, params *RunParams, nets .
"JOB_ID=" + jobID,
"PROXY_CACHE=true",
},
Entrypoint: []string{
"sh", "-c", "update-ca-certificates && /update-job-proxy",
},
}
hostName := namesgenerator.GetRandomName(1)
proxyContainer, err := cli.ContainerCreate(ctx, config, hostCfg, nil, nil, hostName)
Expand Down
114 changes: 114 additions & 0 deletions internal/infra/proxy_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package infra

import (
"archive/tar"
"bytes"
"context"
"errors"
"io"
"net/http"
"os"
"testing"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/namesgenerator"
)

Expand All @@ -14,3 +24,107 @@ func TestSeed(t *testing.T) {
t.Error("Not seeding math/rand")
}
}

// This tests the Proxy's ability to use a custom cert for outbound calls.
// It creates a custom proxy image to test with, passes it a cert, and uses it to
// communicate with a test server using the certs.
func TestNewProxy_customCert(t *testing.T) {
ctx := context.Background()

CertSubject.CommonName = "host.docker.internal"
ca, err := GenerateCertificateAuthority()
if err != nil {
t.Fatal(err)
}

cert, err := os.CreateTemp(os.TempDir(), "cert.pem")
key, err2 := os.CreateTemp(os.TempDir(), "key.pem")
if err != nil || err2 != nil {
t.Fatal(err, err2)
}
_, _ = cert.WriteString(ca.Cert)
_, _ = key.WriteString(ca.Key)
_ = cert.Close()
_ = key.Close()

successChan := make(chan struct{})
addr := "127.0.0.1:8765"
if os.Getenv("CI") != "" {
t.Log("detected running in actions")
addr = "0.0.0.0:8765"
}
testServer := &http.Server{
ReadHeaderTimeout: time.Second,
Addr: addr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("SUCCESS"))
successChan <- struct{}{}
}),
}
defer func() {
_ = testServer.Shutdown(ctx)
}()
go func() {
t.Log("Starting HTTPS server")
if err = testServer.ListenAndServeTLS(cert.Name(), key.Name()); err != nil && !errors.Is(err, http.ErrServerClosed) {
panic(err)
}
}()

cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
t.Fatal(err)
}

// build the test image
var buildContext bytes.Buffer
tw := tar.NewWriter(&buildContext)
addFileToArchive(tw, "/Dockerfile", 0644, proxyTestDockerfile)
_ = tw.Close()

tmp := ProxyImageName
defer func() {
ProxyImageName = tmp
}()
ProxyImageName = "curl-test"
resp, err := cli.ImageBuild(ctx, &buildContext, types.ImageBuildOptions{Tags: []string{ProxyImageName}})
if err != nil {
t.Fatal(err)
}
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()

defer func() {
_, _ = cli.ImageRemove(ctx, ProxyImageName, types.ImageRemoveOptions{})
}()

proxy, err := NewProxy(ctx, cli, &RunParams{
ProxyCertPath: cert.Name(),
})
if err != nil {
panic(err)
}
defer func() {
_ = proxy.Close()
}()

t.Log("Starting proxy")

go proxy.TailLogs(ctx, cli)

select {
case <-successChan:
t.Log("Success!")
case <-time.After(5 * time.Second):
t.Errorf("Not able to contact the test server")
}
}

const proxyTestDockerfile = `
FROM ghcr.io/github/dependabot-update-job-proxy/dependabot-update-job-proxy:latest
RUN apk add --no-cache curl
RUN echo "#!/bin/sh" > /update-job-proxy
RUN echo "CURLing host.docker.internal" >> /update-job-proxy
RUN echo "curl -s https://host.docker.internal:8765" >> /update-job-proxy
RUN chmod +x /update-job-proxy
`
2 changes: 2 additions & 0 deletions internal/infra/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type RunParams struct {
CacheDir string
// write output to a file
Output string
// ProxyCertPath is the path to a cert for the proxy to trust
ProxyCertPath string
// attempt to pull images if they aren't local?
PullImages bool
// run an interactive shell?
Expand Down

0 comments on commit 8292839

Please sign in to comment.