Merge pull request #815 from paulbellamy/proxy-ssl

Proxy TLS Support

Closes #506.
This commit is contained in:
Matthias Radestock committed 2015-06-05 14:48:54 +01:00
commit 87e30d8382
10 files changed
+380 -74

No files matched your search

+1
View File
@@ -47,3 +47,4 @@ prog/weaveexec/weavewait
prog/weaveexec/sigproxy
prog/weaveexec/docker*.tgz
Vagrantfile.local
test/tls/*.pem
+1
View File
@@ -111,6 +111,7 @@ publish: $(PUBLISH)
clean:
-$(SUDO) docker rmi $(IMAGES)
rm -f $(EXES) $(EXPORTS)
rm -f test/tls/*.pem
build:
$(SUDO) go clean -i net
+23 -22
View File
@@ -10,21 +10,33 @@ import (
)
var (
version = "(unreleased version)"
defaultTarget = "unix:///var/run/docker.sock"
defaultListen = ":12375"
version = "(unreleased version)"
defaultDockerAddr = "unix:///var/run/docker.sock"
defaultListenAddr = ":12375"
)
func main() {
var target, listen string
var withDNS, withIPAM, debug, justVersion bool
var (
debug bool
justVersion bool
c = proxy.Config{
DockerAddr: defaultDockerAddr,
ListenAddr: defaultListenAddr,
}
)
c.Version = version
getopt.BoolVarLong(&debug, "debug", 'd', "log debugging information")
getopt.BoolVarLong(&justVersion, "version", 0, "print version and exit")
getopt.StringVar(&target, 'H', fmt.Sprintf("docker daemon URL to proxy (default %s)", defaultTarget))
getopt.StringVar(&listen, 'L', fmt.Sprintf("address on which to listen (default %s)", defaultListen))
getopt.BoolVarLong(&withDNS, "with-dns", 'w', "instruct created containers to use weaveDNS as their nameserver")
getopt.BoolVarLong(&withIPAM, "with-ipam", 'i', "automatically allocate addresses for containers without a WEAVE_CIDR")
getopt.StringVar(&c.ListenAddr, 'L', fmt.Sprintf("address on which to listen (default %s)", defaultListenAddr))
getopt.StringVar(&c.DockerAddr, 'H', fmt.Sprintf("docker daemon URL to proxy (default %s)", defaultDockerAddr))
getopt.StringVarLong(&c.TLSConfig.CACert, "tlscacert", 0, "Trust certs signed only by this CA")
getopt.StringVarLong(&c.TLSConfig.Cert, "tlscert", 0, "Path to TLS certificate file")
getopt.BoolVarLong(&c.TLSConfig.Enabled, "tls", 0, "Use TLS; implied by --tlsverify")
getopt.StringVarLong(&c.TLSConfig.Key, "tlskey", 0, "Path to TLS key file")
getopt.BoolVarLong(&c.TLSConfig.Verify, "tlsverify", 0, "Use TLS and verify the remote")
getopt.BoolVarLong(&c.WithDNS, "with-dns", 'w', "instruct created containers to use weaveDNS as their nameserver")
getopt.BoolVarLong(&c.WithIPAM, "with-ipam", 'i', "automatically allocate addresses for containers without a WEAVE_CIDR")
getopt.Parse()
if justVersion {
@@ -32,27 +44,16 @@ func main() {
os.Exit(0)
}
if target == "" {
target = defaultTarget
}
if listen == "" {
listen = defaultListen
}
if debug {
InitDefaultLogging(true)
}
p, err := proxy.NewProxy(version, target, listen, withDNS, withIPAM)
p, err := proxy.NewProxy(c)
if err != nil {
Error.Fatalf("Could not start proxy: %s", err)
}
Info.Printf("Listening on %s", listen)
Info.Printf("Proxying %s", target)
if err := p.ListenAndServe(); err != nil {
Error.Fatalf("Could not listen on %s: %s", listen, err)
Error.Fatalf("Could not listen on %s: %s", p.ListenAddr, err)
}
}
+74 -43
View File
@@ -2,6 +2,7 @@ package proxy
import (
"bytes"
"crypto/tls"
"fmt"
"net"
"net/http"
@@ -13,6 +14,12 @@ import (
. "github.com/weaveworks/weave/common"
)
const (
defaultCaFile = "ca.pem"
defaultKeyFile = "key.pem"
defaultCertFile = "cert.pem"
)
var (
containerCreateRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/create$")
containerStartRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/[^/]*/(re)?start$")
@@ -20,31 +27,24 @@ var (
)
type Proxy struct {
Dial func() (net.Conn, error)
version string
Config
dial func() (net.Conn, error)
client *docker.Client
dockerAddr string
listenAddr string
withDNS bool
dockerBridgeIP string
withIPAM bool
}
func NewProxy(version, dockerAddr, listenAddr string, withDNS, withIPAM bool) (*Proxy, error) {
u, err := url.Parse(dockerAddr)
if err != nil {
return nil, err
}
type Config struct {
DockerAddr string
ListenAddr string
TLSConfig TLSConfig
Version string
WithDNS bool
WithIPAM bool
}
var dockerBridgeIP []byte
if withDNS {
dockerBridgeIP, err = callWeave("docker-bridge-ip")
if err != nil {
return nil, err
}
}
client, err := docker.NewClient(dockerAddr)
func NewProxy(c Config) (*Proxy, error) {
u, err := url.Parse(c.DockerAddr)
if err != nil {
return nil, err
}
@@ -57,18 +57,31 @@ func NewProxy(version, dockerAddr, listenAddr string, withDNS, withIPAM bool) (*
targetAddr = u.Path
}
return &Proxy{
Dial: func() (net.Conn, error) {
p := &Proxy{
Config: c,
dial: func() (net.Conn, error) {
return net.Dial(u.Scheme, targetAddr)
},
version: version,
client: client,
dockerAddr: dockerAddr,
listenAddr: listenAddr,
withDNS: withDNS,
dockerBridgeIP: string(dockerBridgeIP),
withIPAM: withIPAM,
}, nil
}
if p.WithDNS {
dockerBridgeIP, err := callWeave("docker-bridge-ip")
if err != nil {
return nil, err
}
p.dockerBridgeIP = string(dockerBridgeIP)
}
if err := p.TLSConfig.loadCerts(); err != nil {
Error.Fatalf("Could not configure tls for proxy: %s", err)
}
p.client, err = docker.NewClient(p.DockerAddr)
if err != nil {
return nil, err
}
return p, nil
}
func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -76,13 +89,13 @@ func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case containerCreateRegexp.MatchString(path):
proxy.serveWithInterceptor(&createContainerInterceptor{proxy.client, proxy.withDNS, proxy.dockerBridgeIP, proxy.withIPAM}, w, r)
proxy.serveWithInterceptor(&createContainerInterceptor{proxy.client, proxy.WithDNS, proxy.dockerBridgeIP, proxy.WithIPAM}, w, r)
case containerStartRegexp.MatchString(path):
proxy.serveWithInterceptor(&startContainerInterceptor{proxy.client, proxy.withDNS, proxy.withIPAM}, w, r)
proxy.serveWithInterceptor(&startContainerInterceptor{proxy.client, proxy.WithDNS, proxy.WithIPAM}, w, r)
case execCreateRegexp.MatchString(path):
proxy.serveWithInterceptor(&createExecInterceptor{proxy.client, proxy.withIPAM}, w, r)
proxy.serveWithInterceptor(&createExecInterceptor{proxy.client, proxy.WithIPAM}, w, r)
case strings.HasPrefix(path, "/status"):
fmt.Fprintln(w, "weave proxy", proxy.version)
fmt.Fprintln(w, "weave proxy", proxy.Version)
fmt.Fprintln(w, proxy.Status())
default:
proxy.serveWithInterceptor(&nullInterceptor{}, w, r)
@@ -90,20 +103,28 @@ func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
func (proxy *Proxy) serveWithInterceptor(i interceptor, w http.ResponseWriter, r *http.Request) {
newClient(proxy.Dial, i).ServeHTTP(w, r)
newClient(proxy.dial, i).ServeHTTP(w, r)
}
// Return status string
func (proxy *Proxy) Status() string {
var buf bytes.Buffer
fmt.Fprintln(&buf, "Listen address is", proxy.listenAddr)
fmt.Fprintln(&buf, "Docker address is", proxy.dockerAddr)
if proxy.withDNS {
fmt.Fprintln(&buf, "Listen address is", proxy.ListenAddr)
fmt.Fprintln(&buf, "Docker address is", proxy.DockerAddr)
switch {
case proxy.TLSConfig.Verify:
fmt.Fprintln(&buf, "TLS verify")
case proxy.TLSConfig.Enabled:
fmt.Fprintln(&buf, "TLS on")
default:
fmt.Fprintln(&buf, "TLS off")
}
if proxy.WithDNS {
fmt.Fprintln(&buf, "DNS on")
} else {
fmt.Fprintln(&buf, "DNS off")
}
if proxy.withIPAM {
if proxy.WithIPAM {
fmt.Fprintln(&buf, "IPAM on")
} else {
fmt.Fprintln(&buf, "IPAM off")
@@ -112,8 +133,18 @@ func (proxy *Proxy) Status() string {
}
func (proxy *Proxy) ListenAndServe() error {
return (&http.Server{
Addr: proxy.listenAddr,
Handler: proxy,
}).ListenAndServe()
listener, err := net.Listen("tcp", proxy.ListenAddr)
if err != nil {
return err
}
if proxy.TLSConfig.enabled() {
listener = tls.NewListener(listener, proxy.TLSConfig.Config)
Info.Println("TLS Enabled")
}
Info.Printf("Listening on %s", proxy.ListenAddr)
Info.Printf("Proxying %s", proxy.DockerAddr)
return (&http.Server{Handler: proxy}).Serve(listener)
}
+76
View File
@@ -0,0 +1,76 @@
package proxy
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/docker/docker/pkg/homedir"
)
type TLSConfig struct {
Enabled, Verify bool
Cert, Key, CACert string
*tls.Config
}
func (c *TLSConfig) enabled() bool {
if c == nil {
return false
}
return c.Enabled || c.Verify
}
func (c *TLSConfig) loadCerts() error {
if !c.enabled() {
return nil
}
dockerCertPath := os.Getenv("DOCKER_CERT_PATH")
if dockerCertPath == "" {
dockerCertPath = filepath.Join(homedir.Get(), ".docker")
}
if c.CACert == "" {
c.CACert = filepath.Join(dockerCertPath, defaultCaFile)
}
if c.Cert == "" {
c.Cert = filepath.Join(dockerCertPath, defaultCertFile)
}
if c.Key == "" {
c.Key = filepath.Join(dockerCertPath, defaultKeyFile)
}
tlsConfig := &tls.Config{
NextProtos: []string{"http/1.1"},
// Avoid fallback on insecure SSL protocols
MinVersion: tls.VersionTLS10,
}
if c.Verify {
certPool := x509.NewCertPool()
file, err := ioutil.ReadFile(c.CACert)
if err != nil {
return fmt.Errorf("Couldn't read CA certificate: %v", err)
}
certPool.AppendCertsFromPEM(file)
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
tlsConfig.ClientCAs = certPool
}
_, errCert := os.Stat(c.Cert)
_, errKey := os.Stat(c.Key)
if errCert == nil && errKey == nil {
cert, err := tls.LoadX509KeyPair(c.Cert, c.Key)
if err != nil {
return fmt.Errorf("Couldn't load X509 key pair: %q. Make sure the key is encrypted", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
c.Config = tlsConfig
return nil
}
+50 -6
View File
@@ -5,6 +5,16 @@ layout: default
# Weave Proxy
* [Advantages](#advantages)
* [Setup](#setup)
* [Usage](#usage)
* [Usage with IPAM](#usage-with-ipam)
* [Usage with WeaveDNS](#usage-with-weavedns)
* [Usage with TLS](#usage-with-tls)
* [Limitations](#limitations)
## <a name="advantages"></a>Advantages
Instead of the `weave` command-line utility, you may prefer to use the
standard [Docker command-line
interface](https://docs.docker.com/reference/commandline/cli/), or the
@@ -20,7 +30,7 @@ starting a container's application process. Furthermore, containers
can be started in foreground mode, and can be automatically removed
(with the ususal `--rm`).
## Setup
## <a name="setup"></a>Setup
Start the proxy with
@@ -35,7 +45,7 @@ your `DOCKER_HOST`.
host1$ export DOCKER_HOST=tcp://host1:12375
host1$ docker ps
## Usage
## <a name="usage"></a>Usage
When containers are created via the weave proxy, their entrypoint will
be modified to wait for the weave network interface to become
@@ -56,7 +66,7 @@ Multiple IP addresses and networks can be supplied in the `WEAVE_CIDR`
variable by space-separating them, as in
`WEAVE_CIDR="10.2.1.1/24 10.2.2.1/24"`.
## Usage with IPAM
## <a name="usage-with-ipam"></a>Usage with IPAM
To automatically assign a unique IP address to a container, weave must
be told on startup what range of addresses to allocate from. For
@@ -86,7 +96,7 @@ and attached to the weave network when we start them. For example:
More details on IPAM can be found in the [IPAM
documentation](ipam.html).
## Usage with WeaveDNS
## <a name="usage-with-weavedns"></a>Usage with WeaveDNS
Containers started via the proxy can be automatically configured to
use WeaveDNS for name resolution. To accomplish this we need to launch
@@ -102,7 +112,41 @@ resolution. WeaveDNS is used in addition to any dns servers specified
via the `--dns` option. More details on weaveDNS can be found in the
[weaveDNS documentation](weavedns.html).
## Limitations
## <a name="usage-with-tls"></a>Usage with TLS
If you are using docker with TLS, you probably want to talk to
the proxy over TLS. Launching the proxy with TLS is done
with the same command-line flags as the docker daemon. For example, if
you have generated your certificates and keys into the docker host's
`/tls` directory, we can launch the proxy with:
host1$ weave launch-proxy --tlsverify --tlscacert=/tls/ca.pem \
--tlscert=/tls/server-cert.pem --tlskey=/tls/server-key.pem
The paths to your certificates and key must be provided as absolute
paths which exist on the docker host.
Because the proxy connects to the docker daemon at
`unix:///var/run/docker.sock`, you must ensure that the daemon is
listening there. To do this, you will need to pass the `-H
unix:///var/run/docker.sock` option when starting the docker daemon, in
addition to the `-H` options for configuring the TCP listener. See
[the Docker
documentation](https://docs.docker.com/articles/basics/#bind-docker-to-another-hostport-or-a-unix-socket) for an example.
With the proxy running over TLS, we can configure our regular docker
client
host1$ docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem \
--tlskey=key.pem -H=tcp://host1:12375 version
The certificates and keys used by the client will need to be present
on your client machine.
More details on generating certificates, and configuring docker with
TLS can be found in the [docker
documentation](https://docs.docker.com/articles/https/).
## <a name="limitations"></a>Limitations
* The proxy does not currently support TLS.
* If you have a firewall, you will need to make sure port 12375 is open.
+18
View File
@@ -0,0 +1,18 @@
#! /bin/bash
. ./config.sh
start_suite "Boot the proxy with TLS-enabled Docker support"
PWD=$($SSH $HOST1 pwd)
weave_on $HOST1 launch
weave_on $HOST1 launch-proxy \
--tlsverify \
--tlscacert $PWD/tls/ca.pem \
--tlscert $PWD/tls/$HOST1.pem \
--tlskey $PWD/tls/$HOST1-key.pem
assert_raises "DOCKER_CERT_PATH=./tls proxy docker_on $HOST1 --tlsverify ps"
end_suite
+4 -1
View File
@@ -4,7 +4,9 @@ set -e
. ./config.sh
echo Copying weave images and script to hosts
(cd ./tls && go get ./... && go run generate_certs.go $HOSTS)
echo Copying weave images, scripts, and certificates to hosts
for HOST in $HOSTS; do
docker_on $HOST load -i ../weave.tar
docker_on $HOST load -i ../weavedns.tar
@@ -13,5 +15,6 @@ for HOST in $HOSTS; do
cat ../bin/docker-ns | run_on $HOST sh -c "cat > $DOCKER_NS"
cat ../weave | run_on $HOST sh -c "cat > ./weave"
run_on $HOST chmod a+x $DOCKER_NS ./weave
rsync -az -e "$SSH" ./tls/ $HOST:~/tls
run_on $HOST sudo service docker restart
done
+81
View File
@@ -0,0 +1,81 @@
package main
import (
"fmt"
"log"
"os"
"github.com/docker/machine/utils"
)
const (
org = "vagrant"
bits = 2048
caKeyPath = "ca-key.pem"
caCertPath = "ca.pem"
clientKeyPath = "key.pem"
clientCertPath = "cert.pem"
serverKeyPath = "server-key.pem"
serverCertPath = "server.pem"
)
func main() {
if _, err := os.Stat(caCertPath); os.IsNotExist(err) {
log.Printf("Creating CA: %s", caCertPath)
// check if the key path exists; if so, error
if _, err := os.Stat(caKeyPath); err == nil {
log.Fatalf("The CA key already exists. Please remove it or specify a different key/cert.")
}
if err := utils.GenerateCACertificate(caCertPath, caKeyPath, org, bits); err != nil {
log.Printf("Error generating CA certificate: %s", err)
}
}
if _, err := os.Stat(clientCertPath); os.IsNotExist(err) {
log.Printf("Creating client certificate: %s", clientCertPath)
// check if the key path exists; if so, error
if _, err := os.Stat(clientKeyPath); err == nil {
log.Fatalf("The client key already exists. Please remove it or specify a different key/cert.")
}
err = utils.GenerateCert(
[]string{""},
clientCertPath,
clientKeyPath,
caCertPath,
caKeyPath,
org,
bits,
)
if err != nil {
log.Fatalf("Error generating client certificate: %s", err)
}
}
if len(os.Args) <= 1 {
return
}
for _, ip := range os.Args[1:] {
serverKeyPath := fmt.Sprintf("%s-key.pem", ip)
serverCertPath := fmt.Sprintf("%s.pem", ip)
if _, err := os.Stat(serverCertPath); os.IsNotExist(err) {
log.Printf("Creating server certificate: %s", serverCertPath)
err = utils.GenerateCert(
[]string{ip},
serverCertPath,
serverKeyPath,
caCertPath,
caKeyPath,
org,
bits,
)
if err != nil {
log.Fatalf("error generating server cert: %s", err)
}
}
}
}
+52 -2
View File
@@ -746,6 +746,53 @@ get_ipam_cidr() {
fi
}
######################################################################
# weave proxy helpers
######################################################################
# TODO: Handle relative paths for args
# TODO: Handle args with spaces
tls_arg() {
filename=${3:-$2}
PROXY_VOLUMES="$PROXY_VOLUMES -v $1:/home/weave/tls/$filename.pem"
PROXY_ARGS="$PROXY_ARGS --tls$2 /home/weave/tls/$filename.pem"
}
proxy_args() {
PROXY_VOLUMES=""
PROXY_ARGS=""
while [ $# -gt 0 ]; do
case "$1" in
--tlscacert)
shift
tls_arg "$1" cacert ca
;;
--tlscacert=*)
tls_arg "${1#*=}" cacert ca
;;
--tlscert)
shift
tls_arg "$1" cert
;;
--tlscert=*)
tls_arg "${1#*=}" cert
;;
--tlskey)
shift
tls_arg "$1" key
;;
--tlskey=*)
tls_arg "${1#*=}" key
;;
*)
PROXY_ARGS="$PROXY_ARGS $1"
;;
esac
shift
done
}
##########################################################################################
[ $(id -u) = 0 ] || {
@@ -880,12 +927,15 @@ case "$COMMAND" in
# Set WEAVEPROXY_DOCKER_ARGS in the environment in order to supply
# additional parameters, such as resource limits, to docker
# when launching the weaveproxy container.
proxy_args "$@"
PROXY_CONTAINER=$(docker run --privileged -d --name=$PROXY_CONTAINER_NAME --net=host \
-p $PROXY_PORT:$PROXY_PORT/tcp \
-v /var/run/docker.sock:/var/run/docker.sock -v /proc:/hostproc \
$PROXY_VOLUMES \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /proc:/hostproc \
-e PROCFS=/hostproc \
--entrypoint=/home/weave/weaveproxy \
$WEAVEPROXY_DOCKER_ARGS $EXEC_IMAGE "$@")
$WEAVEPROXY_DOCKER_ARGS $EXEC_IMAGE $PROXY_ARGS)
wait_for_status_ip $PROXY_CONTAINER_NAME 127.0.0.1 $PROXY_PORT
echo $PROXY_CONTAINER
;;