[lxc-devel] [lxd/master] Generate the LXD client certificate on-demand

sean-jc on Github lxc-bot at linuxcontainers.org
Wed Jun 1 20:07:17 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 858 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160601/275d86fc/attachment.bin>
-------------- next part --------------
From 20ac91e7148ef36ae3a321c6e8d04b593404dcdd Mon Sep 17 00:00:00 2001
From: Sean Christopherson <sean.j.christopherson at intel.com>
Date: Wed, 1 Jun 2016 10:12:11 -0700
Subject: [PATCH] Generate the LXD client certificate on-demand

Delay certificate generation until the client attempts to communicate
with with a remote server.

Change the check for the 'first time using LXD' message to key off the
existence of the client's config directory (create the directory after
the help message has been printed).  Invert the check for the existence
of /var/lib/lxd when printing the help message; the message should be
printed if /var/lib/lxd missing, which implies that 'lxd init' has not
been run.

Fixes #1851

Signed-off-by: Sean Christopherson <sean.j.christopherson at intel.com>
---
 client.go             | 36 ++++++++++++++++++++----------------
 lxc/main.go           | 21 ++++++++++-----------
 test/main.sh          | 15 +++++++++++++++
 test/suites/basic.sh  | 13 +------------
 test/suites/remote.sh | 13 +------------
 5 files changed, 47 insertions(+), 51 deletions(-)

diff --git a/client.go b/client.go
index d1e08cb..addd9e5 100644
--- a/client.go
+++ b/client.go
@@ -23,6 +23,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/i18n"
 )
 
 // Client can talk to a LXD daemon.
