From c0b721b5f9d8ffcbcc1df15e20ba1fdb756c58f7 Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Fri, 24 Jun 2016 10:52:24 -0700 Subject: [PATCH] Disable extended key usage as a client for non-Swarm-master server certs Signed-off-by: Nathan LeClaire --- libmachine/cert/bootstrap.go | 14 ++++++- libmachine/cert/cert.go | 51 +++++++++++++++--------- libmachine/cert/cert_test.go | 13 +++++- libmachine/check/check_test.go | 2 +- libmachine/provision/boot2docker.go | 4 ++ libmachine/provision/fake_provisioner.go | 4 ++ libmachine/provision/generic.go | 4 ++ libmachine/provision/provisioner.go | 3 ++ libmachine/provision/utils.go | 20 +++++----- 9 files changed, 84 insertions(+), 31 deletions(-) diff --git a/libmachine/cert/bootstrap.go b/libmachine/cert/bootstrap.go index 5d11ca43..aeadaecd 100644 --- a/libmachine/cert/bootstrap.go +++ b/libmachine/cert/bootstrap.go @@ -66,7 +66,19 @@ func BootstrapCertificates(authOptions *auth.Options) error { return errors.New("The client key already exists. Please remove it or specify a different key/cert.") } - if err := GenerateCert([]string{""}, clientCertPath, clientKeyPath, caCertPath, caPrivateKeyPath, org, bits); err != nil { + // Used to generate the client certificate. + certOptions := &Options{ + Hosts: []string{""}, + CertFile: clientCertPath, + KeyFile: clientKeyPath, + CAFile: caCertPath, + CAKeyFile: caPrivateKeyPath, + Org: org, + Bits: bits, + SwarmMaster: false, + } + + if err := GenerateCert(certOptions); err != nil { return fmt.Errorf("Generating client certificate failed: %s", err) } } diff --git a/libmachine/cert/cert.go b/libmachine/cert/cert.go index b2db6cbf..45583183 100644 --- a/libmachine/cert/cert.go +++ b/libmachine/cert/cert.go @@ -21,9 +21,16 @@ import ( var defaultGenerator = NewX509CertGenerator() +type Options struct { + Hosts []string + CertFile, KeyFile, CAFile, CAKeyFile, Org string + Bits int + SwarmMaster bool +} + type Generator interface { GenerateCACertificate(certFile, keyFile, org string, bits int) error - GenerateCert(hosts []string, certFile, keyFile, caFile, caKeyFile, org string, bits int) error + GenerateCert(opts *Options) error ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error) ValidateCertificate(addr string, authOptions *auth.Options) (bool, error) } @@ -38,8 +45,8 @@ func GenerateCACertificate(certFile, keyFile, org string, bits int) error { return defaultGenerator.GenerateCACertificate(certFile, keyFile, org, bits) } -func GenerateCert(hosts []string, certFile, keyFile, caFile, caKeyFile, org string, bits int) error { - return defaultGenerator.GenerateCert(hosts, certFile, keyFile, caFile, caKeyFile, org, bits) +func GenerateCert(opts *Options) error { + return defaultGenerator.GenerateCert(opts) } func ValidateCertificate(addr string, authOptions *auth.Options) (bool, error) { @@ -150,18 +157,24 @@ func (xcg *X509CertGenerator) GenerateCACertificate(certFile, keyFile, org strin // 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 (xcg *X509CertGenerator) GenerateCert(hosts []string, certFile, keyFile, caFile, caKeyFile, org string, bits int) error { - template, err := xcg.newCertificate(org) +func (xcg *X509CertGenerator) GenerateCert(opts *Options) error { + template, err := xcg.newCertificate(opts.Org) if err != nil { return err } // client - if len(hosts) == 1 && hosts[0] == "" { + if len(opts.Hosts) == 1 && opts.Hosts[0] == "" { template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} template.KeyUsage = x509.KeyUsageDigitalSignature } else { // server - template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth} - for _, h := range hosts { + template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + if opts.SwarmMaster { + // Extend the Swarm master's server certificate + // permissions to also be able to connect to downstream + // nodes as a client. + template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageClientAuth) + } + for _, h := range opts.Hosts { if ip := net.ParseIP(h); ip != nil { template.IPAddresses = append(template.IPAddresses, ip) } else { @@ -170,12 +183,12 @@ func (xcg *X509CertGenerator) GenerateCert(hosts []string, certFile, keyFile, ca } } - tlsCert, err := tls.LoadX509KeyPair(caFile, caKeyFile) + tlsCert, err := tls.LoadX509KeyPair(opts.CAFile, opts.CAKeyFile) if err != nil { return err } - priv, err := rsa.GenerateKey(rand.Reader, bits) + priv, err := rsa.GenerateKey(rand.Reader, opts.Bits) if err != nil { return err } @@ -190,7 +203,7 @@ func (xcg *X509CertGenerator) GenerateCert(hosts []string, certFile, keyFile, ca return err } - certOut, err := os.Create(certFile) + certOut, err := os.Create(opts.CertFile) if err != nil { return err } @@ -198,7 +211,7 @@ func (xcg *X509CertGenerator) GenerateCert(hosts []string, certFile, keyFile, ca 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) + keyOut, err := os.OpenFile(opts.KeyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } @@ -212,8 +225,8 @@ func (xcg *X509CertGenerator) GenerateCert(hosts []string, certFile, keyFile, ca // ReadTLSConfig reads the tls config for a machine. func (xcg *X509CertGenerator) ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error) { caCertPath := authOptions.CaCertPath - serverCertPath := authOptions.ServerCertPath - serverKeyPath := authOptions.ServerKeyPath + clientCertPath := authOptions.ClientCertPath + clientKeyPath := authOptions.ClientKeyPath log.Debugf("Reading CA certificate from %s", caCertPath) caCert, err := ioutil.ReadFile(caCertPath) @@ -221,19 +234,19 @@ func (xcg *X509CertGenerator) ReadTLSConfig(addr string, authOptions *auth.Optio return nil, err } - log.Debugf("Reading server certificate from %s", serverCertPath) - serverCert, err := ioutil.ReadFile(serverCertPath) + log.Debugf("Reading client certificate from %s", clientCertPath) + clientCert, err := ioutil.ReadFile(clientCertPath) if err != nil { return nil, err } - log.Debugf("Reading server key from %s", serverKeyPath) - serverKey, err := ioutil.ReadFile(serverKeyPath) + log.Debugf("Reading client key from %s", clientKeyPath) + clientKey, err := ioutil.ReadFile(clientKeyPath) if err != nil { return nil, err } - return xcg.getTLSConfig(caCert, serverCert, serverKey, false) + return xcg.getTLSConfig(caCert, clientCert, clientKey, false) } // ValidateCertificate validate the certificate installed on the vm. diff --git a/libmachine/cert/cert_test.go b/libmachine/cert/cert_test.go index 50dde8fd..f5b5ecba 100644 --- a/libmachine/cert/cert_test.go +++ b/libmachine/cert/cert_test.go @@ -56,7 +56,18 @@ func TestGenerateCert(t *testing.T) { t.Fatal(err) } - if err := GenerateCert([]string{}, certPath, keyPath, caCertPath, caKeyPath, testOrg, bits); err != nil { + opts := &Options{ + Hosts: []string{}, + CertFile: certPath, + CAKeyFile: caKeyPath, + CAFile: caCertPath, + KeyFile: keyPath, + Org: testOrg, + Bits: bits, + SwarmMaster: false, + } + + if err := GenerateCert(opts); err != nil { t.Fatal(err) } diff --git a/libmachine/check/check_test.go b/libmachine/check/check_test.go index 8054704e..948c08a8 100644 --- a/libmachine/check/check_test.go +++ b/libmachine/check/check_test.go @@ -24,7 +24,7 @@ func (fcg FakeCertGenerator) GenerateCACertificate(certFile, keyFile, org string return nil } -func (fcg FakeCertGenerator) GenerateCert(hosts []string, certFile, keyFile, caFile, caKeyFile, org string, bits int) error { +func (fcg FakeCertGenerator) GenerateCert(opts *cert.Options) error { return nil } diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index 442a6b62..8b09f28c 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -128,6 +128,10 @@ func (provisioner *Boot2DockerProvisioner) GetAuthOptions() auth.Options { return provisioner.AuthOptions } +func (provisioner *Boot2DockerProvisioner) GetSwarmOptions() swarm.Options { + return provisioner.SwarmOptions +} + func (provisioner *Boot2DockerProvisioner) GenerateDockerOptions(dockerPort int) (*DockerOptions, error) { var ( engineCfg bytes.Buffer diff --git a/libmachine/provision/fake_provisioner.go b/libmachine/provision/fake_provisioner.go index 50e30c2b..7d4caaed 100644 --- a/libmachine/provision/fake_provisioner.go +++ b/libmachine/provision/fake_provisioner.go @@ -43,6 +43,10 @@ func (fp *FakeProvisioner) GetAuthOptions() auth.Options { return auth.Options{} } +func (fp *FakeProvisioner) GetSwarmOptions() swarm.Options { + return swarm.Options{} +} + func (fp *FakeProvisioner) Package(name string, action pkgaction.PackageAction) error { return nil } diff --git a/libmachine/provision/generic.go b/libmachine/provision/generic.go index 72f8bfc0..84fc173f 100644 --- a/libmachine/provision/generic.go +++ b/libmachine/provision/generic.go @@ -76,6 +76,10 @@ func (provisioner *GenericProvisioner) GetAuthOptions() auth.Options { return provisioner.AuthOptions } +func (provisioner *GenericProvisioner) GetSwarmOptions() swarm.Options { + return provisioner.SwarmOptions +} + func (provisioner *GenericProvisioner) SetOsReleaseInfo(info *OsRelease) { provisioner.OsReleaseInfo = info } diff --git a/libmachine/provision/provisioner.go b/libmachine/provision/provisioner.go index 3325fa9f..81e005a0 100644 --- a/libmachine/provision/provisioner.go +++ b/libmachine/provision/provisioner.go @@ -46,6 +46,9 @@ type Provisioner interface { // Return the auth options used to configure remote connection for the daemon. GetAuthOptions() auth.Options + // Get the swarm options associated with this host. + GetSwarmOptions() swarm.Options + // Run a package action e.g. install Package(name string, action pkgaction.PackageAction) error diff --git a/libmachine/provision/utils.go b/libmachine/provision/utils.go index 7fefb702..ee8deffd 100644 --- a/libmachine/provision/utils.go +++ b/libmachine/provision/utils.go @@ -64,6 +64,7 @@ func ConfigureAuth(p Provisioner) error { driver := p.GetDriver() machineName := driver.GetMachineName() authOptions := p.GetAuthOptions() + swarmOptions := p.GetSwarmOptions() org := mcnutils.GetUsername() + "." + machineName bits := 2048 @@ -98,15 +99,16 @@ func ConfigureAuth(p Provisioner) error { // TODO: Switch to passing just authOptions to this func // instead of all these individual fields - err = cert.GenerateCert( - hosts, - authOptions.ServerCertPath, - authOptions.ServerKeyPath, - authOptions.CaCertPath, - authOptions.CaPrivateKeyPath, - org, - bits, - ) + err = cert.GenerateCert(&cert.Options{ + Hosts: hosts, + CertFile: authOptions.ServerCertPath, + KeyFile: authOptions.ServerKeyPath, + CAFile: authOptions.CaCertPath, + CAKeyFile: authOptions.CaPrivateKeyPath, + Org: org, + Bits: bits, + SwarmMaster: swarmOptions.Master, + }) if err != nil { return fmt.Errorf("error generating server cert: %s", err)