[lxc-devel] [lxd/master] Improve certificate checks

stgraber on Github lxc-bot at linuxcontainers.org
Thu Feb 11 23:50:08 UTC 2016


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/20160211/89b7a8e3/attachment.bin>
-------------- next part --------------
From 294cb5771f8093786508360a697a69135f477a5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 11 Feb 2016 16:56:28 -0500
Subject: [PATCH 1/4] Enforce server certificate check everywhere
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go              | 104 +++++--------------------------------------------
 lxc/remote.go          |  46 ++++++++++++++++------
 lxd/containers_post.go |   2 +-
 lxd/daemon.go          |   4 +-
 lxd/images.go          |   2 +-
 shared/network.go      |  73 ++++++++++++++++++++++++++++++++++
 shared/util.go         |  28 -------------
 7 files changed, 122 insertions(+), 137 deletions(-)

diff --git a/client.go b/client.go
index 9e9d07d..2d41728 100644
--- a/client.go
+++ b/client.go
@@ -2,11 +2,9 @@ package lxd
 
 import (
 	"bytes"
-	"crypto/sha256"
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/json"
-	"encoding/pem"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -31,21 +29,12 @@ type Client struct {
 	BaseURL   string
 	BaseWSURL string
 	Config    Config
-	Http      http.Client
 	Name      string
 	Remote    *RemoteConfig
 	Transport string
 
-	certf           string
-	keyf            string
+	Http            http.Client
 	websocketDialer websocket.Dialer
-
-	scert *x509.Certificate // the cert stored on disk
-
-	scertWire          *x509.Certificate // the cert from the tls connection
-	scertIntermediates *x509.CertPool
-	scertDigest        [sha256.Size]byte // fingerprint of server cert from connection
-	scertDigestSet     bool              // whether we've stored the fingerprint
 }
 
 type ResponseType string
@@ -160,19 +149,6 @@ func readMyCert(configDir string) (string, string, error) {
 	return certf, keyf, err
 }
 
-/*
- * load the server cert from disk
- */
-func (c *Client) loadServerCert() {
-	cert, err := shared.ReadCert(c.Config.ServerCertPath(c.Name))
-	if err != nil {
-		shared.Debugf("Error reading the server certificate for %s: %v", c.Name, err)
-		return
-	}
-
-	c.scert = cert
-}
-
 // NewClient returns a new LXD client.
 func NewClient(config *Config, remote string) (*Client, error) {
 	c := Client{
@@ -217,7 +193,15 @@ func NewClient(config *Config, remote string) (*Client, error) {
 				return nil, err
 			}
 
-			tlsconfig, err := shared.GetTLSConfig(certf, keyf)
+			var cert *x509.Certificate
+			if shared.PathExists(c.Config.ServerCertPath(c.Name)) {
+				cert, err = shared.ReadCert(c.Config.ServerCertPath(c.Name))
+				if err != nil {
+					return nil, err
+				}
+			}
+
+			tlsconfig, err := shared.GetTLSConfig(certf, keyf, cert)
 			if err != nil {
 				return nil, err
 			}
@@ -231,9 +215,6 @@ func NewClient(config *Config, remote string) (*Client, error) {
 			c.websocketDialer.NetDial = shared.RFC3493Dialer
 			c.websocketDialer.TLSClientConfig = tlsconfig
 
-			c.certf = certf
-			c.keyf = keyf
-
 			if r.Addr[0:8] == "https://" {
 				c.BaseURL = "https://" + r.Addr[8:]
 				c.BaseWSURL = "wss://" + r.Addr[8:]
@@ -243,7 +224,6 @@ func NewClient(config *Config, remote string) (*Client, error) {
 			}
 			c.Transport = "https"
 			c.Http.Transport = tr
-			c.loadServerCert()
 			c.Remote = &r
 		}
 	} else {
@@ -294,23 +274,6 @@ func (c *Client) baseGet(getUrl string) (*Response, error) {
 		return nil, err
 	}
 
-	if c.scert != nil && resp.TLS != nil {
-		if !bytes.Equal(resp.TLS.PeerCertificates[0].Raw, c.scert.Raw) {
-			pUrl, _ := url.Parse(getUrl)
-			return nil, fmt.Errorf("Server certificate for host %s has changed. Add correct certificate or remove certificate in %s", pUrl.Host, c.Config.ConfigPath("servercerts"))
-		}
-	}
-
-	if c.scertDigestSet == false && resp.TLS != nil {
-		c.scertWire = resp.TLS.PeerCertificates[0]
-		c.scertIntermediates = x509.NewCertPool()
-		for _, cert := range resp.TLS.PeerCertificates {
-			c.scertIntermediates.AddCert(cert)
-		}
-		c.scertDigest = sha256.Sum256(resp.TLS.PeerCertificates[0].Raw)
-		c.scertDigestSet = true
-	}
-
 	return HoistResponse(resp, Sync)
 }
 
@@ -1001,53 +964,6 @@ func (c *Client) ListAliases() ([]shared.ImageAlias, error) {
 	return result, nil
 }
 
-// Try to verify the server's cert with the current host's CA list.
-func (c *Client) TryVerifyServerCert(name string) (string, error) {
-	digest := fmt.Sprintf("%x", c.scertDigest)
-	if !c.scertDigestSet {
-		if err := c.Finger(); err != nil {
-			return digest, err
-		}
-
-		if !c.scertDigestSet {
-			return digest, fmt.Errorf("No certificate on this connection")
-		}
-	}
-
-	if c.scert != nil {
-		return digest, nil
-	}
-
-	_, err := c.scertWire.Verify(x509.VerifyOptions{
-		DNSName:       name,
-		Intermediates: c.scertIntermediates,
-	})
-	return digest, err
-}
-
-func (c *Client) SaveCert(name string) error {
-	if c.scertWire == nil {
-		return fmt.Errorf("can't save empty server cert")
-	}
-
-	// User acked the cert, now add it to our store
-	dnam := c.Config.ConfigPath("servercerts")
-	err := os.MkdirAll(dnam, 0750)
-	if err != nil {
-		return fmt.Errorf("Could not create server cert dir")
-	}
-	certf := fmt.Sprintf("%s/%s.crt", dnam, c.Name)
-	certOut, err := os.Create(certf)
-	if err != nil {
-		return err
-	}
-
-	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: c.scertWire.Raw})
-
-	certOut.Close()
-	return err
-}
-
 func (c *Client) CertificateList() ([]shared.CertInfo, error) {
 	resp, err := c.get("certificates?recursion=1")
 	if err != nil {
diff --git a/lxc/remote.go b/lxc/remote.go
index 1e58825..4aee914 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -1,6 +1,9 @@
 package main
 
 import (
+	"crypto/sha256"
+	"crypto/x509"
+	"encoding/pem"
 	"fmt"
 	"net"
 	"net/url"
@@ -133,17 +136,22 @@ func addServer(config *lxd.Config, server string, addr string, acceptCert bool,
 		return nil
 	}
 
-	/* grab the server's cert */
+	var certificate *x509.Certificate
+
+	/* Attempt to connect using the system root CA */
 	err = c.Finger()
 	if err != nil {
-		return err
+		// Failed to connect using the system CA, so retrieve the remote certificate
+		certificate, err = shared.GetRemoteCertificate(addr)
+		if err != nil {
+			return err
+		}
 	}
 
-	if !acceptCert {
-		// Try to use the CAs on localhost to verify the cert so we
-		// don't have to bother the user.
-		digest, err := c.TryVerifyServerCert(host)
-		if err != nil {
+	if certificate != nil {
+		if !acceptCert {
+			digest := sha256.Sum256(certificate.Raw)
+
 			fmt.Printf(i18n.G("Certificate fingerprint: %x")+"\n", digest)
 			fmt.Printf(i18n.G("ok (y/n)?") + " ")
 			line, err := shared.ReadStdin()
@@ -155,11 +163,27 @@ func addServer(config *lxd.Config, server string, addr string, acceptCert bool,
 				return fmt.Errorf(i18n.G("Server certificate NACKed by user"))
 			}
 		}
-	}
 
-	err = c.SaveCert(host)
-	if err != nil {
-		return err
+		dnam := c.Config.ConfigPath("servercerts")
+		err := os.MkdirAll(dnam, 0750)
+		if err != nil {
+			return fmt.Errorf(i18n.G("Could not create server cert dir"))
+		}
+
+		certf := fmt.Sprintf("%s/%s.crt", dnam, c.Name)
+		certOut, err := os.Create(certf)
+		if err != nil {
+			return err
+		}
+
+		pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw})
+		certOut.Close()
+
+		// Setup a new connection, this time with the remote certificate
+		c, err = lxd.NewClient(config, remote)
+		if err != nil {
+			return err
+		}
 	}
 
 	if c.IsPublic() || public {
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 4abb2d1..4e6b496 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -186,7 +186,7 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 			}
 		}
 
-		config, err := shared.GetTLSConfig("", "")
+		config, err := shared.GetTLSConfig("", "", nil)
 		if err != nil {
 			c.Delete()
 			return err
diff --git a/lxd/daemon.go b/lxd/daemon.go
index f2512ed..18624de 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -107,7 +107,7 @@ type Command struct {
 func (d *Daemon) httpGetSync(url string) (*lxd.Response, error) {
 	var err error
 
-	tlsConfig, err := shared.GetTLSConfig("", "")
+	tlsConfig, err := shared.GetTLSConfig("", "", nil)
 	if err != nil {
 		return nil, err
 	}
@@ -148,7 +148,7 @@ func (d *Daemon) httpGetSync(url string) (*lxd.Response, error) {
 func (d *Daemon) httpGetFile(url string) (*http.Response, error) {
 	var err error
 
-	tlsConfig, err := shared.GetTLSConfig("", "")
+	tlsConfig, err := shared.GetTLSConfig("", "", nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/images.go b/lxd/images.go
index 226e096..db87f41 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -324,7 +324,7 @@ func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
 	}
 
 	// Resolve the image URL
-	tlsConfig, err := shared.GetTLSConfig("", "")
+	tlsConfig, err := shared.GetTLSConfig("", "", nil)
 	if err != nil {
 		return err
 	}
diff --git a/shared/network.go b/shared/network.go
index fa1a66c..daf5978 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -1,10 +1,13 @@
 package shared
 
 import (
+	"crypto/tls"
+	"crypto/x509"
 	"fmt"
 	"io"
 	"io/ioutil"
 	"net"
+	"net/http"
 	"time"
 
 	"github.com/gorilla/websocket"
@@ -30,6 +33,76 @@ func RFC3493Dialer(network, address string) (net.Conn, error) {
 	return nil, fmt.Errorf("Unable to connect to: " + address)
 }
 
+func GetRemoteCertificate(address string) (*x509.Certificate, error) {
+	// Setup a permissive TLS config
+	tlsConfig, err := GetTLSConfig("", "", nil)
+	if err != nil {
+		return nil, err
+	}
+
+	tlsConfig.InsecureSkipVerify = true
+	tr := &http.Transport{
+		TLSClientConfig: tlsConfig,
+	}
+
+	// Connect
+	client := &http.Client{Transport: tr}
+	resp, err := client.Get(address)
+	if err != nil {
+		return nil, err
+	}
+
+	// Retrieve the certificate
+	if resp.TLS == nil || len(resp.TLS.PeerCertificates) == 0 {
+		return nil, fmt.Errorf("Unable to read remote TLS certificate")
+	}
+
+	return resp.TLS.PeerCertificates[0], nil
+}
+
+func GetTLSConfig(tlsClientCert string, tlsClientKey string, tlsRemoteCert *x509.Certificate) (*tls.Config, error) {
+	tlsConfig := &tls.Config{
+		MinVersion: tls.VersionTLS12,
+		MaxVersion: tls.VersionTLS12,
+		CipherSuites: []uint16{
+			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+		PreferServerCipherSuites: true,
+	}
+
+	// Client authentication
+	if tlsClientCert != "" && tlsClientKey != "" {
+		cert, err := tls.LoadX509KeyPair(tlsClientCert, tlsClientKey)
+		if err != nil {
+			return nil, err
+		}
+
+		tlsConfig.Certificates = []tls.Certificate{cert}
+	}
+
+	// Trusted certificates
+	if tlsRemoteCert != nil {
+		caCertPool := x509.NewCertPool()
+
+		// Make it a valid RootCA
+		tlsRemoteCert.IsCA = true
+		tlsRemoteCert.KeyUsage = x509.KeyUsageCertSign
+
+		// Setup the pool
+		caCertPool.AddCert(tlsRemoteCert)
+		tlsConfig.RootCAs = caCertPool
+
+		// Set the ServerName
+		if tlsRemoteCert.DNSNames != nil {
+			tlsConfig.ServerName = tlsRemoteCert.DNSNames[0]
+		}
+	}
+
+	tlsConfig.BuildNameToCertificate()
+
+	return tlsConfig, nil
+}
+
 func IsLoopback(iface *net.Interface) bool {
 	return int(iface.Flags&net.FlagLoopback) > 0
 }
diff --git a/shared/util.go b/shared/util.go
index 9ffc2fc..a583af4 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -4,7 +4,6 @@ import (
 	"bufio"
 	"bytes"
 	"crypto/rand"
-	"crypto/tls"
 	"encoding/gob"
 	"encoding/hex"
 	"encoding/json"
@@ -188,33 +187,6 @@ func ReadStdin() ([]byte, error) {
 	return line, nil
 }
 
-// FIXME: Remove
-func GetTLSConfig(certf string, keyf string) (*tls.Config, error) {
-	tlsConfig := &tls.Config{
-		InsecureSkipVerify: true,
-		ClientAuth:         tls.RequestClientCert,
-		MinVersion:         tls.VersionTLS12,
-		MaxVersion:         tls.VersionTLS12,
-		CipherSuites: []uint16{
-			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
-			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-		PreferServerCipherSuites: true,
-	}
-
-	if certf != "" && keyf != "" {
-		cert, err := tls.LoadX509KeyPair(certf, keyf)
-		if err != nil {
-			return nil, err
-		}
-
-		tlsConfig.Certificates = []tls.Certificate{cert}
-	}
-
-	tlsConfig.BuildNameToCertificate()
-
-	return tlsConfig, nil
-}
-
 func WriteAll(w io.Writer, buf []byte) error {
 	return WriteAllBuf(w, bytes.NewBuffer(buf))
 }

From b8d57806a49d239922fc5954e595e5e9531beeb6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 11 Feb 2016 18:12:21 -0500
Subject: [PATCH 2/4] Update daemon to use client provided certificate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is an extra protection against MITM attacks, the client can provide
the server with the expected certificate and the server will ensure that
it matches. If not provided, the server system CA is used for
verification.

Note that providing an expected certificate bypasses the ServerName check.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go              | 31 +++++++++++++++++++------------
 lxc/copy.go            |  2 +-
 lxd/containers_post.go | 22 +++++++++++++++++-----
 lxd/daemon.go          | 30 ++++++++++++++++++++++++++----
 lxd/daemon_images.go   |  8 +++-----
 lxd/images.go          |  8 +++-----
 lxd/remote.go          |  7 +++----
 specs/rest-api.md      |  4 ++++
 8 files changed, 76 insertions(+), 36 deletions(-)

diff --git a/client.go b/client.go
index 2d41728..d4f83d7 100644
--- a/client.go
+++ b/client.go
@@ -5,6 +5,7 @@ import (
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/json"
+	"encoding/pem"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -26,12 +27,13 @@ import (
 
 // Client can talk to a LXD daemon.
 type Client struct {
-	BaseURL   string
-	BaseWSURL string
-	Config    Config
-	Name      string
-	Remote    *RemoteConfig
-	Transport string
+	BaseURL     string
+	BaseWSURL   string
+	Config      Config
+	Name        string
+	Remote      *RemoteConfig
+	Transport   string
+	Certificate string
 
 	Http            http.Client
 	websocketDialer websocket.Dialer
@@ -199,6 +201,8 @@ func NewClient(config *Config, remote string) (*Client, error) {
 				if err != nil {
 					return nil, err
 				}
+
+				c.Certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}))
 			}
 
 			tlsconfig, err := shared.GetTLSConfig(certf, keyf, cert)
@@ -489,6 +493,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 		"type":        "image",
 		"mode":        "pull",
 		"server":      c.BaseURL,
+		"certificate": c.Certificate,
 		"fingerprint": fingerprint}
 
 	if !info.Public {
@@ -1087,6 +1092,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 		}
 
 		source["server"] = tmpremote.BaseURL
+		source["certificate"] = tmpremote.Certificate
 		source["fingerprint"] = fingerprint
 	} else {
 		fingerprint := c.GetAlias(image)
@@ -1449,13 +1455,14 @@ func (c *Client) GetMigrationSourceWS(container string) (*Response, error) {
 	return c.post(url, body, Async)
 }
 
-func (c *Client) MigrateFrom(name string, operation string, secrets map[string]string, architecture int, config map[string]string, devices shared.Devices, profiles []string, baseImage string, ephemeral bool) (*Response, error) {
+func (c *Client) MigrateFrom(name string, operation string, certificate string, secrets map[string]string, architecture int, config map[string]string, devices shared.Devices, profiles []string, baseImage string, ephemeral bool) (*Response, error) {
 	source := shared.Jmap{
-		"type":       "migration",
-		"mode":       "pull",
-		"operation":  operation,
-		"secrets":    secrets,
-		"base-image": baseImage,
+		"type":        "migration",
+		"mode":        "pull",
+		"operation":   operation,
+		"certificate": certificate,
+		"secrets":     secrets,
+		"base-image":  baseImage,
 	}
 	body := shared.Jmap{
 		"architecture": architecture,
diff --git a/lxc/copy.go b/lxc/copy.go
index 0f3d20d..4a2480e 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -146,7 +146,7 @@ func copyContainer(config *lxd.Config, sourceResource string, destResource strin
 			var migration *lxd.Response
 
 			sourceWSUrl := "https://" + addr + sourceWSResponse.Operation
-			migration, err = dest.MigrateFrom(destName, sourceWSUrl, secrets, status.Architecture, status.Config, status.Devices, status.Profiles, baseImage, ephemeral == 1)
+			migration, err = dest.MigrateFrom(destName, sourceWSUrl, source.Certificate, secrets, status.Architecture, status.Config, status.Devices, status.Profiles, baseImage, ephemeral == 1)
 			if err != nil {
 				continue
 			}
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 4e6b496..2dda678 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -1,7 +1,9 @@
 package main
 
 import (
+	"crypto/x509"
 	"encoding/json"
+	"encoding/pem"
 	"fmt"
 	"net/http"
 	"strings"
@@ -14,7 +16,8 @@ import (
 )
 
 type containerImageSource struct {
-	Type string `json:"type"`
+	Type        string `json:"type"`
+	Certificate string `json:"certificate"`
 
 	/* for "image" type */
 	Alias       string `json:"alias"`
@@ -56,7 +59,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 
 	if req.Source.Alias != "" {
 		if req.Source.Mode == "pull" && req.Source.Server != "" {
-			hash, err = remoteGetImageFingerprint(d, req.Source.Server, req.Source.Alias)
+			hash, err = remoteGetImageFingerprint(d, req.Source.Server, req.Source.Certificate, req.Source.Alias)
 			if err != nil {
 				return InternalError(err)
 			}
@@ -75,7 +78,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 
 	run := func(op *operation) error {
 		if req.Source.Server != "" {
-			err := d.ImageDownload(op, req.Source.Server, hash, req.Source.Secret, true, false)
+			err := d.ImageDownload(op, req.Source.Server, req.Source.Certificate, req.Source.Secret, hash, true, false)
 			if err != nil {
 				return err
 			}
@@ -186,7 +189,17 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 			}
 		}
 
-		config, err := shared.GetTLSConfig("", "", nil)
+		var cert *x509.Certificate
+		if req.Source.Certificate != "" {
+			certBlock, _ := pem.Decode([]byte(req.Source.Certificate))
+
+			cert, err = x509.ParseCertificate(certBlock.Bytes)
+			if err != nil {
+				return err
+			}
+		}
+
+		config, err := shared.GetTLSConfig("", "", cert)
 		if err != nil {
 			c.Delete()
 			return err
@@ -345,5 +358,4 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	default:
 		return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
 	}
-
 }
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 18624de..f616f08 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -7,6 +7,7 @@ import (
 	"crypto/x509"
 	"database/sql"
 	"encoding/hex"
+	"encoding/pem"
 	"fmt"
 	"io"
 	"net"
@@ -104,10 +105,20 @@ type Command struct {
 	delete        func(d *Daemon, r *http.Request) Response
 }
 
-func (d *Daemon) httpGetSync(url string) (*lxd.Response, error) {
+func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, error) {
 	var err error
 
-	tlsConfig, err := shared.GetTLSConfig("", "", nil)
+	var cert *x509.Certificate
+	if certificate != "" {
+		certBlock, _ := pem.Decode([]byte(certificate))
+
+		cert, err = x509.ParseCertificate(certBlock.Bytes)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	tlsConfig, err := shared.GetTLSConfig("", "", cert)
 	if err != nil {
 		return nil, err
 	}
@@ -117,6 +128,7 @@ func (d *Daemon) httpGetSync(url string) (*lxd.Response, error) {
 		Dial:            shared.RFC3493Dialer,
 		Proxy:           http.ProxyFromEnvironment,
 	}
+
 	myhttp := http.Client{
 		Transport: tr,
 	}
@@ -145,10 +157,20 @@ func (d *Daemon) httpGetSync(url string) (*lxd.Response, error) {
 	return resp, nil
 }
 
-func (d *Daemon) httpGetFile(url string) (*http.Response, error) {
+func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, error) {
 	var err error
 
-	tlsConfig, err := shared.GetTLSConfig("", "", nil)
+	var cert *x509.Certificate
+	if certificate != "" {
+		certBlock, _ := pem.Decode([]byte(certificate))
+
+		cert, err = x509.ParseCertificate(certBlock.Bytes)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	tlsConfig, err := shared.GetTLSConfig("", "", cert)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 47e1a7a..cbe933f 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -54,9 +54,7 @@ func (pt *Progress) Read(p []byte) (int, error) {
 
 // ImageDownload checks if we have that Image Fingerprint else
 // downloads the image from a remote server.
-func (d *Daemon) ImageDownload(op *operation,
-	server, fp string, secret string, forContainer bool, directDownload bool) error {
-
+func (d *Daemon) ImageDownload(op *operation, server string, certificate string, secret string, fp string, forContainer bool, directDownload bool) error {
 	if _, err := dbImageGet(d.db, fp, false, false); err == nil {
 		shared.Log.Debug("Image already exists in the db", log.Ctx{"image": fp})
 		// already have it
@@ -134,7 +132,7 @@ func (d *Daemon) ImageDownload(op *operation,
 			url = fmt.Sprintf("%s/%s/images/%s", server, shared.APIVersion, fp)
 		}
 
-		resp, err := d.httpGetSync(url)
+		resp, err := d.httpGetSync(url, certificate)
 		if err != nil {
 			shared.Log.Error(
 				"Failed to download image metadata",
@@ -160,7 +158,7 @@ func (d *Daemon) ImageDownload(op *operation,
 		}
 	}
 
-	raw, err := d.httpGetFile(exporturl)
+	raw, err := d.httpGetFile(exporturl, certificate)
 	if err != nil {
 		shared.Log.Error(
 			"Failed to download image",
diff --git a/lxd/images.go b/lxd/images.go
index db87f41..875d633 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -273,7 +273,7 @@ func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation) error {
 
 	if req.Source["alias"] != "" {
 		if req.Source["mode"] == "pull" && req.Source["server"] != "" {
-			hash, err = remoteGetImageFingerprint(d, req.Source["server"], req.Source["alias"])
+			hash, err = remoteGetImageFingerprint(d, req.Source["server"], req.Source["certificate"], req.Source["alias"])
 			if err != nil {
 				return err
 			}
@@ -289,8 +289,7 @@ func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation) error {
 		return fmt.Errorf("must specify one of alias or fingerprint for init from image")
 	}
 
-	err = d.ImageDownload(op,
-		req.Source["server"], hash, req.Source["secret"], false, false)
+	err = d.ImageDownload(op, req.Source["server"], req.Source["certificate"], req.Source["secret"], hash, false, false)
 
 	if err != nil {
 		return err
@@ -369,8 +368,7 @@ func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
 	}
 
 	// Import the image
-	err = d.ImageDownload(op,
-		url, hash, "", false, true)
+	err = d.ImageDownload(op, url, "", "", hash, false, true)
 
 	if err != nil {
 		return err
diff --git a/lxd/remote.go b/lxd/remote.go
index 4231243..238ea0e 100644
--- a/lxd/remote.go
+++ b/lxd/remote.go
@@ -7,14 +7,12 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
-func remoteGetImageFingerprint(
-	d *Daemon, server string, alias string) (string, error) {
-
+func remoteGetImageFingerprint(d *Daemon, server string, certificate string, alias string) (string, error) {
 	url := fmt.Sprintf(
 		"%s/%s/images/aliases/%s",
 		server, shared.APIVersion, alias)
 
-	resp, err := d.httpGetSync(url)
+	resp, err := d.httpGetSync(url, certificate)
 	if err != nil {
 		return "", err
 	}
@@ -23,5 +21,6 @@ func remoteGetImageFingerprint(
 	if err = json.Unmarshal(resp.Metadata, &result); err != nil {
 		return "", fmt.Errorf("Error reading alias")
 	}
+
 	return result.Name, nil
 }
diff --git a/specs/rest-api.md b/specs/rest-api.md
index 0ca83db..2c9c3bf 100644
--- a/specs/rest-api.md
+++ b/specs/rest-api.md
@@ -322,6 +322,7 @@ Input (using a public remote image):
         'source': {'type': "image",                                         # Can be: "image", "migration", "copy" or "none"
                    'mode': "pull",                                          # One of "local" (default), "pull" or "receive"
                    'server': "https://10.0.2.3:8443",                       # Remote server (pull mode only)
+                   'certificate': "PEM certificate",                        # Optional PEM certificate. If not mentioned, system CA is used.
                    'alias': "ubuntu/devel"},                                # Name of the alias
     }
 
@@ -338,6 +339,7 @@ Input (using a private remote image after having obtained a secret for that imag
                    'mode': "pull",                                          # One of "local" (default), "pull" or "receive"
                    'server': "https://10.0.2.3:8443",                       # Remote server (pull mode only)
                    'secret': "my-secret-string",                            # Secret to use to retrieve the image (pull mode only)
+                   'certificate': "PEM certificate",                        # Optional PEM certificate. If not mentioned, system CA is used.
                    'alias': "ubuntu/devel"},                                # Name of the alias
     }
 
@@ -352,6 +354,7 @@ Input (using a remote container, sent over the migration websocket):
         'source': {'type': "migration",                                                 # Can be: "image", "migration", "copy" or "none"
                    'mode': "pull",                                                      # One of "pull" or "receive"
                    'operation': "https://10.0.2.3:8443/1.0/operations/<UUID>",          # Full URL to the remote operation (pull mode only)
+                   'certificate': "PEM certificate",                        # Optional PEM certificate. If not mentioned, system CA is used.
                    'base-image': "<some hash>"                                          # Optional, the base image the container was created from
                    'secrets': {'control': "my-secret-string",                           # Secrets to use when talking to the migration source
                                'criu':    "my-other-secret",
@@ -764,6 +767,7 @@ In the source image case, the following dict must be passed:
             "mode": "pull",                     # One of "pull" or "receive"
             "server": "https://10.0.2.3:8443",  # Remote server (pull mode only)
             "secret": "my-secret-string",       # Secret (pull mode only, private images only)
+            'certificate': "PEM certificate",   # Optional PEM certificate. If not mentioned, system CA is used.
             "fingerprint": "SHA256",            # Fingerprint of the image (must be set if alias isn't)
             "alias": "ubuntu/devel",            # Name of the alias (must be set if fingerprint isn't)
         }

From 5c1ccfd2d7a2122bef9a10db9d2ef4490bbc977c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 11 Feb 2016 18:17:41 -0500
Subject: [PATCH 3/4] Update translations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/lxd.pot | 46 +++++++++++++++++++++++++---------------------
 1 file changed, 25 insertions(+), 21 deletions(-)

diff --git a/po/lxd.pot b/po/lxd.pot
index 6c0ebb6..1cf9b01 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-02-10 22:15-0500\n"
+        "POT-Creation-Date: 2016-02-11 18:17-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -86,11 +86,11 @@ msgstr  ""
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/remote.go:46
+#: lxc/remote.go:49
 msgid   "Accept certificate"
 msgstr  ""
 
-#: lxc/remote.go:181
+#: lxc/remote.go:205
 #, c-format
 msgid   "Admin password for %s: "
 msgstr  ""
@@ -130,7 +130,7 @@ msgstr  ""
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
-#: lxc/remote.go:147
+#: lxc/remote.go:155
 #, c-format
 msgid   "Certificate fingerprint: %x"
 msgstr  ""
@@ -142,7 +142,7 @@ msgid   "Changes state of one or more containers to %s.\n"
         "lxc %s <name> [<name>...]"
 msgstr  ""
 
-#: lxc/remote.go:204
+#: lxc/remote.go:228
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
@@ -192,6 +192,10 @@ msgstr  ""
 msgid   "Copying the image: %s"
 msgstr  ""
 
+#: lxc/remote.go:170
+msgid   "Could not create server cert dir"
+msgstr  ""
+
 #: lxc/snapshot.go:21
 msgid   "Create a read-only snapshot of a container.\n"
         "\n"
@@ -528,7 +532,7 @@ msgid   "Manage files on a container.\n"
         "<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>"
 msgstr  ""
 
-#: lxc/remote.go:33
+#: lxc/remote.go:36
 msgid   "Manage remote LXD servers.\n"
         "\n"
         "lxc remote add <name> <url> [--accept-certificate] [--password=PASSWORD] [--public]    Add the remote <name> at <url>.\n"
@@ -628,11 +632,11 @@ msgid   "Move containers within or in between lxd instances.\n"
         "    Rename a local container.\n"
 msgstr  ""
 
-#: lxc/list.go:224 lxc/remote.go:271
+#: lxc/list.go:224 lxc/remote.go:295
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/list.go:293 lxc/remote.go:257
+#: lxc/list.go:293 lxc/remote.go:281
 msgid   "NO"
 msgstr  ""
 
@@ -674,7 +678,7 @@ msgstr  ""
 msgid   "PID"
 msgstr  ""
 
-#: lxc/image.go:522 lxc/remote.go:273
+#: lxc/image.go:522 lxc/remote.go:297
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -755,7 +759,7 @@ msgstr  ""
 msgid   "Properties:"
 msgstr  ""
 
-#: lxc/remote.go:48
+#: lxc/remote.go:51
 msgid   "Public image server"
 msgstr  ""
 
@@ -770,7 +774,7 @@ msgid   "Publish containers as images.\n"
         "lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-value]..."
 msgstr  ""
 
-#: lxc/remote.go:47
+#: lxc/remote.go:50
 msgid   "Remote admin password"
 msgstr  ""
 
@@ -800,11 +804,11 @@ msgstr  ""
 msgid   "STATE"
 msgstr  ""
 
-#: lxc/remote.go:155
+#: lxc/remote.go:163
 msgid   "Server certificate NACKed by user"
 msgstr  ""
 
-#: lxc/remote.go:201
+#: lxc/remote.go:225
 msgid   "Server doesn't trust us after adding our cert"
 msgstr  ""
 
@@ -897,7 +901,7 @@ msgstr  ""
 msgid   "UPLOAD DATE"
 msgstr  ""
 
-#: lxc/remote.go:272
+#: lxc/remote.go:296
 msgid   "URL"
 msgstr  ""
 
@@ -931,7 +935,7 @@ msgstr  ""
 msgid   "Whether to show the expanded configuration"
 msgstr  ""
 
-#: lxc/list.go:291 lxc/remote.go:259
+#: lxc/list.go:291 lxc/remote.go:283
 msgid   "YES"
 msgstr  ""
 
@@ -951,11 +955,11 @@ msgstr  ""
 msgid   "can't copy to the same container name"
 msgstr  ""
 
-#: lxc/remote.go:247
+#: lxc/remote.go:271
 msgid   "can't remove the default remote"
 msgstr  ""
 
-#: lxc/remote.go:264
+#: lxc/remote.go:288
 msgid   "default"
 msgstr  ""
 
@@ -985,7 +989,7 @@ msgstr  ""
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
-#: lxc/remote.go:148
+#: lxc/remote.go:156
 msgid   "ok (y/n)?"
 msgstr  ""
 
@@ -994,17 +998,17 @@ msgstr  ""
 msgid   "processing aliases failed %s\n"
 msgstr  ""
 
-#: lxc/remote.go:291
+#: lxc/remote.go:315
 #, c-format
 msgid   "remote %s already exists"
 msgstr  ""
 
-#: lxc/remote.go:243 lxc/remote.go:287 lxc/remote.go:317 lxc/remote.go:328
+#: lxc/remote.go:267 lxc/remote.go:311 lxc/remote.go:341 lxc/remote.go:352
 #, c-format
 msgid   "remote %s doesn't exist"
 msgstr  ""
 
-#: lxc/remote.go:227
+#: lxc/remote.go:251
 #, c-format
 msgid   "remote %s exists as <%s>"
 msgstr  ""

From d6a0a1ce6b2a019cf7346ba6fdbcc7acea9cedcd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 11 Feb 2016 18:45:45 -0500
Subject: [PATCH 4/4] Set remote certificate when using unix socket too
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go         | 6 ++++++
 lxd/api_1.0.go    | 7 +++++++
 shared/server.go  | 1 +
 specs/rest-api.md | 1 +
 4 files changed, 15 insertions(+)

diff --git a/client.go b/client.go
index d4f83d7..48fb9af 100644
--- a/client.go
+++ b/client.go
@@ -189,6 +189,12 @@ func NewClient(config *Config, remote string) (*Client, error) {
 			c.Http.Transport = &http.Transport{Dial: uDial}
 			c.websocketDialer.NetDial = uDial
 			c.Remote = &r
+
+			st, err := c.ServerStatus()
+			if err != nil {
+				return nil, err
+			}
+			c.Certificate = st.Environment.Certificate
 		} else {
 			certf, keyf, err := readMyCert(config.ConfigDir)
 			if err != nil {
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 98cd42c..8bb4f22 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/pem"
 	"fmt"
 	"net/http"
 	"os"
@@ -88,9 +89,15 @@ func api10Get(d *Daemon, r *http.Request) Response {
 			return InternalError(err)
 		}
 
+		var certificate string
+		if len(d.tlsConfig.Certificates) != 0 {
+			certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: d.tlsConfig.Certificates[0].Certificate[0]}))
+		}
+
 		env := shared.Jmap{
 			"addresses":           addresses,
 			"architectures":       d.architectures,
+			"certificate":         certificate,
 			"driver":              "lxc",
 			"driver_version":      lxc.Version(),
 			"kernel":              kernel,
diff --git a/shared/server.go b/shared/server.go
index 9727c9b..fe42142 100644
--- a/shared/server.go
+++ b/shared/server.go
@@ -3,6 +3,7 @@ package shared
 type ServerStateEnvironment struct {
 	Addresses          []string `json:"addresses"`
 	Architectures      []int    `json:"architectures"`
+	Certificate        string   `json:"certificate"`
 	Driver             string   `json:"driver"`
 	DriverVersion      string   `json:"driver_version"`
 	Kernel             string   `json:"kernel"`
diff --git a/specs/rest-api.md b/specs/rest-api.md
index 2c9c3bf..9904468 100644
--- a/specs/rest-api.md
+++ b/specs/rest-api.md
@@ -216,6 +216,7 @@ Return value (if trusted):
         'environment': {                                # Various information about the host (OS, kernel, ...)
                         'addresses': ["1.2.3.4:8443", "[1234::1234]:8443"],
                         'architectures': [1, 2],
+                        'certificate': 'PEM certificate',
                         'driver': "lxc",
                         'driver_version': "1.0.6",
                         'kernel': "Linux",


More information about the lxc-devel mailing list