diff --git a/commands/create.go b/commands/create.go index 4b13bfc384..bbe4bd71c2 100644 --- a/commands/create.go +++ b/commands/create.go @@ -115,6 +115,11 @@ var ( Usage: "addr to advertise for Swarm (default: detect and use the machine IP)", Value: "", }, + cli.StringSliceFlag{ + Name: "tls-san", + Usage: "Support extra SANs for TLS certs", + Value: &cli.StringSlice{}, + }, } ) @@ -178,6 +183,7 @@ func cmdCreateInner(c CommandLine) error { ServerCertPath: filepath.Join(mcndirs.GetMachineDir(), name, "server.pem"), ServerKeyPath: filepath.Join(mcndirs.GetMachineDir(), name, "server-key.pem"), StorePath: filepath.Join(mcndirs.GetMachineDir(), name), + ServerCertSANs: c.StringSlice("tls-san"), }, EngineOptions: &engine.Options{ ArbitraryFlags: c.StringSlice("engine-opt"), @@ -355,14 +361,14 @@ func getDriverOpts(c CommandLine, mcnflags []mcnflag.Flag) drivers.DriverOptions for _, name := range c.FlagNames() { getter, ok := c.Generic(name).(flag.Getter) - if !ok { + if ok { + driverOpts.Values[name] = getter.Get() + } else { // TODO: This is pretty hacky. StringSlice is the only // type so far we have to worry about which is not a // Getter, though. driverOpts.Values[name] = c.StringSlice(name) - continue } - driverOpts.Values[name] = getter.Get() } return driverOpts diff --git a/libmachine/auth/auth.go b/libmachine/auth/auth.go index b375e9eee3..86ae79d232 100644 --- a/libmachine/auth/auth.go +++ b/libmachine/auth/auth.go @@ -11,7 +11,7 @@ type Options struct { ServerCertRemotePath string ServerKeyRemotePath string ClientCertPath string - + ServerCertSANs []string // StorePath is left in for historical reasons, but not really meant to // be used directly. StorePath string diff --git a/libmachine/provision/utils.go b/libmachine/provision/utils.go index 2595d6f771..0681c648c7 100644 --- a/libmachine/provision/utils.go +++ b/libmachine/provision/utils.go @@ -85,17 +85,20 @@ func ConfigureAuth(p Provisioner) error { return fmt.Errorf("Copying key.pem to machine dir failed: %s", err) } - log.Debugf("generating server cert: %s ca-key=%s private-key=%s org=%s", + // The Host IP is always added to the certificate's SANs list + hosts := append(authOptions.ServerCertSANs, ip, "localhost") + log.Debugf("generating server cert: %s ca-key=%s private-key=%s org=%s san=%s", authOptions.ServerCertPath, authOptions.CaCertPath, authOptions.CaPrivateKeyPath, org, + hosts, ) // TODO: Switch to passing just authOptions to this func // instead of all these individual fields err = cert.GenerateCert( - []string{ip, "localhost"}, + hosts, authOptions.ServerCertPath, authOptions.ServerKeyPath, authOptions.CaCertPath, diff --git a/test/integration/core/certs-extra-san.bats b/test/integration/core/certs-extra-san.bats new file mode 100644 index 0000000000..4973fb5259 --- /dev/null +++ b/test/integration/core/certs-extra-san.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + + +@test "$DRIVER: create" { + run machine create --tls-san foo.bar.tld --tls-san 10.42.42.42 -d $DRIVER $NAME + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: verify that server cert contains the extra SANs" { + machine ssh $NAME -- openssl x509 -in /var/lib/boot2docker/server.pem -text | grep 'DNS:foo.bar.tld' + machine ssh $NAME -- openssl x509 -in /var/lib/boot2docker/server.pem -text | grep 'IP Address:10.42.42.42' +} + +@test "$DRIVER: verify that server cert SANs are still there after 'regenerate-certs'" { + machine regenerate-certs -f $NAME + machine ssh $NAME -- openssl x509 -in /var/lib/boot2docker/server.pem -text | grep 'DNS:foo.bar.tld' + machine ssh $NAME -- openssl x509 -in /var/lib/boot2docker/server.pem -text | grep 'IP Address:10.42.42.42' +}