[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