From 4534944f6a46a0a633f49c400a0f4875dadeaeae Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Fri, 9 Jan 2015 09:52:24 -0800 Subject: [PATCH] use tls for auth Signed-off-by: Evan Hazlett --- commands.go | 41 ++--- drivers/amazonec2/amazonec2.go | 73 +------- drivers/digitalocean/digitalocean.go | 68 ++------ drivers/drivers.go | 6 +- drivers/virtualbox/virtualbox.go | 6 +- host.go | 247 ++++++++++++++------------- main.go | 10 ++ store.go | 14 +- utils/certs.go | 147 ++++++++++++++++ utils/utils.go | 26 +++ 10 files changed, 370 insertions(+), 268 deletions(-) create mode 100644 utils/certs.go create mode 100644 utils/utils.go diff --git a/commands.go b/commands.go index 327b6fd0b9..3d1ae81179 100644 --- a/commands.go +++ b/commands.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "sort" "strings" "text/tabwriter" @@ -149,7 +150,7 @@ var Commands = []cli.Command{ func cmdActive(c *cli.Context) { name := c.Args().First() - store := NewStore(c.GlobalString("storage-path")) + store := NewStore(c.GlobalString("storage-path"), c.GlobalString("auth-ca"), c.GlobalString("auth-key")) if name == "" { host, err := store.GetActive() @@ -182,25 +183,7 @@ func cmdCreate(c *cli.Context) { log.Fatal("You must specify a machine name") } - store := NewStore(c.GlobalString("storage-path")) - - exists, err := store.Exists(name) - if err != nil { - log.Fatal(err) - } - - if exists { - log.Fatal("There's already a machine with the same name") - } - - keyExists, err := drivers.PublicKeyExists() - if err != nil { - log.Fatal(err) - } - - if !keyExists { - log.Fatalf("Identity authentication public key doesn't exist at %q. Create your public key by running the \"docker\" command.", drivers.PublicKeyPath()) - } + store := NewStore(c.GlobalString("storage-path"), c.GlobalString("auth-ca"), c.GlobalString("auth-key")) host, err := store.Create(name, driver, c) if err != nil { @@ -220,7 +203,15 @@ func cmdCreate(c *cli.Context) { log.Fatalf("error setting active host: %v", err) } - log.Infof("%q has been created and is now the active machine. To point Docker at this machine, run: export DOCKER_HOST=$(machine url) DOCKER_AUTH=identity", name) + log.Infof("%q has been created and is now the active machine", name) + // TODO @ehazlett - this will change but at least show how to connect for now + log.Info("To connect, pass these args to Docker: ") + storeDir := c.GlobalString("storage-path") + caCert := filepath.Join(storeDir, name, "ca.pem") + clientCert := filepath.Join(storeDir, name, "client.pem") + clientKey := filepath.Join(storeDir, name, "client-key.pem") + log.Infof("--auth=cert --auth-ca=%s --auth-cert=%s --auth-key=%s -H $(machine url)", + caCert, clientCert, clientKey) } func cmdInspect(c *cli.Context) { @@ -249,7 +240,7 @@ func cmdKill(c *cli.Context) { func cmdLs(c *cli.Context) { quiet := c.Bool("quiet") - store := NewStore(c.GlobalString("storage-path")) + store := NewStore(c.GlobalString("storage-path"), c.GlobalString("auth-ca"), c.GlobalString("auth-key")) hostList, err := store.List() if err != nil { @@ -320,7 +311,7 @@ func cmdRm(c *cli.Context) { isError := false - store := NewStore(c.GlobalString("storage-path")) + store := NewStore(c.GlobalString("storage-path"), c.GlobalString("auth-ca"), c.GlobalString("auth-key")) for _, host := range c.Args() { if err := store.Remove(host, force); err != nil { log.Errorf("Error removing machine %s: %s", host, err) @@ -334,7 +325,7 @@ func cmdRm(c *cli.Context) { func cmdSsh(c *cli.Context) { name := c.Args().First() - store := NewStore(c.GlobalString("storage-path")) + store := NewStore(c.GlobalString("storage-path"), c.GlobalString("auth-ca"), c.GlobalString("auth-key")) if name == "" { host, err := store.GetActive() @@ -412,7 +403,7 @@ func cmdNotFound(c *cli.Context, command string) { func getHost(c *cli.Context) *Host { name := c.Args().First() - store := NewStore(c.GlobalString("storage-path")) + store := NewStore(c.GlobalString("storage-path"), c.GlobalString("auth-ca"), c.GlobalString("auth-key")) if name == "" { host, err := store.GetActive() diff --git a/drivers/amazonec2/amazonec2.go b/drivers/amazonec2/amazonec2.go index a653d92966..08ab5909d2 100644 --- a/drivers/amazonec2/amazonec2.go +++ b/drivers/amazonec2/amazonec2.go @@ -9,7 +9,6 @@ import ( "os" "os/exec" "path" - "path/filepath" "time" log "github.com/Sirupsen/logrus" @@ -23,7 +22,7 @@ import ( const ( driverName = "amazonec2" defaultRegion = "us-east-1" - defaultAMI = "ami-a00461c8" + defaultAMI = "ami-4ae27e22" defaultInstanceType = "t2.micro" defaultRootSize = 16 ipRange = "0.0.0.0/0" @@ -31,6 +30,7 @@ const ( type Driver struct { Id string + MachineName string AccessKey string SecretKey string SessionToken string @@ -48,6 +48,8 @@ type Driver struct { VpcId string SubnetId string Zone string + CaCertPath string + PrivateKeyPath string storePath string keyPath string } @@ -134,9 +136,9 @@ func GetCreateFlags() []cli.Flag { } } -func NewDriver(machineName string, storePath string) (drivers.Driver, error) { +func NewDriver(machineName string, storePath string, caCert string, privateKey string) (drivers.Driver, error) { id := generateId() - return &Driver{Id: id, MachineName: machineName, storePath: storePath}, nil + return &Driver{Id: id, MachineName: machineName, storePath: storePath, CaCertPath: caCert, PrivateKeyPath: privateKey}, nil } func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { @@ -263,72 +265,11 @@ func (d *Driver) Create() error { cmd, err = d.GetSSHCommand("if [ ! -e /usr/bin/docker ]; then curl get.docker.io | sudo sh -; fi") if err != nil { return err + } if err := cmd.Run(); err != nil { return err - } - cmd, err = d.GetSSHCommand("sudo stop docker") - if err != nil { - return err - } - if err := cmd.Run(); err != nil { - return err - } - - log.Debugf("HACK: Downloading version of Docker with identity auth...") - - cmd, err = d.GetSSHCommand("sudo curl -sS -o /usr/bin/docker https://ehazlett.s3.amazonaws.com/public/docker/linux/docker-1.4.1-136b351e-identity") - if err != nil { - return err - } - if err := cmd.Run(); err != nil { - return err - } - - log.Debugf("Updating /etc/default/docker to use identity auth...") - - cmd, err = d.GetSSHCommand("echo 'export DOCKER_OPTS=\"--auth=identity --host=tcp://0.0.0.0:2376 --host=unix:///var/run/docker.sock --auth-authorized-dir=/root/.docker/authorized-keys.d\"' | sudo tee -a /etc/default/docker") - if err != nil { - return err - } - if err := cmd.Run(); err != nil { - return err - } - - // HACK: create dir for ubuntu user to access - log.Debugf("Adding key to authorized-keys.d...") - - cmd, err = d.GetSSHCommand("sudo mkdir -p /root/.docker && sudo chown -R ubuntu /root/.docker") - if err != nil { - return err - } - if err := cmd.Run(); err != nil { - return err - } - - f, err := os.Open(filepath.Join(os.Getenv("HOME"), ".docker/public-key.json")) - if err != nil { - return err - } - defer f.Close() - - cmdString := fmt.Sprintf("sudo mkdir -p %q && sudo tee -a %q", "/root/.docker/authorized-keys.d", "/root/.docker/authorized-keys.d/docker-host.json") - cmd, err = d.GetSSHCommand(cmdString) - if err != nil { - return err - } - cmd.Stdin = f - if err := cmd.Run(); err != nil { - return err - } - - cmd, err = d.GetSSHCommand("sudo start docker") - if err != nil { - return err - } - if err := cmd.Run(); err != nil { - return err } return nil diff --git a/drivers/digitalocean/digitalocean.go b/drivers/digitalocean/digitalocean.go index e6b6a76115..837c93960e 100644 --- a/drivers/digitalocean/digitalocean.go +++ b/drivers/digitalocean/digitalocean.go @@ -19,15 +19,19 @@ import ( ) type Driver struct { - AccessToken string - DropletID int - Image string - IPAddress string - MachineName string - Region string - SSHKeyID int - Size string - storePath string + AccessToken string + DropletID int + DropletName string + Image string + MachineName string + IPAddress string + Region string + SSHKeyID int + Size string + CaCertPath string + PrivateKeyPath string + DriverKeyPath string + storePath string } func init() { @@ -67,8 +71,8 @@ func GetCreateFlags() []cli.Flag { } } -func NewDriver(machineName string, storePath string) (drivers.Driver, error) { - return &Driver{MachineName: machineName, storePath: storePath}, nil +func NewDriver(machineName string, storePath string, caCert string, privateKey string) (drivers.Driver, error) { + return &Driver{MachineName: machineName, storePath: storePath, CaCertPath: caCert, PrivateKeyPath: privateKey}, nil } func (d *Driver) DriverName() string { @@ -160,48 +164,6 @@ func (d *Driver) Create() error { return err } - log.Debugf("HACK: Downloading version of Docker with identity auth...") - - cmd, err = d.GetSSHCommand("stop docker") - if err != nil { - return err - } - if err := cmd.Run(); err != nil { - return err - } - - cmd, err = d.GetSSHCommand("curl -sS https://ehazlett.s3.amazonaws.com/public/docker/linux/docker-1.4.1-136b351e-identity > /usr/bin/docker") - if err != nil { - return err - } - if err := cmd.Run(); err != nil { - return err - } - - log.Debugf("Updating /etc/default/docker to use identity auth...") - - cmd, err = d.GetSSHCommand("echo 'export DOCKER_OPTS=\"--auth=identity --host=tcp://0.0.0.0:2376 --host=unix:///var/run/docker.sock\"' >> /etc/default/docker") - if err != nil { - return err - } - if err := cmd.Run(); err != nil { - return err - } - - log.Debugf("Adding key to authorized-keys.d...") - - if err := drivers.AddPublicKeyToAuthorizedHosts(d, "/.docker/authorized-keys.d"); err != nil { - return err - } - - cmd, err = d.GetSSHCommand("start docker") - if err != nil { - return err - } - if err := cmd.Run(); err != nil { - return err - } - return nil } diff --git a/drivers/drivers.go b/drivers/drivers.go index e4530dde01..fdbcc68d39 100644 --- a/drivers/drivers.go +++ b/drivers/drivers.go @@ -67,7 +67,7 @@ type Driver interface { // - RegisterCreateFlags: a function that takes the FlagSet for // "docker hosts create" and returns an object to pass to SetConfigFromFlags type RegisteredDriver struct { - New func(machineName string, storePath string) (Driver, error) + New func(machineName string, storePath string, caCert string, privateKey string) (Driver, error) GetCreateFlags func() []cli.Flag } @@ -92,12 +92,12 @@ func Register(name string, registeredDriver *RegisteredDriver) error { } // NewDriver creates a new driver of type "name" -func NewDriver(name string, machineName string, storePath string) (Driver, error) { +func NewDriver(name string, machineName string, storePath string, caCert string, privateKey string) (Driver, error) { driver, exists := drivers[name] if !exists { return nil, fmt.Errorf("hosts: Unknown driver %q", name) } - return driver.New(machineName, storePath) + return driver.New(machineName, storePath, caCert, privateKey) } // GetCreateFlags runs GetCreateFlags for all of the drivers and diff --git a/drivers/virtualbox/virtualbox.go b/drivers/virtualbox/virtualbox.go index ac0212fdc0..f287b3c6fe 100644 --- a/drivers/virtualbox/virtualbox.go +++ b/drivers/virtualbox/virtualbox.go @@ -32,6 +32,8 @@ type Driver struct { Memory int DiskSize int Boot2DockerURL string + CaCertPath string + PrivateKeyPath string storePath string } @@ -71,8 +73,8 @@ func GetCreateFlags() []cli.Flag { } } -func NewDriver(machineName string, storePath string) (drivers.Driver, error) { - return &Driver{MachineName: machineName, storePath: storePath}, nil +func NewDriver(machineName string, storePath string, caCert string, privateKey string) (drivers.Driver, error) { + return &Driver{MachineName: machineName, storePath: storePath, CaCertPath: caCert, PrivateKeyPath: privateKey}, nil } func (d *Driver) DriverName() string { diff --git a/host.go b/host.go index 1aef043dd3..734dc82b00 100644 --- a/host.go +++ b/host.go @@ -1,8 +1,6 @@ package main import ( - "crypto/tls" - "crypto/x509" "encoding/json" "fmt" "io/ioutil" @@ -11,11 +9,10 @@ import ( "os" "path/filepath" "regexp" - "time" log "github.com/Sirupsen/logrus" - "github.com/docker/libtrust" "github.com/docker/machine/drivers" + "github.com/docker/machine/utils" ) var ( @@ -24,10 +21,15 @@ var ( ) type Host struct { - Name string `json:"-"` - DriverName string - Driver drivers.Driver - storePath string + Name string `json:"-"` + DriverName string + Driver drivers.Driver + CaCertPath string + ServerCertPath string + ServerKeyPath string + PrivateKeyPath string + ClientCertPath string + storePath string } type hostConfig struct { @@ -47,16 +49,18 @@ func waitForDocker(addr string) error { return nil } -func NewHost(name, driverName, storePath string) (*Host, error) { - driver, err := drivers.NewDriver(driverName, name, storePath) +func NewHost(name, driverName, storePath, caCert, privateKey string) (*Host, error) { + driver, err := drivers.NewDriver(driverName, name, storePath, caCert, privateKey) if err != nil { return nil, err } return &Host{ - Name: name, - DriverName: driverName, - Driver: driver, - storePath: storePath, + Name: name, + DriverName: driverName, + Driver: driver, + CaCertPath: caCert, + PrivateKeyPath: privateKey, + storePath: storePath, }, nil } @@ -79,139 +83,156 @@ func ValidateHostName(name string) (string, error) { return name, nil } -func loadTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) { - if err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700); err != nil { - return nil, err +func (h *Host) GenerateCertificates(serverIPs []string) error { + var ( + caPathExists bool + privateKeyExists bool + org = "docker-machine" + bits = 2048 + ) + caCertPath := filepath.Join(h.storePath, "ca.pem") + privateKeyPath := filepath.Join(h.storePath, "private.pem") + + if _, err := os.Stat(h.CaCertPath); os.IsNotExist(err) { + caPathExists = false + } else { + caPathExists = true } - trustKey, err := libtrust.LoadKeyFile(trustKeyPath) - if err == libtrust.ErrKeyFileDoesNotExist { - trustKey, err = libtrust.GenerateECP256PrivateKey() - if err != nil { - return nil, fmt.Errorf("error generating key: %s", err) - } - - if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil { - return nil, fmt.Errorf("error saving key file: %s", err) - - } - - dir, file := filepath.Split(trustKeyPath) - if err := libtrust.SavePublicKey(filepath.Join(dir, "public-"+file), trustKey.PublicKey()); err != nil { - return nil, fmt.Errorf("error saving public key file: %s", err) - - } - } else if err != nil { - return nil, fmt.Errorf("error loading key file: %s", err) - + if _, err := os.Stat(h.PrivateKeyPath); os.IsNotExist(err) { + privateKeyExists = false + } else { + privateKeyExists = true } - return trustKey, nil + + if !caPathExists && !privateKeyExists { + log.Debugf("generating self-signed CA cert: %s", caCertPath) + if err := utils.GenerateCACert(caCertPath, privateKeyPath, org, bits); err != nil { + return fmt.Errorf("error generating self-signed CA cert: %s", err) + } + } else { + if err := utils.CopyFile(h.CaCertPath, caCertPath); err != nil { + return fmt.Errorf("unable to copy CA cert: %s", err) + } + if err := utils.CopyFile(h.PrivateKeyPath, privateKeyPath); err != nil { + return fmt.Errorf("unable to copy private key: %s", err) + } + } + + serverCertPath := filepath.Join(h.storePath, "server.pem") + serverKeyPath := filepath.Join(h.storePath, "server-key.pem") + + log.Debugf("generating server cert: %s", serverCertPath) + + if err := utils.GenerateCert(serverIPs, serverCertPath, serverKeyPath, caCertPath, privateKeyPath, org, bits); err != nil { + return fmt.Errorf("error generating server cert: %s", err) + } + + clientCertPath := filepath.Join(h.storePath, "client.pem") + clientKeyPath := filepath.Join(h.storePath, "client-key.pem") + log.Debugf("generating client cert: %s", clientCertPath) + if err := utils.GenerateCert([]string{""}, clientCertPath, clientKeyPath, caCertPath, privateKeyPath, org, bits); err != nil { + return fmt.Errorf("error generating client cert: %s", err) + } + + return nil } -func (h *Host) addHostToKnownHosts() error { - tlsConfig := &tls.Config{ - MinVersion: tls.VersionTLS10, - } +func (h *Host) ConfigureAuth() error { + d := h.Driver - trustKeyPath := filepath.Join(drivers.GetDockerDir(), "key.json") - knownHostsPath := filepath.Join(drivers.GetDockerDir(), "known-hosts.json") - - driverUrl, err := h.GetURL() + ip, err := d.GetIP() if err != nil { - return fmt.Errorf("unable to get machine url: %s", err) + return err } - u, err := url.Parse(driverUrl) + log.Debugf("generating certificates for %s", ip) + if err := h.GenerateCertificates([]string{ip}); err != nil { + return err + } + + serverCertPath := filepath.Join(h.storePath, "server.pem") + caCertPath := filepath.Join(h.storePath, "ca.pem") + serverKeyPath := filepath.Join(h.storePath, "server-key.pem") + + cmd, err := d.GetSSHCommand("sudo stop docker") if err != nil { - return fmt.Errorf("unable to parse machine url") + return err + } + if err := cmd.Run(); err != nil { + return err } - if u.Scheme == "unix" { - return nil - } - - addr := u.Host - proto := "tcp" - - trustKey, err := loadTrustKey(trustKeyPath) + cmd, err = d.GetSSHCommand("sudo mkdir -p /etc/docker") if err != nil { - return fmt.Errorf("unable to load trust key: %s", err) + return err + } + if err := cmd.Run(); err != nil { + return err } - knownHosts, err := libtrust.LoadKeySetFile(knownHostsPath) + // upload certs and configure TLS auth + caCert, err := ioutil.ReadFile(caCertPath) if err != nil { - return fmt.Errorf("could not load trusted hosts file: %s", err) + return err } - allowedHosts, err := libtrust.FilterByHosts(knownHosts, addr, false) + serverCert, err := ioutil.ReadFile(serverCertPath) if err != nil { - return fmt.Errorf("error filtering hosts: %s", err) + return err } - certPool, err := libtrust.GenerateCACertPool(trustKey, allowedHosts) + serverKey, err := ioutil.ReadFile(serverKeyPath) if err != nil { - return fmt.Errorf("Could not create CA pool: %s", err) + return err } - tlsConfig.ServerName = "docker" - tlsConfig.RootCAs = certPool - - x509Cert, err := libtrust.GenerateSelfSignedClientCert(trustKey) + cmd, err = d.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a /etc/docker/ca.pem", string(caCert))) if err != nil { - return fmt.Errorf("certificate generation error: %s", err) + return err + } + if err := cmd.Run(); err != nil { + return err } - tlsConfig.Certificates = []tls.Certificate{{ - Certificate: [][]byte{x509Cert.Raw}, - PrivateKey: trustKey.CryptoPrivateKey(), - Leaf: x509Cert, - }} - - tlsConfig.InsecureSkipVerify = true - - log.Debugf("waiting for Docker to become available on %s", addr) - if err := waitForDocker(addr); err != nil { - return fmt.Errorf("unable to connect to Docker daemon: %s", err) - } - testConn, err := tls.Dial(proto, addr, tlsConfig) + cmd, err = d.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a /etc/docker/server-key.pem", string(serverKey))) if err != nil { - return fmt.Errorf("tls Handshake error: %s", err) + return err + } + if err := cmd.Run(); err != nil { + return err } - opts := x509.VerifyOptions{ - Roots: tlsConfig.RootCAs, - CurrentTime: time.Now(), - DNSName: tlsConfig.ServerName, - Intermediates: x509.NewCertPool(), + cmd, err = d.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a /etc/docker/server.pem", string(serverCert))) + if err != nil { + return err + } + if err := cmd.Run(); err != nil { + return err } - certs := testConn.ConnectionState().PeerCertificates - for i, cert := range certs { - if i == 0 { - continue - } - opts.Intermediates.AddCert(cert) + cmd, err = d.GetSSHCommand(`echo 'export DOCKER_OPTS=" \ + --tlsverify \ + --tlscacert=/etc/docker/ca.pem \ + --tlskey=/etc/docker/server-key.pem \ + --tlscert=/etc/docker/server.pem \ + --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2376"' | sudo tee -a /etc/default/docker`) + if err != nil { + return err + } + if err := cmd.Run(); err != nil { + return err } - if _, err := certs[0].Verify(opts); err != nil { - if _, ok := err.(x509.UnknownAuthorityError); ok { - pubKey, err := libtrust.FromCryptoPublicKey(certs[0].PublicKey) - if err != nil { - return fmt.Errorf("error extracting public key from cert: %s", err) - } - - pubKey.AddExtendedField("hosts", []string{addr}) - - log.Debugf("Adding machine to known hosts: %s", addr) - - if err := libtrust.AddKeySetFile(knownHostsPath, pubKey); err != nil { - return fmt.Errorf("error adding machine to known hosts: %s", err) - } - } + cmd, err = d.GetSSHCommand("sudo start docker") + if err != nil { + return err + } + if err := cmd.Run(); err != nil { + return err } - testConn.Close() return nil } @@ -224,10 +245,6 @@ func (h *Host) Create(name string) error { return err } - if err := h.addHostToKnownHosts(); err != nil { - return err - } - return nil } @@ -279,7 +296,7 @@ func (h *Host) LoadConfig() error { return err } - driver, err := drivers.NewDriver(config.DriverName, h.Name, h.storePath) + driver, err := drivers.NewDriver(config.DriverName, h.Name, h.storePath, h.CaCertPath, h.PrivateKeyPath) if err != nil { return err } diff --git a/main.go b/main.go index 62ed9bad6c..b45dcf7dee 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,16 @@ func main() { Name: "storage-path", Usage: "Configures storage path", }, + cli.StringFlag{ + EnvVar: "MACHINE_AUTH_CA", + Name: "auth-ca", + Usage: "CA to verify remotes against", + }, + cli.StringFlag{ + EnvVar: "MACHINE_AUTH_PRIVATE_KEY", + Name: "auth-key", + Usage: "Private key to generate certificates", + }, } app.Run(os.Args) diff --git a/store.go b/store.go index 9905e2fbd0..61a4025977 100644 --- a/store.go +++ b/store.go @@ -12,15 +12,17 @@ import ( // Store persists hosts on the filesystem type Store struct { - Path string + Path string + CaCertPath string + PrivateKeyPath string } -func NewStore(rootPath string) *Store { +func NewStore(rootPath string, caCert string, privateKey string) *Store { if rootPath == "" { rootPath = filepath.Join(drivers.GetHomeDir(), ".docker", "machines") } - return &Store{Path: rootPath} + return &Store{Path: rootPath, CaCertPath: caCert, PrivateKeyPath: privateKey} } func (s *Store) Create(name string, driverName string, flags drivers.DriverOptions) (*Host, error) { @@ -34,7 +36,7 @@ func (s *Store) Create(name string, driverName string, flags drivers.DriverOptio hostPath := filepath.Join(s.Path, name) - host, err := NewHost(name, driverName, hostPath) + host, err := NewHost(name, driverName, hostPath, s.CaCertPath, s.PrivateKeyPath) if err != nil { return host, err } @@ -56,6 +58,10 @@ func (s *Store) Create(name string, driverName string, flags drivers.DriverOptio return host, err } + if err := host.ConfigureAuth(); err != nil { + return host, err + } + return host, nil } diff --git a/utils/certs.go b/utils/certs.go new file mode 100644 index 0000000000..9e8084d84e --- /dev/null +++ b/utils/certs.go @@ -0,0 +1,147 @@ +package utils + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "os" + "time" +) + +func newCertificate(org string) (*x509.Certificate, error) { + notBefore := time.Now() + notAfter := notBefore.Add(time.Hour * 24 * 1080) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, err + } + + return &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{org}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + BasicConstraintsValid: true, + }, nil + +} + +// GenerateCACert generates a new certificate authority from the specified org +// and bit size and stores the resulting certificate and key file +// in the arguments. +func GenerateCACert(certFile, keyFile, org string, bits int) error { + template, err := newCertificate(org) + if err != nil { + return err + } + + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + + priv, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return err + } + + derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv) + if err != nil { + return err + } + + certOut, err := os.Create(certFile) + if err != nil { + return err + } + + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + + keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + + } + + pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) + keyOut.Close() + + return nil +} + +// GenerateCert generates a new certificate signed using the provided +// certificate authority files and stores the result in the certificate +// file and key provided. The provided host names are set to the +// appropriate certificate fields. +func GenerateCert(hosts []string, certFile, keyFile, caFile, caKeyFile, org string, bits int) error { + template, err := newCertificate(org) + if err != nil { + return err + } + // client + if len(hosts) == 1 && hosts[0] == "" { + template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} + template.KeyUsage = x509.KeyUsageDigitalSignature + } else { // server + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + } + + tlsCert, err := tls.LoadX509KeyPair(caFile, caKeyFile) + if err != nil { + return err + + } + + priv, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return err + + } + + x509Cert, err := x509.ParseCertificate(tlsCert.Certificate[0]) + if err != nil { + return err + } + + derBytes, err := x509.CreateCertificate(rand.Reader, template, x509Cert, &priv.PublicKey, tlsCert.PrivateKey) + if err != nil { + return err + } + + certOut, err := os.Create(certFile) + if err != nil { + return err + + } + + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + + keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + + } + + pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) + keyOut.Close() + + return nil +} diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000000..88809b56bd --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,26 @@ +package utils + +import ( + "io" + "os" +) + +func CopyFile(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + + if _, err = io.Copy(out, in); err != nil { + return err + } + + return nil +}