[proxy] Add tls support

Caveats:
* TLS files must be present on the target server (and readable)
* TLS file paths cannot contain spaces
* TLS file paths must be absolute (i.e. cannot be relative)
* Bash argument parsing is terrible
This commit is contained in:
Paul Bellamy committed 2015-06-05 13:23:14 +01:00
1 parent 8514172e57
commit c31358259f
9 files changed
+290 -21

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
+27 -11
View File
@@ -16,13 +16,25 @@ var (
)
func main() {
var target, listen string
var withDNS, withIPAM, debug, justVersion bool
var (
debug bool
justVersion bool
listen string
target string
tlsConfig proxy.TLSConfig
withDNS bool
withIPAM bool
)
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.StringVar(&target, 'H', fmt.Sprintf("docker daemon URL to proxy (default %s)", defaultTarget))
getopt.StringVarLong(&tlsConfig.CACert, "tlscacert", 0, "Trust certs signed only by this CA")
getopt.StringVarLong(&tlsConfig.Cert, "tlscert", 0, "Path to TLS certificate file")
getopt.BoolVarLong(&tlsConfig.Enabled, "tls", 0, "Use TLS; implied by --tlsverify")
getopt.StringVarLong(&tlsConfig.Key, "tlskey", 0, "Path to TLS key file")
getopt.BoolVarLong(&tlsConfig.Verify, "tlsverify", 0, "Use TLS and verify the remote")
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.Parse()
@@ -32,6 +44,10 @@ func main() {
os.Exit(0)
}
if debug {
InitDefaultLogging(true)
}
if target == "" {
target = defaultTarget
}
@@ -40,18 +56,18 @@ func main() {
listen = defaultListen
}
if debug {
InitDefaultLogging(true)
}
p, err := proxy.NewProxy(version, target, listen, withDNS, withIPAM)
p, err := proxy.NewProxy(
version,
target,
listen,
withDNS,
withIPAM,
&tlsConfig,
)
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)
}
+31 -8
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$")
@@ -24,13 +31,14 @@ type Proxy struct {
version string
client *docker.Client
dockerAddr string
listenAddr string
withDNS bool
dockerBridgeIP string
listenAddr string
tlsConfig *TLSConfig
withDNS bool
withIPAM bool
}
func NewProxy(version, dockerAddr, listenAddr string, withDNS, withIPAM bool) (*Proxy, error) {
func NewProxy(version, dockerAddr, listenAddr string, withDNS, withIPAM bool, tlsConfig *TLSConfig) (*Proxy, error) {
u, err := url.Parse(dockerAddr)
if err != nil {
return nil, err
@@ -44,6 +52,10 @@ func NewProxy(version, dockerAddr, listenAddr string, withDNS, withIPAM bool) (*
}
}
if err := tlsConfig.loadCerts(); err != nil {
Error.Fatalf("Could not configure tls for proxy: %s", err)
}
client, err := docker.NewClient(dockerAddr)
if err != nil {
return nil, err
@@ -65,8 +77,9 @@ func NewProxy(version, dockerAddr, listenAddr string, withDNS, withIPAM bool) (*
client: client,
dockerAddr: dockerAddr,
listenAddr: listenAddr,
withDNS: withDNS,
dockerBridgeIP: string(dockerBridgeIP),
tlsConfig: tlsConfig,
withDNS: withDNS,
withIPAM: withIPAM,
}, nil
}
@@ -112,8 +125,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
}
+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)
}
}
}
}
+51 -1
View File
@@ -746,6 +746,53 @@ get_ipam_cidr() {
fi
}
######################################################################
# weave proxy helpers
######################################################################
# TODO: Handle relative paths for args
# TODO: Handle args with spaces
proxy_args() {
PROXY_VOLUMES=""
PROXY_ARGS=""
while [ $# -gt 0 ]; do
case "$1" in
--tlscacert)
shift
PROXY_VOLUMES="$PROXY_VOLUMES -v $1:/home/weave/tls/ca.pem"
PROXY_ARGS="$PROXY_ARGS --tlscacert /home/weave/tls/ca.pem"
;;
--tlscacert=*)
PROXY_VOLUMES="$PROXY_VOLUMES -v ${1#*=}:/home/weave/tls/ca.pem"
PROXY_ARGS="$PROXY_ARGS --tlscacert /home/weave/tls/ca.pem"
;;
--tlscert)
shift
PROXY_VOLUMES="$PROXY_VOLUMES -v $1:/home/weave/tls/cert.pem"
PROXY_ARGS="$PROXY_ARGS --tlscert /home/weave/tls/cert.pem"
;;
--tlscert=*)
PROXY_VOLUMES="$PROXY_VOLUMES -v ${1#*=}:/home/weave/tls/cert.pem"
PROXY_ARGS="$PROXY_ARGS --tlscert /home/weave/tls/cert.pem"
;;
--tlskey)
shift
PROXY_VOLUMES="$PROXY_VOLUMES -v $1:/home/weave/tls/key.pem"
PROXY_ARGS="$PROXY_ARGS --tlskey /home/weave/tls/key.pem"
;;
--tlskey=*)
PROXY_VOLUMES="$PROXY_VOLUMES -v ${1#*=}:/home/weave/tls/key.pem"
PROXY_ARGS="$PROXY_ARGS --tlskey /home/weave/tls/key.pem"
;;
*)
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 \
-e PROCFS=/hostproc \
$PROXY_VOLUMES \
--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
;;