[lxc-devel] [lxd/master] Passwordless PKI mode

monstermunchkin on Github lxc-bot at linuxcontainers.org
Fri Mar 20 17:49:28 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200320/3efac81d/attachment-0001.bin>
-------------- next part --------------
From 31ba7377e590534ff36ccd342f0daff4ae154270 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 20 Mar 2020 18:24:54 +0100
Subject: [PATCH 1/8] shared/version/api: Add trust_ca_certificates

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 shared/version/api.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/shared/version/api.go b/shared/version/api.go
index e004cf6438..e06f0d8a7e 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -196,6 +196,7 @@ var APIExtensions = []string{
 	"projects_restrictions",
 	"custom_volume_snapshot_expiry",
 	"volume_snapshot_scheduling",
+	"trust_ca_certificates",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From 78eed95e0012a5fb26eb735ccc4c6a60924af0dc Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 20 Mar 2020 18:24:13 +0100
Subject: [PATCH 2/8] doc: Add core.trust_ca_certificates

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 doc/api-extensions.md | 7 +++++++
 doc/server.md         | 1 +
 2 files changed, 8 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index cb8fe50bfc..6dbd0a8b9c 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -962,3 +962,10 @@ Expiry dates can be set individually, or by setting the `snapshots.expiry` confi
 This adds support for custom volume snapshot scheduling. It introduces two new
 configuration keys: `snapshots.schedule` and
 `snapshots.pattern`. Snapshots can be created automatically up to every minute.
+
+## trust\_ca\_certificates
+This allows for checking client certificates trusted by the provided CA (`server.ca`).
+It can be enabled by setting `core.trust_ca_certificates` to true.
+If enabled, it will perform the check, and bypass the trusted password if true.
+An exception will be made if the connecting client certificate is in the provided CRL (`ca.crl`).
+In this case, it will ask for the password.
diff --git a/doc/server.md b/doc/server.md
index 560dd168fe..28157d0bd8 100644
--- a/doc/server.md
+++ b/doc/server.md
@@ -33,6 +33,7 @@ core.https\_allowed\_origin         | string    | global    | -         | -
 core.proxy\_https                   | string    | global    | -         | -                                 | https proxy to use, if any (falls back to HTTPS\_PROXY environment variable)
 core.proxy\_http                    | string    | global    | -         | -                                 | http proxy to use, if any (falls back to HTTP\_PROXY environment variable)
 core.proxy\_ignore\_hosts           | string    | global    | -         | -                                 | hosts which don't need the proxy for use (similar format to NO\_PROXY, e.g. 1.2.3.4,1.2.3.5, falls back to NO\_PROXY environment variable)
+core.trust\_ca\_certificates        | boolean   | global    | -         | -                                 | Whether to check for client certificates trusted by the CA
 core.trust\_password                | string    | global    | -         | -                                 | Password to be provided by clients to setup a trust
 images.auto\_update\_cached         | boolean   | global    | true      | -                                 | Whether to automatically update any image that LXD caches
 images.auto\_update\_interval       | integer   | global    | 6         | -                                 | Interval in hours at which to look for update to cached images (0 disables it)

From 786c3f87d94841e9cce915a585db89d67fd43756 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 20 Mar 2020 16:58:16 +0100
Subject: [PATCH 3/8] lxd/cluster/config: Add core.trust_ca_certificates

This adds the core.trust_ca_certificates config key.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/cluster/config.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/cluster/config.go b/lxd/cluster/config.go
index 6791a20ae6..fb39d8a08d 100644
--- a/lxd/cluster/config.go
+++ b/lxd/cluster/config.go
@@ -244,6 +244,7 @@ var ConfigSchema = config.Schema{
 	"core.proxy_https":               {},
 	"core.proxy_ignore_hosts":        {},
 	"core.trust_password":            {Hidden: true, Setter: passwordSetter},
+	"core.trust_ca_certificates":     {Type: config.Bool},
 	"candid.api.key":                 {},
 	"candid.api.url":                 {},
 	"candid.domains":                 {},

From 771b062f63c290a2a05eb38d38eb5ea46ec7852e Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 18 Mar 2020 16:39:50 +0100
Subject: [PATCH 4/8] lxd/main: Add option for trusting CA-signed client
 certificates

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/cluster/config.go        |  6 ++++++
 lxd/main_init.go             | 22 ++++++++++++----------
 lxd/main_init_auto.go        |  8 ++++++++
 lxd/main_init_interactive.go |  1 +
 4 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/lxd/cluster/config.go b/lxd/cluster/config.go
index fb39d8a08d..1bb8ac8a09 100644
--- a/lxd/cluster/config.go
+++ b/lxd/cluster/config.go
@@ -64,6 +64,12 @@ func (c *Config) TrustPassword() string {
 	return c.m.GetString("core.trust_password")
 }
 
+// TrustCACertificates returns whether client certificates are checked
+// against a CA.
+func (c *Config) TrustCACertificates() bool {
+	return c.m.GetBool("core.trust_ca_certificates")
+}
+
 // CandidServer returns all the Candid settings needed to connect to a server.
 func (c *Config) CandidServer() (string, string, int64, string) {
 	return c.m.GetString("candid.api.url"),
diff --git a/lxd/main_init.go b/lxd/main_init.go
index 4c92d51d7f..1b368a3d87 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -26,13 +26,14 @@ type cmdInit struct {
 	flagPreseed bool
 	flagDump    bool
 
-	flagNetworkAddress  string
-	flagNetworkPort     int
-	flagStorageBackend  string
-	flagStorageDevice   string
-	flagStorageLoopSize int
-	flagStoragePool     string
-	flagTrustPassword   string
+	flagNetworkAddress      string
+	flagNetworkPort         int
+	flagStorageBackend      string
+	flagStorageDevice       string
+	flagStorageLoopSize     int
+	flagStoragePool         string
+	flagTrustPassword       string
+	flagTrustCACertificates bool
 }
 
 func (c *cmdInit) Command() *cobra.Command {
@@ -45,7 +46,7 @@ func (c *cmdInit) Command() *cobra.Command {
 	cmd.Example = `  init --preseed
   init --auto [--network-address=IP] [--network-port=8443] [--storage-backend=dir]
               [--storage-create-device=DEVICE] [--storage-create-loop=SIZE]
-              [--storage-pool=POOL] [--trust-password=PASSWORD]
+              [--storage-pool=POOL] [--trust-password=PASSWORD] [--trust-ca-certificates]
   init --dump
 `
 	cmd.RunE = c.Run
@@ -60,6 +61,7 @@ func (c *cmdInit) Command() *cobra.Command {
 	cmd.Flags().IntVar(&c.flagStorageLoopSize, "storage-create-loop", -1, "Setup loop based storage with SIZE in GB"+"``")
 	cmd.Flags().StringVar(&c.flagStoragePool, "storage-pool", "", "Storage pool to use or create"+"``")
 	cmd.Flags().StringVar(&c.flagTrustPassword, "trust-password", "", "Password required to add new clients"+"``")
+	cmd.Flags().BoolVar(&c.flagTrustCACertificates, "trust-ca-certificates", false, "Trust client certificates issued by server.ca"+"``")
 
 	return cmd
 }
@@ -73,14 +75,14 @@ func (c *cmdInit) Run(cmd *cobra.Command, args []string) error {
 	if !c.flagAuto && (c.flagNetworkAddress != "" || c.flagNetworkPort != -1 ||
 		c.flagStorageBackend != "" || c.flagStorageDevice != "" ||
 		c.flagStorageLoopSize != -1 || c.flagStoragePool != "" ||
-		c.flagTrustPassword != "") {
+		c.flagTrustPassword != "" || c.flagTrustCACertificates) {
 		return fmt.Errorf("Configuration flags require --auto")
 	}
 
 	if c.flagDump && (c.flagAuto || c.flagPreseed || c.flagNetworkAddress != "" ||
 		c.flagNetworkPort != -1 || c.flagStorageBackend != "" ||
 		c.flagStorageDevice != "" || c.flagStorageLoopSize != -1 ||
-		c.flagStoragePool != "" || c.flagTrustPassword != "") {
+		c.flagStoragePool != "" || c.flagTrustPassword != "" || c.flagTrustCACertificates) {
 		return fmt.Errorf("Can't use --dump with other flags")
 	}
 
diff --git a/lxd/main_init_auto.go b/lxd/main_init_auto.go
index 19840d0a59..d425176406 100644
--- a/lxd/main_init_auto.go
+++ b/lxd/main_init_auto.go
@@ -40,6 +40,10 @@ func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.InstanceServe
 		if c.flagTrustPassword != "" {
 			return nil, fmt.Errorf("--trust-password can't be used without --network-address")
 		}
+
+		if c.flagTrustCACertificates {
+			return nil, fmt.Errorf("--trust-ca-certificates can't be used without --network-address")
+		}
 	}
 
 	storagePools, err := d.GetStoragePoolNames()
@@ -71,6 +75,10 @@ func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.InstanceServe
 		if c.flagTrustPassword != "" {
 			config.Config["core.trust_password"] = c.flagTrustPassword
 		}
+
+		if c.flagTrustCACertificates {
+			config.Config["core.trust_ca_certificates"] = c.flagTrustCACertificates
+		}
 	}
 
 	// Storage configuration
diff --git a/lxd/main_init_interactive.go b/lxd/main_init_interactive.go
index 171e91ae72..d418a697a2 100644
--- a/lxd/main_init_interactive.go
+++ b/lxd/main_init_interactive.go
@@ -653,6 +653,7 @@ they otherwise would.
 		if config.Node.Config["core.trust_password"] == "" {
 			fmt.Printf("No password set, client certificates will have to be manually trusted.")
 		}
+		config.Node.Config["core.trust_ca_certificates"] = cli.AskBool("Trust client certificates issued by server.ca? (yes/no) [default=no]: ", "no")
 	}
 
 	// Ask if the user wants images to be automatically refreshed

From 9182bdee00f97f9aac9572a1d18e74918d2d6c47 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 18 Mar 2020 22:23:04 +0100
Subject: [PATCH 5/8] *: Add parameters to CheckTrustState

This adds two more parameters to CheckTrustState. The first is a
certInfo which contains all server certificates including an optional
CA.
The second parameter is a boolean which tells the function if it
should check for trusted CA certificates.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd-agent/server.go |  2 +-
 lxd/cluster/tls.go  |  2 +-
 lxd/daemon.go       | 12 +++++++++---
 lxd/util/http.go    | 10 +++++++++-
 4 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/lxd-agent/server.go b/lxd-agent/server.go
index bcf889bc94..4f7b9e1505 100644
--- a/lxd-agent/server.go
+++ b/lxd-agent/server.go
@@ -112,7 +112,7 @@ func authenticate(r *http.Request, cert *x509.Certificate) bool {
 	clientCerts := map[string]x509.Certificate{"0": *cert}
 
 	for _, cert := range r.TLS.PeerCertificates {
-		trusted, _ := util.CheckTrustState(*cert, clientCerts)
+		trusted, _ := util.CheckTrustState(*cert, clientCerts, nil, false)
 		if trusted {
 			return true
 		}
diff --git a/lxd/cluster/tls.go b/lxd/cluster/tls.go
index 04ea9fa4b4..5a409a9d41 100644
--- a/lxd/cluster/tls.go
+++ b/lxd/cluster/tls.go
@@ -48,7 +48,7 @@ func tlsCheckCert(r *http.Request, info *shared.CertInfo) bool {
 	}
 	trustedCerts := map[string]x509.Certificate{"0": *cert}
 
-	trusted, _ := util.CheckTrustState(*r.TLS.PeerCertificates[0], trustedCerts)
+	trusted, _ := util.CheckTrustState(*r.TLS.PeerCertificates[0], trustedCerts, nil, false)
 
 	return r.TLS != nil && trusted
 }
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 47aefb41b0..354fb8d67d 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -255,7 +255,7 @@ func (d *Daemon) Authenticate(r *http.Request) (bool, string, string, error) {
 		cert, _ := x509.ParseCertificate(d.endpoints.NetworkCert().KeyPair().Certificate[0])
 		clusterCerts := map[string]x509.Certificate{"0": *cert}
 		for i := range r.TLS.PeerCertificates {
-			trusted, _ := util.CheckTrustState(*r.TLS.PeerCertificates[i], clusterCerts)
+			trusted, _ := util.CheckTrustState(*r.TLS.PeerCertificates[i], clusterCerts, nil, false)
 			if trusted {
 				return true, "", "cluster", nil
 			}
@@ -308,8 +308,15 @@ func (d *Daemon) Authenticate(r *http.Request) (bool, string, string, error) {
 	}
 
 	// Validate normal TLS access
+	var err error
+
+	trustCACertificates, err := cluster.ConfigGetBool(d.cluster, "core.trust_ca_certificates")
+	if err != nil {
+		return false, "", "", err
+	}
+
 	for i := range r.TLS.PeerCertificates {
-		trusted, username := util.CheckTrustState(*r.TLS.PeerCertificates[i], d.clientCerts)
+		trusted, username := util.CheckTrustState(*r.TLS.PeerCertificates[i], d.clientCerts, d.endpoints.NetworkCert(), trustCACertificates)
 		if trusted {
 			return true, username, "tls", nil
 		}
@@ -904,7 +911,6 @@ func (d *Daemon) init() error {
 		candidAPIURL, candidAPIKey, candidExpiry, candidDomains = config.CandidServer()
 		maasAPIURL, maasAPIKey = config.MAASController()
 		rbacAPIURL, rbacAPIKey, rbacExpiry, rbacAgentURL, rbacAgentUsername, rbacAgentPrivateKey, rbacAgentPublicKey = config.RBACServer()
-
 		return nil
 	})
 	if err != nil {
diff --git a/lxd/util/http.go b/lxd/util/http.go
index d2e16ea050..f05868cd23 100644
--- a/lxd/util/http.go
+++ b/lxd/util/http.go
@@ -132,12 +132,20 @@ type ContextAwareRequest interface {
 // CheckTrustState checks whether the given client certificate is trusted
 // (i.e. it has a valid time span and it belongs to the given list of trusted
 // certificates).
-func CheckTrustState(cert x509.Certificate, trustedCerts map[string]x509.Certificate) (bool, string) {
+func CheckTrustState(cert x509.Certificate, trustedCerts map[string]x509.Certificate, certInfo *shared.CertInfo, trustCACertificates bool) (bool, string) {
 	// Extra validity check (should have been caught by TLS stack)
 	if time.Now().Before(cert.NotBefore) || time.Now().After(cert.NotAfter) {
 		return false, ""
 	}
 
+	if certInfo != nil && trustCACertificates {
+		ca := certInfo.CA()
+
+		if ca != nil && cert.CheckSignatureFrom(ca) == nil {
+			return true, shared.CertFingerprint(&cert)
+		}
+	}
+
 	for k, v := range trustedCerts {
 		if bytes.Compare(cert.Raw, v.Raw) == 0 {
 			logger.Debug("Found cert", log.Ctx{"name": k})

From 269773e1cfca79e5ac8568c82fa722dca16468fa Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 19 Mar 2020 19:28:56 +0100
Subject: [PATCH 6/8] shared/cert: Add CRL to CertInfo

This adds a CRL to CertInfo which can hold a list of revoked
certificates.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 shared/cert.go | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/shared/cert.go b/shared/cert.go
index 9cc09c7ae7..a1e923e0d8 100644
--- a/shared/cert.go
+++ b/shared/cert.go
@@ -69,15 +69,30 @@ func KeyPairAndCA(dir, prefix string, kind CertKind, addHosts bool) (*CertInfo,
 		}
 	}
 
+	crlFilename := filepath.Join(dir, "ca.crl")
+	var crl *pkix.CertificateList
+	if PathExists(crlFilename) {
+		data, err := ioutil.ReadFile(crlFilename)
+		if err != nil {
+			return nil, err
+		}
+
+		crl, err = x509.ParseCRL(data)
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	info := &CertInfo{
 		keypair: keypair,
 		ca:      ca,
+		crl:     crl,
 	}
 	return info, nil
 }
 
 // CertInfo captures TLS certificate information about a certain public/private
-// keypair and an optional CA certificate.
+// keypair and an optional CA certificate and CRL.
 //
 // Given LXD's support for PKI setups, these two bits of information are
 // normally used and passed around together, so this structure helps with that
@@ -85,6 +100,7 @@ func KeyPairAndCA(dir, prefix string, kind CertKind, addHosts bool) (*CertInfo,
 type CertInfo struct {
 	keypair tls.Certificate
 	ca      *x509.Certificate
+	crl     *pkix.CertificateList
 }
 
 // KeyPair returns the public/private key pair.
@@ -135,6 +151,11 @@ func (c *CertInfo) Fingerprint() string {
 	return fingerprint
 }
 
+// CRL returns the certificate revocation list.
+func (c *CertInfo) CRL() *pkix.CertificateList {
+	return c.crl
+}
+
 // CertKind defines the kind of certificate to generate from scratch in
 // KeyPairAndCA when it's not there.
 //

From 5c552306fa1852e22cebb92596d6334462f0fb60 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 19 Mar 2020 19:29:44 +0100
Subject: [PATCH 7/8] lxd/util/http: Check CRL for revoked clients

This checks the CRL for revoked client certificates.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/util/http.go | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/lxd/util/http.go b/lxd/util/http.go
index f05868cd23..2b4cece709 100644
--- a/lxd/util/http.go
+++ b/lxd/util/http.go
@@ -142,7 +142,25 @@ func CheckTrustState(cert x509.Certificate, trustedCerts map[string]x509.Certifi
 		ca := certInfo.CA()
 
 		if ca != nil && cert.CheckSignatureFrom(ca) == nil {
-			return true, shared.CertFingerprint(&cert)
+			trusted := true
+
+			// Check whether the certificate has been revoked.
+			crl := certInfo.CRL()
+
+			if crl != nil {
+				for _, revoked := range crl.TBSCertList.RevokedCertificates {
+					if cert.SerialNumber.Cmp(revoked.SerialNumber) == 0 {
+						// Instead of returning false, we set trusted to false, allowing the client
+						// to authenticate using the trust password.
+						trusted = false
+						break
+					}
+				}
+			}
+
+			if trusted {
+				return true, shared.CertFingerprint(&cert)
+			}
 		}
 	}
 

From cf5d7908f0a5e1b2839bb7ec39c81ae654b8256b Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 20 Mar 2020 17:49:55 +0100
Subject: [PATCH 8/8] test: Extend PKI test

This extends the PKI tests by testing CA-trusted client certificates.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 test/suites/pki.sh | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/test/suites/pki.sh b/test/suites/pki.sh
index 62c0812f86..5c521f786e 100644
--- a/test/suites/pki.sh
+++ b/test/suites/pki.sh
@@ -16,15 +16,22 @@ test_pki() {
         ./pkitool --initca
         ./pkitool --server 127.0.0.1
         ./pkitool lxd-client
+        ./pkitool lxd-client-revoked
+        ./revoke-full lxd-client-revoked
     else
         ./easyrsa init-pki
         echo "lxd" | ./easyrsa build-ca nopass
+        ./easyrsa gen-crl
         ./easyrsa build-server-full 127.0.0.1 nopass
         ./easyrsa build-client-full lxd-client nopass
+        ./easyrsa build-client-full lxd-client-revoked nopass
         mkdir keys
         cp pki/private/* keys/
         cp pki/issued/* keys/
         cp pki/ca.crt keys/
+        echo "yes" | ./easyrsa revoke lxd-client-revoked
+        ./easyrsa gen-crl
+        cp pki/crl.pem keys/
     fi
   )
 
@@ -34,6 +41,7 @@ test_pki() {
   cat "${TEST_DIR}/pki/keys/127.0.0.1.crt" "${TEST_DIR}/pki/keys/ca.crt" > "${LXD5_DIR}/server.crt"
   cp "${TEST_DIR}/pki/keys/127.0.0.1.key" "${LXD5_DIR}/server.key"
   cp "${TEST_DIR}/pki/keys/ca.crt" "${LXD5_DIR}/server.ca"
+  cp "${TEST_DIR}/pki/keys/crl.pem" "${LXD5_DIR}/ca.crl"
   spawn_lxd "${LXD5_DIR}" true
   LXD5_ADDR=$(cat "${LXD5_DIR}/lxd.addr")
 
@@ -47,9 +55,34 @@ test_pki() {
   (
     set -e
     export LXD_CONF=${LXC5_DIR}
+
+    # Try adding remote using an incorrect password
+    ! lxc_remote remote add pki-lxd "${LXD5_ADDR}" --accept-certificate --password=bar || false
+
+    # Add remote using the correct password
     lxc_remote remote add pki-lxd "${LXD5_ADDR}" --accept-certificate --password=foo
     lxc_remote info pki-lxd:
     lxc_remote remote remove pki-lxd
+
+    # Add remote using a CA-signed client certificate, and not providing a password
+    LXD_DIR=${LXD5_DIR} lxc config set core.trust_ca_certificates true
+    lxc_remote remote add pki-lxd "${LXD5_ADDR}" --accept-certificate
+    lxc_remote info pki-lxd:
+    lxc_remote remote remove pki-lxd
+
+    # Add remote using a CA-signed client certificate, and providing an incorrect password
+    lxc_remote remote add pki-lxd "${LXD5_ADDR}" --accept-certificate --password=bar
+    lxc_remote info pki-lxd:
+    lxc_remote remote remove pki-lxd
+
+    cp "${TEST_DIR}/pki/keys/lxd-client-revoked.crt" "${LXC5_DIR}/client.crt"
+    cp "${TEST_DIR}/pki/keys/lxd-client-revoked.key" "${LXC5_DIR}/client.key"
+
+    # Try adding a remote using a revoked client certificate, and the correct password
+    lxc_remote remote add pki-lxd "${LXD5_ADDR}" --accept-certificate --password=foo
+
+    # Try adding a remote using a revoked client certificate, and an incorrect password
+    ! lxc_remote remote add pki-lxd "${LXD5_ADDR}" --accept-certificate --password=bar || false
   )
 
   # Confirm that a normal, non-PKI certificate doesn't


More information about the lxc-devel mailing list