From f2d23dbb9ce36ed9f46c641d5de5fc6337f4701c Mon Sep 17 00:00:00 2001 From: njucjc Date: Sat, 19 Aug 2023 03:37:53 +0800 Subject: [PATCH] buildctl: Add configured TLS certificate to trust store when making calls to registry auth Signed-off-by: njucjc --- cmd/buildctl/build.go | 24 +++++-- session/auth/authprovider/authprovider.go | 62 ++++++++++++++++--- .../auth/authprovider/authprovider_test.go | 5 +- 3 files changed, 77 insertions(+), 14 deletions(-) diff --git a/cmd/buildctl/build.go b/cmd/buildctl/build.go index 911c142f10a88..5916fa5ce8d62 100644 --- a/cmd/buildctl/build.go +++ b/cmd/buildctl/build.go @@ -11,6 +11,11 @@ import ( "github.com/containerd/continuity" "github.com/docker/cli/cli/config" + digest "github.com/opencontainers/go-digest" + "github.com/pkg/errors" + "github.com/urfave/cli" + "golang.org/x/sync/errgroup" + "github.com/moby/buildkit/client" "github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/cmd/buildctl/build" @@ -24,10 +29,6 @@ import ( spb "github.com/moby/buildkit/sourcepolicy/pb" "github.com/moby/buildkit/util/bklog" "github.com/moby/buildkit/util/progress/progresswriter" - digest "github.com/opencontainers/go-digest" - "github.com/pkg/errors" - "github.com/urfave/cli" - "golang.org/x/sync/errgroup" ) var buildCommand = cli.Command{ @@ -105,6 +106,18 @@ var buildCommand = cli.Command{ Name: "ref-file", Usage: "Write build ref to a file", }, + cli.StringFlag{ + Name: "registry-auth-tlscacert", + Usage: "CA certificate to validate TLS when authenticating with registries", + }, + cli.StringFlag{ + Name: "registry-auth-tlscert", + Usage: "Client certificate to validate TLS when authenticating with registries", + }, + cli.StringFlag{ + Name: "registry-auth-tlskey", + Usage: "Client key to validate TLS when authenticating with registries", + }, }, } @@ -158,7 +171,8 @@ func buildAction(clicontext *cli.Context) error { } dockerConfig := config.LoadDefaultConfigFile(os.Stderr) - attachable := []session.Attachable{authprovider.NewDockerAuthProvider(dockerConfig)} + attachable := []session.Attachable{authprovider.NewDockerAuthProvider(dockerConfig, + clicontext.String("registry-auth-tlscacert"), clicontext.String("registry-auth-tlscert"), clicontext.String("registry-auth-tlskey"))} if ssh := clicontext.StringSlice("ssh"); len(ssh) > 0 { configs, err := build.ParseSSH(ssh) diff --git a/session/auth/authprovider/authprovider.go b/session/auth/authprovider/authprovider.go index 045185d6b6742..dae97848a7853 100644 --- a/session/auth/authprovider/authprovider.go +++ b/session/auth/authprovider/authprovider.go @@ -5,9 +5,12 @@ import ( "crypto/ed25519" "crypto/hmac" "crypto/sha256" + "crypto/tls" + "crypto/x509" "fmt" "net/http" "os" + "runtime" "strconv" "strings" "sync" @@ -18,26 +21,30 @@ import ( "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/types" - "github.com/moby/buildkit/session" - "github.com/moby/buildkit/session/auth" - "github.com/moby/buildkit/util/progress/progresswriter" "github.com/pkg/errors" "golang.org/x/crypto/nacl/sign" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + + "github.com/moby/buildkit/session" + "github.com/moby/buildkit/session/auth" + "github.com/moby/buildkit/util/progress/progresswriter" ) const defaultExpiration = 60 const dockerHubConfigfileKey = "https://index.docker.io/v1/" const dockerHubRegistryHost = "registry-1.docker.io" -func NewDockerAuthProvider(cfg *configfile.ConfigFile) session.Attachable { +func NewDockerAuthProvider(cfg *configfile.ConfigFile, tlscacert string, tlscert string, tlskey string) session.Attachable { return &authProvider{ authConfigCache: map[string]*types.AuthConfig{}, config: cfg, seeds: &tokenSeeds{dir: config.Dir()}, loggerCache: map[string]struct{}{}, + tlscacert: tlscacert, + tlscert: tlscert, + tlskey: tlskey, } } @@ -47,6 +54,9 @@ type authProvider struct { seeds *tokenSeeds logger progresswriter.Logger loggerCache map[string]struct{} + tlscacert string + tlscert string + tlskey string // The need for this mutex is not well understood. // Without it, the docker cli on OS X hangs when @@ -89,6 +99,12 @@ func (ap *authProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequ Secret: creds.Secret, } + var httpClient = http.DefaultClient + if tc, err := ap.tlsConfig(); err == nil { + httpClient.Transport = http.DefaultTransport + httpClient.Transport.(*http.Transport).TLSClientConfig = tc + } + if creds.Secret != "" { done := func(progresswriter.SubLogger) error { return err @@ -103,7 +119,7 @@ func (ap *authProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequ } ap.mu.Unlock() // credential information is provided, use oauth POST endpoint - resp, err := authutil.FetchTokenWithOAuth(ctx, http.DefaultClient, nil, "buildkit-client", to) + resp, err := authutil.FetchTokenWithOAuth(ctx, httpClient, nil, "buildkit-client", to) if err != nil { var errStatus remoteserrors.ErrUnexpectedStatus if errors.As(err, &errStatus) { @@ -111,7 +127,7 @@ func (ap *authProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequ // As of September 2017, GCR is known to return 404. // As of February 2018, JFrog Artifactory is known to return 401. if (errStatus.StatusCode == 405 && to.Username != "") || errStatus.StatusCode == 404 || errStatus.StatusCode == 401 { - resp, err := authutil.FetchToken(ctx, http.DefaultClient, nil, to) + resp, err := authutil.FetchToken(ctx, httpClient, nil, to) if err != nil { return nil, err } @@ -123,13 +139,45 @@ func (ap *authProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequ return toTokenResponse(resp.AccessToken, resp.IssuedAt, resp.ExpiresIn), nil } // do request anonymously - resp, err := authutil.FetchToken(ctx, http.DefaultClient, nil, to) + resp, err := authutil.FetchToken(ctx, httpClient, nil, to) if err != nil { return nil, errors.Wrap(err, "failed to fetch anonymous token") } return toTokenResponse(resp.Token, resp.IssuedAt, resp.ExpiresIn), nil } +func (ap *authProvider) tlsConfig() (*tls.Config, error) { + if ap.tlscacert == "" && (ap.tlscert == "" || ap.tlskey == "") { + return nil, nil + } + tc := &tls.Config{} + if ap.tlscacert != "" { + caCert, err := os.ReadFile(ap.tlscacert) + if err != nil { + return nil, err + } + rootCAs, err := x509.SystemCertPool() + if err != nil { + if runtime.GOOS == "windows" { + rootCAs = x509.NewCertPool() + } else { + return nil, errors.Wrapf(err, "unable to get system cert pool") + } + } + rootCAs.AppendCertsFromPEM(caCert) + tc.RootCAs = rootCAs + } + + if ap.tlscert != "" && ap.tlskey != "" { + cert, err := tls.LoadX509KeyPair(ap.tlscert, ap.tlskey) + if err != nil { + return nil, errors.Wrapf(err, "failed to load keypair for %s", ap.tlscert) + } + tc.Certificates = append(tc.Certificates, cert) + } + return tc, nil +} + func (ap *authProvider) credentials(host string) (*auth.CredentialsResponse, error) { ac, err := ap.getAuthConfig(host) if err != nil { diff --git a/session/auth/authprovider/authprovider_test.go b/session/auth/authprovider/authprovider_test.go index 28b9bcfd1dd88..32f70078441db 100644 --- a/session/auth/authprovider/authprovider_test.go +++ b/session/auth/authprovider/authprovider_test.go @@ -6,9 +6,10 @@ import ( "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/types" - "github.com/moby/buildkit/session/auth" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/moby/buildkit/session/auth" ) func TestFetchTokenCaching(t *testing.T) { @@ -17,7 +18,7 @@ func TestFetchTokenCaching(t *testing.T) { dockerHubConfigfileKey: {Username: "user", RegistryToken: "hunter2"}, }, } - p := NewDockerAuthProvider(cfg).(*authProvider) + p := NewDockerAuthProvider(cfg, "", "", "").(*authProvider) res, err := p.FetchToken(context.Background(), &auth.FetchTokenRequest{Host: dockerHubRegistryHost}) require.NoError(t, err) assert.Equal(t, "hunter2", res.Token)