[lxc-devel] [lxd/master] daemon: support proxy configuration options

tych0 on Github lxc-bot at linuxcontainers.org
Wed Mar 9 16:24:45 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 751 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160309/4e511a7e/attachment.bin>
-------------- next part --------------
From bbb02f4de57d6df7b7b6a4d0f03e5d791837e2d8 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 9 Mar 2016 09:19:57 -0700
Subject: [PATCH] daemon: support proxy configuration options

juju will want to set proxy information on the daemon to tell it where to
ask for images. This commit adds configuration varaibles for common proxy
information, so that juju can use the LXD API as opposed to hacking the
init script to start LXD with HTTP_PROXY env enabled.

Note that this commit will no longer use HTTP_PROXY, but requires users to
use the configuration instead.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 client.go               |   2 +-
 lxd/api_1.0.go          |   6 +++
 lxd/daemon.go           | 131 +++++++++++++++++++++++++++++++++++++++++++++++-
 lxd/daemon_images.go    |   2 +-
 lxd/images.go           |   2 +-
 shared/simplestreams.go |   5 +-
 specs/configuration.md  |   3 ++
 7 files changed, 144 insertions(+), 7 deletions(-)

diff --git a/client.go b/client.go
index 665cf7d..fc44857 100644
--- a/client.go
+++ b/client.go
@@ -314,7 +314,7 @@ func NewClientFromInfo(info ConnectInfo) (*Client, error) {
 	}
 
 	if info.RemoteConfig.Protocol == "simplestreams" {
-		ss, err := shared.SimpleStreamsClient(c.Remote.Addr)
+		ss, err := shared.SimpleStreamsClient(c.Remote.Addr, http.ProxyFromEnvironment)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 2d5b757..e111cf8 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -239,6 +239,12 @@ func api10Put(d *Daemon, r *http.Request) Response {
 			if err != nil {
 				return InternalError(err)
 			}
+		} else if key == "core.http_proxy" || key == "core.https_proxy" || key == "core.no_proxy" {
+			err = d.ConfigValueSet(key, value.(string))
+			if err != nil {
+				return InternalError(err)
+			}
+
 		} else {
 			err := d.ConfigValueSet(key, value.(string))
 			if err != nil {
diff --git a/lxd/daemon.go b/lxd/daemon.go
index f220570..cd12f22 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -12,6 +12,7 @@ import (
 	"io"
 	"net"
 	"net/http"
+	"net/url"
 	"os"
 	"os/exec"
 	"runtime"
@@ -128,7 +129,7 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err
 	tr := &http.Transport{
 		TLSClientConfig: tlsConfig,
 		Dial:            shared.RFC3493Dialer,
-		Proxy:           http.ProxyFromEnvironment,
+		Proxy:           d.ProxyFromConfig,
 	}
 
 	myhttp := http.Client{
@@ -180,7 +181,7 @@ func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, er
 	tr := &http.Transport{
 		TLSClientConfig: tlsConfig,
 		Dial:            shared.RFC3493Dialer,
-		Proxy:           http.ProxyFromEnvironment,
+		Proxy:           d.ProxyFromConfig,
 	}
 	myhttp := http.Client{
 		Transport: tr,
@@ -1134,6 +1135,8 @@ func (d *Daemon) Stop() error {
 // ConfigKeyIsValid returns if the given key is a known config value.
 func (d *Daemon) ConfigKeyIsValid(key string) bool {
 	switch key {
+	case "core.http_proxy":
+		return true
 	case "core.https_address":
 		return true
 	case "core.https_allowed_origin":
@@ -1142,6 +1145,10 @@ func (d *Daemon) ConfigKeyIsValid(key string) bool {
 		return true
 	case "core.https_allowed_headers":
 		return true
+	case "core.https_proxy":
+		return true
+	case "core.no_proxy":
+		return true
 	case "core.trust_password":
 		return true
 	case "storage.lvm_vg_name":
@@ -1279,6 +1286,126 @@ func (d *Daemon) PasswordCheck(password string) bool {
 	return true
 }
 
+// ProxyFromConfig is basically a copy of http.ProxyFromEnvironment but looks
+// at our config to decide the proxy instead of in the environment.
+// Unfortunately, we can't just use putenv(), because the golang stdlib caches
+// the values of the environment variables after it reads them once, see e.g.
+// httpProxyEnv in https://golang.org/src/net/http/transport.go
+func (d *Daemon) ProxyFromConfig(req *http.Request) (*url.URL, error) {
+	var proxy string
+	var err error
+
+	if req.URL.Scheme == "https" {
+		proxy, err = d.ConfigValueGet("core.https_proxy")
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if proxy == "" {
+		proxy, err = d.ConfigValueGet("core.http_proxy")
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if proxy == "" {
+		return nil, nil
+	}
+
+	addr := req.URL.Host
+	if !hasPort(addr) {
+		if req.URL.Scheme == "https" {
+			addr = addr + ":443"
+		} else if req.URL.Scheme == "http" {
+			addr = addr + ":80"
+		} else {
+			return nil, fmt.Errorf("unknown scheme %s", req.URL.Scheme)
+		}
+	}
+
+	use, err := d.useProxy(addr)
+	if err != nil {
+		return nil, err
+	}
+	if !use {
+		return nil, nil
+	}
+
+	proxyURL, err := url.Parse(proxy)
+	if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
+		// proxy was bogus. Try prepending "http://" to it and
+		// see if that parses correctly. If not, we fall
+		// through and complain about the original one.
+		if proxyURL, err := url.Parse("http://" + proxy); err == nil {
+			return proxyURL, nil
+		}
+	}
+	if err != nil {
+		return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
+	}
+	return proxyURL, nil
+}
+
+func hasPort(s string) bool {
+	return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
+}
+
+// Similar deal to ProxyFromConfig above r.e. go stdlib
+func (d *Daemon) useProxy(addr string) (bool, error) {
+	if len(addr) == 0 {
+		return true, nil
+	}
+	host, _, err := net.SplitHostPort(addr)
+	if err != nil {
+		return false, nil
+	}
+	if host == "localhost" {
+		return false, nil
+	}
+	if ip := net.ParseIP(host); ip != nil {
+		if ip.IsLoopback() {
+			return false, nil
+		}
+	}
+
+	noProxy, err := d.ConfigValueGet("core.no_proxy")
+	if err != nil {
+		return false, err
+	}
+
+	if noProxy == "*" {
+		return false, nil
+	}
+
+	addr = strings.ToLower(strings.TrimSpace(addr))
+	if hasPort(addr) {
+		addr = addr[:strings.LastIndex(addr, ":")]
+	}
+
+	for _, p := range strings.Split(noProxy, ",") {
+		p = strings.ToLower(strings.TrimSpace(p))
+		if len(p) == 0 {
+			continue
+		}
+		if hasPort(p) {
+			p = p[:strings.LastIndex(p, ":")]
+		}
+		if addr == p {
+			return false, nil
+		}
+		if p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:]) {
+			// noProxy ".foo.com" matches "bar.foo.com" or "foo.com"
+			return false, nil
+		}
+		if p[0] != '.' && strings.HasSuffix(addr, p) && addr[len(addr)-len(p)-1] == '.' {
+			// noProxy "foo.com" matches "bar.foo.com"
+			return false, nil
+		}
+	}
+	return true, nil
+}
+
 type lxdHttpServer struct {
 	r *mux.Router
 	d *Daemon
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 685f2cd..ebb508d 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -29,7 +29,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 
 	// Expand aliases
 	if protocol == "simplestreams" {
-		ss, err = shared.SimpleStreamsClient(server)
+		ss, err = shared.SimpleStreamsClient(server, d.ProxyFromConfig)
 		if err != nil {
 			return "", err
 		}
diff --git a/lxd/images.go b/lxd/images.go
index adf4a1d..2c9b436 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -328,7 +328,7 @@ func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
 	tr := &http.Transport{
 		TLSClientConfig: tlsConfig,
 		Dial:            shared.RFC3493Dialer,
-		Proxy:           http.ProxyFromEnvironment,
+		Proxy:           d.ProxyFromConfig,
 	}
 
 	myhttp := http.Client{
diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index 54cf488..e17b496 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -7,6 +7,7 @@ import (
 	"io"
 	"io/ioutil"
 	"net/http"
+	"net/url"
 	"os"
 	"path/filepath"
 	"sort"
@@ -215,7 +216,7 @@ type SimpleStreamsIndexStream struct {
 	Products []string `json:"products"`
 }
 
-func SimpleStreamsClient(url string) (*SimpleStreams, error) {
+func SimpleStreamsClient(url string, proxy func(*http.Request) (*url.URL, error)) (*SimpleStreams, error) {
 	// Setup a http client
 	tlsConfig, err := GetTLSConfig("", "", nil)
 	if err != nil {
@@ -225,7 +226,7 @@ func SimpleStreamsClient(url string) (*SimpleStreams, error) {
 	tr := &http.Transport{
 		TLSClientConfig: tlsConfig,
 		Dial:            RFC3493Dialer,
-		Proxy:           http.ProxyFromEnvironment,
+		Proxy:           proxy,
 	}
 
 	myHttp := http.Client{
diff --git a/specs/configuration.md b/specs/configuration.md
index a265084..bd43071 100644
--- a/specs/configuration.md
+++ b/specs/configuration.md
@@ -23,6 +23,9 @@ core.https\_address             | string        | -                         | Ad
 core.https\_allowed\_origin     | string        | -                         | Access-Control-Allow-Origin http header value
 core.https\_allowed\_methods    | string        | -                         | Access-Control-Allow-Methods http header value
 core.https\_allowed\_headers    | string        | -                         | Access-Control-Allow-Headers http header value
+core.https\_proxy               | string        | -                         | https proxy to use, if any
+core.http\_proxy                | string        | -                         | http proxy to use, if any
+core.no\_proxy                  | string        | -                         | hosts which don't need the proxy for use
 core.trust\_password            | string        | -                         | Password to be provided by clients to setup a trust
 storage.lvm\_vg\_name           | string        | -                         | LVM Volume Group name to be used for container and image storage. A default Thin Pool is created using 100% of the free space in the Volume Group, unless `storage.lvm_thinpool_name` is set.
 storage.lvm\_thinpool\_name     | string        | "LXDPool"                 | LVM Thin Pool to use within the Volume Group specified in `storage.lvm_vg_name`, if the default pool parameters are undesirable.


More information about the lxc-devel mailing list