@@ -164,27 +165,30 @@ func NewClient(config *Config, remote string) (*Client, error) {
 			info.RemoteConfig.Addr = fmt.Sprintf("unix:%s", shared.VarPath("unix.socket"))
 		}
 	} else {
-		// Read the client certificate (if it exists)
-		clientCertPath := path.Join(config.ConfigDir, "client.crt")
-		if shared.PathExists(clientCertPath) {
-			certBytes, err := ioutil.ReadFile(clientCertPath)
-			if err != nil {
-				return nil, err
-			}
 
-			info.ClientPEMCert = string(certBytes)
+		certf := config.ConfigPath("client.crt")
+		keyf := config.ConfigPath("client.key")
+
+		if !shared.PathExists(certf) || !shared.PathExists(keyf) {
+			fmt.Fprintf(os.Stderr, i18n.G("Generating a client certificate. This may take a minute...")+"\n")
 		}
 
-		// Read the client key (if it exists)
-		clientKeyPath := path.Join(config.ConfigDir, "client.key")
-		if shared.PathExists(clientKeyPath) {
-			keyBytes, err := ioutil.ReadFile(clientKeyPath)
-			if err != nil {
-				return nil, err
-			}
+		err := shared.FindOrGenCert(certf, keyf)
+		if err != nil {
+			return nil, err
+		}
+
+		certBytes, err := ioutil.ReadFile(certf)
+		if err != nil {
+			return nil, err
+		}
+		info.ClientPEMCert = string(certBytes)
 
-			info.ClientPEMKey = string(keyBytes)
+		keyBytes, err := ioutil.ReadFile(keyf)
+		if err != nil {
+			return nil, err
 		}
+		info.ClientPEMKey = string(keyBytes)
 
 		// Read the server certificate (if it exists)
 		serverCertPath := config.ServerCertPath(remote)
diff --git a/lxc/main.go b/lxc/main.go
index fa49434..047e660 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -131,21 +131,20 @@ func run() error {
 		return err
 	}
 
-	certf := config.ConfigPath("client.crt")
-	keyf := config.ConfigPath("client.key")
-
-	if !*forceLocal && os.Args[0] != "help" && os.Args[0] != "version" && (!shared.PathExists(certf) || !shared.PathExists(keyf)) {
-		fmt.Fprintf(os.Stderr, i18n.G("Generating a client certificate. This may take a minute...")+"\n")
-
-		err = shared.FindOrGenCert(certf, keyf)
+	// If the user is running a command that will attempt to connect to the daemon and this
+	// is the first time the client has been run by the user, then check to see if LXD has
+	// been properly configured.  The existence of the LXD var directory and LXC ConfigDir
+	// are good indicators of LXD initialization and this not being the client's first run.
+	if os.Args[0] != "help" && os.Args[0] != "version" && !shared.PathExists(shared.VarPath("")) && !shared.PathExists(config.ConfigDir) {
+
+		// Create the config dir so that we don't get in here again for this user.
+		err = os.MkdirAll(config.ConfigDir, 0750)
 		if err != nil {
 			return err
 		}
 
-		if shared.PathExists("/var/lib/lxd/") {
-			fmt.Fprintf(os.Stderr, i18n.G("If this is your first time using LXD, you should also run: sudo lxd init")+"\n")
-			fmt.Fprintf(os.Stderr, i18n.G("To start your first container, try: lxc launch ubuntu:16.04")+"\n\n")
-		}
+		fmt.Fprintf(os.Stderr, i18n.G("If this is your first time using LXD, you should also run: sudo lxd init")+"\n")
+		fmt.Fprintf(os.Stderr, i18n.G("To start your first container, try: lxc launch ubuntu:16.04")+"\n\n")
 	}
 
 	err = cmd.run(config, gnuflag.Args())
diff --git a/test/main.sh b/test/main.sh
index fa22aa3..f745a09 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -129,6 +129,21 @@ lxc_remote() {
   eval "${cmd}"
 }
 
+gen_cert() {
+  # Temporarily move the existing cert to trick LXC into generating
+  # a second cert.  LXC will only generate a cert if communication
+  # with a remote server is likely, so ping the remote specified in
+  # ${1} to trigger cert creation.
+  [ -f "${LXD_CONF}/${2}.crt" ] && return
+  mv "${LXD_CONF}/client.crt" "${LXD_CONF}/client.crt.bak"
+  mv "${LXD_CONF}/client.key" "${LXD_CONF}/client.key.bak"
+  lxc_remote finger "${1}"
+  mv "${LXD_CONF}/client.crt" "${LXD_CONF}/${2}.crt"
+  mv "${LXD_CONF}/client.key" "${LXD_CONF}/${2}.key"
+  mv "${LXD_CONF}/client.crt.bak" "${LXD_CONF}/client.crt"
+  mv "${LXD_CONF}/client.key.bak" "${LXD_CONF}/client.key"
+}
+
 my_curl() {
   curl -k -s --cert "${LXD_CONF}/client.crt" --key "${LXD_CONF}/client.key" "$@"
 }
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 691f8e5..d32e5c1 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -1,16 +1,5 @@
 #!/bin/sh
 
-gen_third_cert() {
-  [ -f "${LXD_CONF}/client3.crt" ] && return
-  mv "${LXD_CONF}/client.crt" "${LXD_CONF}/client.crt.bak"
-  mv "${LXD_CONF}/client.key" "${LXD_CONF}/client.key.bak"
-  lxc_remote list > /dev/null 2>&1
-  mv "${LXD_CONF}/client.crt" "${LXD_CONF}/client3.crt"
-  mv "${LXD_CONF}/client.key" "${LXD_CONF}/client3.key"
-  mv "${LXD_CONF}/client.crt.bak" "${LXD_CONF}/client.crt"
-  mv "${LXD_CONF}/client.key.bak" "${LXD_CONF}/client.key"
-}
-
 test_basic_usage() {
   ensure_import_testimage
   ensure_has_localhost_remote "${LXD_ADDR}"
@@ -82,7 +71,7 @@ test_basic_usage() {
   lxc delete foo
 
   # gen untrusted cert
-  gen_third_cert
+  gen_cert localhost client3
 
   # don't allow requests without a cert to get trusted data
   curl -k -s -X GET "https://${LXD_ADDR}/1.0/containers/foo" | grep 403
diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index ee04ad1..af409a9 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -1,16 +1,5 @@
 #!/bin/sh
 
-gen_second_cert() {
-  [ -f "${LXD_CONF}/client2.crt" ] && return
-  mv "${LXD_CONF}/client.crt" "${LXD_CONF}/client.crt.bak"
-  mv "${LXD_CONF}/client.key" "${LXD_CONF}/client.key.bak"
-  lxc_remote list > /dev/null 2>&1
-  mv "${LXD_CONF}/client.crt" "${LXD_CONF}/client2.crt"
-  mv "${LXD_CONF}/client.key" "${LXD_CONF}/client2.key"
-  mv "${LXD_CONF}/client.crt.bak" "${LXD_CONF}/client.crt"
-  mv "${LXD_CONF}/client.key.bak" "${LXD_CONF}/client.key"
-}
-
 test_remote_url() {
   for url in "${LXD_ADDR}" "https://${LXD_ADDR}"; do
     lxc_remote remote add test "${url}" --accept-certificate --password foo
@@ -58,7 +47,7 @@ test_remote_admin() {
 
   # we just re-add our cert under a different name to test the cert
   # manipulation mechanism.
-  gen_second_cert
+  gen_cert localhost client2
 
   # Test for #623
   lxc_remote remote add test-623 "${LXD_ADDR}" --accept-certificate --password foo


More information about the lxc-devel mailing list