TLS support.

TLS authentication support between the CLI and Swarm but also between
Swarm and the Docker nodes.

Closes #148.

Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
Andrea Luzzardi 2014-12-08 18:09:06 -08:00
parent c6815ee1fe
commit 39fe73a231
4 changed files with 103 additions and 6 deletions

View File

@ -51,6 +51,19 @@ $ swarm list --token=6856663cdefdec325839a4b7e1de38e8
http://<node_ip:2375>
```
### TLS
Swarm supports TLS authentication between the CLI and Swarm but also between Swarm and the Docker nodes.
In order to enable TLS, the same command line options as Docker can be specified:
`swarm manage --tlsverify --tlscacert=<CACERT> --tlscert=<CERT> --tlskey=<KEY> [...]`
Please refer to the [Docker documentation](https://docs.docker.com/articles/https/) for more information on how
to set up TLS authentication on Docker and generating the certificates.
Note that Swarm certificates must be generated with`extendedKeyUsage = clientAuth,serverAuth`.
## Participating
We welcome pull requests and patches; come say hi on IRC, #docker-swarm on freenode.

View File

@ -2,9 +2,11 @@ package api
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"regexp"
@ -362,7 +364,7 @@ func createRouter(c *context, enableCors bool) (*mux.Router, error) {
return r, nil
}
func ListenAndServe(c *cluster.Cluster, s *scheduler.Scheduler, addr, version string, enableCors bool) error {
func ListenAndServe(c *cluster.Cluster, s *scheduler.Scheduler, addr, version string, enableCors bool, tlsConfig *tls.Config) error {
context := &context{
cluster: c,
scheduler: s,
@ -374,9 +376,19 @@ func ListenAndServe(c *cluster.Cluster, s *scheduler.Scheduler, addr, version st
if err != nil {
return err
}
server := &http.Server{
Addr: addr,
Handler: r,
}
return server.ListenAndServe()
l, err := net.Listen("tcp", addr)
if err != nil {
return err
}
if tlsConfig != nil {
tlsConfig.NextProtos = []string{"http/1.1"}
l = tls.NewListener(l, tlsConfig)
}
return server.Serve(l)
}

27
main.go
View File

@ -54,6 +54,26 @@ func main() {
Name: "api-enable-cors, cors",
Usage: "enable CORS headers in the remote API",
}
flTls := cli.BoolFlag{
Name: "tls",
Usage: "Use TLS; implied by --tlsverify=true",
}
flTlsCaCert := cli.StringFlag{
Name: "tlscacert",
Usage: "Trust only remotes providing a certificate signed by the CA given here",
}
flTlsCert := cli.StringFlag{
Name: "tlscert",
Usage: "Path to TLS certificate file",
}
flTlsKey := cli.StringFlag{
Name: "tlskey",
Usage: "Path to TLS key file",
}
flTlsVerify := cli.BoolFlag{
Name: "tlsverify",
Usage: "Use TLS and verify the remote",
}
app.Commands = []cli.Command{
{
@ -91,8 +111,11 @@ func main() {
Name: "manage",
ShortName: "m",
Usage: "manage a docker cluster",
Flags: []cli.Flag{flToken, flAddr, flHeartBeat, flEnableCors},
Action: manage,
Flags: []cli.Flag{
flToken, flAddr, flHeartBeat,
flTls, flTlsCaCert, flTlsCert, flTlsKey, flTlsVerify,
flEnableCors},
Action: manage,
},
{
Name: "join",

View File

@ -1,6 +1,10 @@
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"strings"
"time"
@ -22,7 +26,52 @@ func (h *logHandler) Handle(e *cluster.Event) error {
return nil
}
// Load the TLS certificates/keys and, if verify is true, the CA.
func loadTlsConfig(ca, cert, key string, verify bool) (*tls.Config, error) {
c, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?",
cert, key, err)
}
config := &tls.Config{
Certificates: []tls.Certificate{c},
MinVersion: tls.VersionTLS10,
}
if verify {
certPool := x509.NewCertPool()
file, err := ioutil.ReadFile(ca)
if err != nil {
return nil, fmt.Errorf("Couldn't read CA certificate: %s", err)
}
certPool.AppendCertsFromPEM(file)
config.RootCAs = certPool
} else {
// If --tlsverify is not supplied, disable CA validation.
config.InsecureSkipVerify = true
}
return config, nil
}
func manage(c *cli.Context) {
var (
tlsConfig *tls.Config = nil
err error
)
// If either --tls or --tlsverify are specified, load the certificates.
if c.Bool("tls") || c.Bool("tlsverify") {
tlsConfig, err = loadTlsConfig(
c.String("tlscacert"),
c.String("tlscert"),
c.String("tlskey"),
c.Bool("tlsverify"))
if err != nil {
log.Fatal(err)
}
}
refresh := func(c *cluster.Cluster, nodes []string) {
for _, addr := range nodes {
@ -32,7 +81,7 @@ func manage(c *cli.Context) {
}
if c.Node(addr) == nil {
n := cluster.NewNode(addr)
if err := n.Connect(nil); err != nil {
if err := n.Connect(tlsConfig); err != nil {
log.Error(err)
return
}
@ -82,5 +131,5 @@ func manage(c *cli.Context) {
},
)
log.Fatal(api.ListenAndServe(cluster, s, c.String("addr"), c.App.Version, c.Bool("cors")))
log.Fatal(api.ListenAndServe(cluster, s, c.String("addr"), c.App.Version, c.Bool("cors"), tlsConfig))
}