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

transport: add possibility to use TLS secured transport layer #236

Merged
merged 3 commits into from
Dec 21, 2017
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
63 changes: 62 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package client

import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"time"

controlapi "github.com/moby/buildkit/api/services/control"
"github.com/moby/buildkit/util/appdefaults"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

type Client struct {
Expand All @@ -18,15 +22,26 @@ type ClientOpt interface{}
// New returns a new buildkit client. Address can be empty for the system-default address.
func New(address string, opts ...ClientOpt) (*Client, error) {
gopts := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithTimeout(30 * time.Second),
grpc.WithDialer(dialer),
grpc.FailOnNonTempDialError(true),
}
needWithInsecure := true
for _, o := range opts {
if _, ok := o.(*withBlockOpt); ok {
gopts = append(gopts, grpc.WithBlock(), grpc.FailOnNonTempDialError(true))
}
if credInfo, ok := o.(*withCredentials); ok {
opt, err := loadCredentials(credInfo)
if err != nil {
return nil, err
}
gopts = append(gopts, opt)
needWithInsecure = false
}
}
if needWithInsecure {
gopts = append(gopts, grpc.WithInsecure())
}
if address == "" {
address = appdefaults.Address
Expand Down Expand Up @@ -54,3 +69,49 @@ type withBlockOpt struct{}
func WithBlock() ClientOpt {
return &withBlockOpt{}
}

type withCredentials struct {
ServerName string
CACert string
Cert string
Key string
}

// WithCredentials configures the TLS parameters of the client.
// Arguments:
// * serverName: specifies the name of the target server
// * ca: specifies the filepath of the CA certificate to use for verification
// * cert: specifies the filepath of the client certificate
// * key: specifies the filepath of the client key
func WithCredentials(serverName, ca, cert, key string) ClientOpt {
return &withCredentials{serverName, ca, cert, key}
}

func loadCredentials(opts *withCredentials) (grpc.DialOption, error) {
ca, err := ioutil.ReadFile(opts.CACert)
if err != nil {
return nil, errors.Wrap(err, "could not read ca certificate")
}

certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(ca); !ok {
return nil, errors.New("failed to append ca certs")
}

cfg := &tls.Config{
ServerName: opts.ServerName,
RootCAs: certPool,
}

// we will produce an error if the user forgot about either cert or key if at least one is specified
if opts.Cert != "" || opts.Key != "" {
cert, err := tls.LoadX509KeyPair(opts.Cert, opts.Key)
if err != nil {
return nil, errors.Wrap(err, "could not read certificate/key")
}
cfg.Certificates = []tls.Certificate{cert}
cfg.BuildNameToCertificate()
}

return grpc.WithTransportCredentials(credentials.NewTLS(cfg)), nil
}
41 changes: 39 additions & 2 deletions cmd/buildctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"net/url"
"os"

"github.com/moby/buildkit/client"
Expand All @@ -28,9 +29,29 @@ func main() {
},
cli.StringFlag{
Name: "addr",
Usage: "listening address",
Usage: "buildkitd address",
Value: defaultAddress,
},
cli.StringFlag{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the use case of this one ? a certificate issued for another address than the specified addr ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imagine you have your certificates generated for a specific addressd and your buildserver is running, but you need to access it directly via IP because your DNS is broken or you have setup some port-forwarding to debug things in for example a k8s deployment.

Then it would be usefull to be able to tell the client which name it should expect in the servers certificate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok 😛 sounds fair 👼

Name: "tlsservername",
Usage: "buildkitd server name for certificate validation",
Value: "",
},
cli.StringFlag{
Name: "tlscacert",
Usage: "CA certificate for validation",
Value: "",
},
cli.StringFlag{
Name: "tlscert",
Usage: "client certificate",
Value: "",
},
cli.StringFlag{
Name: "tlskey",
Usage: "client key",
Value: "",
},
}

app.Commands = []cli.Command{
Expand Down Expand Up @@ -62,5 +83,21 @@ func main() {
}

func resolveClient(c *cli.Context) (*client.Client, error) {
return client.New(c.GlobalString("addr"), client.WithBlock())
serverName := c.GlobalString("tlsservername")
if serverName == "" {
// guess servername as hostname of target address
uri, err := url.Parse(c.GlobalString("addr"))
if err != nil {
return nil, err
}
serverName = uri.Hostname()
}
caCert := c.GlobalString("tlscacert")
cert := c.GlobalString("tlscert")
key := c.GlobalString("tlskey")
opts := []client.ClientOpt{client.WithBlock()}
if caCert != "" || cert != "" || key != "" {
opts = append(opts, client.WithCredentials(serverName, caCert, cert, key))
}
return client.New(c.GlobalString("addr"), opts...)
}
65 changes: 63 additions & 2 deletions cmd/buildkitd/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package main

import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
Expand All @@ -27,6 +30,7 @@ import (
"golang.org/x/net/context"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

type workerInitializerOpt struct {
Expand Down Expand Up @@ -79,6 +83,18 @@ func main() {
Usage: "debugging address (eg. 0.0.0.0:6060)",
Value: "",
},
cli.StringFlag{
Name: "tlscert",
Usage: "certificate file to use",
},
cli.StringFlag{
Name: "tlskey",
Usage: "key file to use",
},
cli.StringFlag{
Name: "tlscacert",
Usage: "ca certificate to verify clients",
},
}

app.Flags = append(app.Flags, appFlags...)
Expand All @@ -91,8 +107,15 @@ func main() {
return err
}
}

server := grpc.NewServer(unaryInterceptor(ctx))
opts := []grpc.ServerOption{unaryInterceptor(ctx)}
creds, err := serverCredentials(c)
if err != nil {
return err
}
if creds != nil {
opts = append(opts, creds)
}
server := grpc.NewServer(opts...)

// relative path does not work with nightlyone/lockfile
root, err := filepath.Abs(c.GlobalString("root"))
Expand Down Expand Up @@ -214,6 +237,44 @@ func unaryInterceptor(globalCtx context.Context) grpc.ServerOption {
})
}

func serverCredentials(c *cli.Context) (grpc.ServerOption, error) {
certFile := c.GlobalString("tlscert")
keyFile := c.GlobalString("tlskey")
caFile := c.GlobalString("tlscacert")
if certFile == "" && keyFile == "" {
return nil, nil
}
err := errors.New("you must specify key and cert file if one is specified")
if certFile == "" {
return nil, err
}
if keyFile == "" {
return nil, err
}
certificate, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, errors.Wrap(err, "could not load server key pair")
}
tlsConf := &tls.Config{
Certificates: []tls.Certificate{certificate},
}
if caFile != "" {
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, errors.Wrap(err, "could not read ca certificate")
}
// Append the client certificates from the CA
if ok := certPool.AppendCertsFromPEM(ca); !ok {
return nil, errors.New("failed to append ca cert")
}
tlsConf.ClientAuth = tls.RequireAndVerifyClientCert
tlsConf.ClientCAs = certPool
}
creds := grpc.Creds(credentials.NewTLS(tlsConf))
return creds, nil
}

func newController(c *cli.Context, root string) (*control.Controller, error) {
sessionManager, err := session.NewManager()
if err != nil {
Expand Down