Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 79 additions & 10 deletions api/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bufio"
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"expvar"
Expand All @@ -15,6 +14,7 @@ import (
"net/http"
"net/http/pprof"
"os"
"path"
"strconv"
"strings"
"syscall"
Expand All @@ -33,6 +33,7 @@ import (
"github.com/docker/docker/pkg/version"
"github.com/docker/docker/registry"
"github.com/docker/docker/utils"
"github.com/docker/libtrust"
)

var (
Expand Down Expand Up @@ -1391,6 +1392,23 @@ func changeGroup(addr string, nameOrGid string) error {
return os.Chown(addr, 0, gid)
}

// parseAddr parses an address into an array of IPs and domains
func parseAddr(addr string) ([]net.IP, []string, error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, nil, err
}
var domains []string
var ips []net.IP
ip := net.ParseIP(host)
if ip != nil {
ips = []net.IP{ip}
} else {
domains = []string{host}
}
return ips, domains, nil
}

// ListenAndServe sets up the required http.Server and gets it listening for
// each addr passed in and does protocol specific checking.
func ListenAndServe(proto, addr string, job *engine.Job) error {
Expand Down Expand Up @@ -1428,24 +1446,75 @@ func ListenAndServe(proto, addr string, job *engine.Job) error {
return err
}

if proto != "unix" && (job.GetenvBool("Tls") || job.GetenvBool("TlsVerify")) {
if proto != "unix" && (!job.GetenvBool("Insecure")) {
trustKeyFile := job.Getenv("TrustKey")
err = os.MkdirAll(path.Dir(trustKeyFile), 0700)
if err != nil {
return fmt.Errorf("Error creating directory: %s", err)
}
trustKey, err := libtrust.LoadKeyFile(trustKeyFile)
if err == libtrust.ErrKeyFileDoesNotExist {
trustKey, err = libtrust.GenerateECP256PrivateKey()
if err != nil {
return fmt.Errorf("Error generating key: %s", err)
}
if err := libtrust.SaveKey(trustKeyFile, trustKey); err != nil {
return fmt.Errorf("Error saving key file: %s", err)
}
} else if err != nil {
return fmt.Errorf("Error loading key file: %s", err)
}

var cert tls.Certificate
tlsCert := job.Getenv("TlsCert")
tlsKey := job.Getenv("TlsKey")
cert, err := tls.LoadX509KeyPair(tlsCert, tlsKey)
if err != nil {
return fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?",
tlsCert, tlsKey, err)
if tlsCert != "" && tlsKey != "" {
var err error
cert, err = tls.LoadX509KeyPair(tlsCert, tlsKey)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?",
tlsCert, tlsKey, err)
}
ips, domains, err := parseAddr(addr)
if err != nil {
return err
}
// add default docker domain for docker clients to look for
domains = append(domains, "docker")
x509Cert, err := libtrust.GenerateSelfSignedServerCert(trustKey, domains, ips)
if err != nil {
return fmt.Errorf("certificate generation error: %s", err)
}
cert = tls.Certificate{
Certificate: [][]byte{x509Cert.Raw},
PrivateKey: trustKey.CryptoPrivateKey(),
Leaf: x509Cert,
}
}

}

tlsConfig := &tls.Config{
NextProtos: []string{"http/1.1"},
Certificates: []tls.Certificate{cert},
// Avoid fallback on insecure SSL protocols
MinVersion: tls.VersionTLS10,
}

// Load authorized keys file
clients, err := libtrust.LoadKeySetFile(job.Getenv("TrustClients"))
if err != nil {
return fmt.Errorf("unable to load authorized keys: %s", err)
}

if job.GetenvBool("TlsVerify") {
certPool := x509.NewCertPool()
certPool, poolErr := libtrust.GenerateCACertPool(trustKey, clients)
if poolErr != nil {
return fmt.Errorf("CA pool generation error: %s", poolErr)
}
file, err := ioutil.ReadFile(job.Getenv("TlsCa"))
if err != nil {
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("Couldn't read CA certificate: %s", err)
}
certPool.AppendCertsFromPEM(file)
Expand All @@ -1459,8 +1528,8 @@ func ListenAndServe(proto, addr string, job *engine.Job) error {
// Basic error and sanity checking
switch proto {
case "tcp":
if !strings.HasPrefix(addr, "127.0.0.1") && !job.GetenvBool("TlsVerify") {
log.Infof("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
if job.GetenvBool("Insecure") {
log.Infof("/!\\ DON'T BIND INSECURELY ON A TCP ADDRESS IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
case "unix":
socketGroup := job.Getenv("SocketGroup")
Expand Down
3 changes: 3 additions & 0 deletions docker/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,14 @@ func mainDaemon() {
job.Setenv("Version", dockerversion.VERSION)
job.Setenv("SocketGroup", *flSocketGroup)

job.SetenvBool("Insecure", *flInsecure)
job.SetenvBool("Tls", *flTls)
job.SetenvBool("TlsVerify", *flTlsVerify)
job.Setenv("TlsCa", *flCa)
job.Setenv("TlsCert", *flCert)
job.Setenv("TlsKey", *flKey)
job.Setenv("TrustKey", *flTrustKey)
job.Setenv("TrustClients", *flTrustClients)
job.SetenvBool("BufferRequests", true)
if err := job.Run(); err != nil {
log.Fatal(err)
Expand Down
157 changes: 138 additions & 19 deletions docker/docker.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package main

import (
"bufio"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"time"

"github.com/docker/docker/api"
"github.com/docker/docker/api/client"
Expand All @@ -15,13 +18,16 @@ import (
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/reexec"
"github.com/docker/docker/utils"
"github.com/docker/libtrust"
)

const (
defaultTrustKeyFile = "key.json"
defaultCaFile = "ca.pem"
defaultKeyFile = "key.pem"
defaultCertFile = "cert.pem"
defaultTrustKeyFile = "key.json"
defaultHostKeysFile = "allowed_hosts.json"
defaultClientKeysFile = "authorized_keys.json"
defaultCaFile = "ca.pem"
defaultKeyFile = "key.pem"
defaultCertFile = "cert.pem"
)

func main() {
Expand Down Expand Up @@ -51,6 +57,7 @@ func main() {
}
flHosts = append(flHosts, defaultHost)
}
*flTlsVerify = true

if *flDaemon {
mainDaemon()
Expand All @@ -62,45 +69,145 @@ func main() {
}
protoAddrParts := strings.SplitN(flHosts[0], "://", 2)

err := os.MkdirAll(path.Dir(*flTrustKey), 0700)
if err != nil {
log.Fatal(err)
}
trustKey, err := libtrust.LoadKeyFile(*flTrustKey)
if err == libtrust.ErrKeyFileDoesNotExist {
trustKey, err = libtrust.GenerateECP256PrivateKey()
if err != nil {
log.Fatalf("Error generating key: %s", err)
}
if err := libtrust.SaveKey(*flTrustKey, trustKey); err != nil {
log.Fatalf("Error saving key file: %s", err)
}
} else if err != nil {
log.Fatalf("Error loading key file: %s", err)
}

var (
cli *client.DockerCli
tlsConfig tls.Config
)
tlsConfig.InsecureSkipVerify = true

// Load known hosts
knownHosts, err := libtrust.LoadKeySetFile(*flTrustHosts)
if err != nil {
log.Fatalf("Could not load trusted hosts file: %s", err)
}

// If we should verify the server, we need to load a trusted ca
if *flTlsVerify {
*flTls = true
certPool := x509.NewCertPool()
file, err := ioutil.ReadFile(*flCa)
allowedHosts, err := libtrust.FilterByHosts(knownHosts, protoAddrParts[1], false)
if err != nil {
log.Fatalf("Couldn't read ca cert %s: %s", *flCa, err)
log.Fatalf("Error filtering hosts: %s", err)
}
certPool, err := libtrust.GenerateCACertPool(trustKey, allowedHosts)
if err != nil {
log.Fatalf("Could not create CA pool: %s", err)
}
if *flCa != "" {
file, err := ioutil.ReadFile(*flCa)
if err != nil {
if !os.IsNotExist(err) {
log.Fatalf("Couldn't read ca cert %s: %s", *flCa, err)
} else {
tlsConfig.ServerName = "docker"
}
} else {
certPool.AppendCertsFromPEM(file)
}
} else {
tlsConfig.ServerName = "docker"
}
certPool.AppendCertsFromPEM(file)
tlsConfig.RootCAs = certPool
tlsConfig.InsecureSkipVerify = false
}

// If tls is enabled, try to load and send client certificates
if *flTls || *flTlsVerify {
_, errCert := os.Stat(*flCert)
_, errKey := os.Stat(*flKey)
if errCert == nil && errKey == nil {
*flTls = true
cert, err := tls.LoadX509KeyPair(*flCert, *flKey)
if err != nil {
*flTls = true
cert, err := tls.LoadX509KeyPair(*flCert, *flKey)
if err != nil {
if !os.IsNotExist(err) {
log.Fatalf("Couldn't load X509 key pair: %s. Key encrypted?", err)
} else {
x509Cert, certErr := libtrust.GenerateSelfSignedClientCert(trustKey)
if certErr != nil {
log.Fatalf("Certificate generation error: %s", certErr)
}
cert = tls.Certificate{
Certificate: [][]byte{x509Cert.Raw},
PrivateKey: trustKey.CryptoPrivateKey(),
Leaf: x509Cert,
}
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
tlsConfig.Certificates = []tls.Certificate{cert}
}

if protoAddrParts[0] != "unix" && (!*flInsecure) {
savedInsecure := tlsConfig.InsecureSkipVerify
tlsConfig.InsecureSkipVerify = true
testConn, connErr := tls.Dial(protoAddrParts[0], protoAddrParts[1], &tlsConfig)
if connErr != nil {
log.Fatalf("TLS Handshake error: %s", connErr)
}
opts := x509.VerifyOptions{
Roots: tlsConfig.RootCAs,
CurrentTime: time.Now(),
DNSName: tlsConfig.ServerName,
Intermediates: x509.NewCertPool(),
}

certs := testConn.ConnectionState().PeerCertificates
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
_, err = certs[0].Verify(opts)
if err != nil {
if _, ok := err.(x509.UnknownAuthorityError); ok {
pubKey, err := libtrust.FromCryptoPublicKey(certs[0].PublicKey)
if err != nil {
log.Fatalf("Error extracting public key from certificate: %s", err)
}

if promptUnknownKey(pubKey, protoAddrParts[1]) {
pubKey.AddExtendedField("hosts", []string{protoAddrParts[1]})
err = libtrust.AddKeySetFile(*flTrustHosts, pubKey)
if err != nil {
log.Fatalf("Error saving updated host keys file: %s", err)
}

ca, err := libtrust.GenerateCACert(trustKey, pubKey)
if err != nil {
log.Fatalf("Error generating CA: %s", err)
}
tlsConfig.RootCAs.AddCert(ca)
} else {
log.Fatalf("Cancelling request due to invalid certificate")
}
} else {
log.Fatalf("TLS verification error: %s", connErr)
}
}

testConn.Close()
tlsConfig.InsecureSkipVerify = savedInsecure

// Avoid fallback to SSL protocols < TLS1.0
tlsConfig.MinVersion = tls.VersionTLS10
}

if *flTls || *flTlsVerify {
cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, nil, protoAddrParts[0], protoAddrParts[1], &tlsConfig)
if protoAddrParts[0] == "unix" || *flInsecure {
cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, trustKey, protoAddrParts[0], protoAddrParts[1], nil)
} else {
cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, nil, protoAddrParts[0], protoAddrParts[1], nil)
cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, trustKey, protoAddrParts[0], protoAddrParts[1], &tlsConfig)
}

if err := cli.Cmd(flag.Args()...); err != nil {
Expand All @@ -114,6 +221,18 @@ func main() {
}
}

func promptUnknownKey(key libtrust.PublicKey, host string) bool {
fmt.Printf("The authenticity of host %q can't be established.\nRemote key ID %s\n", host, key.KeyID())
fmt.Printf("Are you sure you want to continue connecting (yes/no)? ")
reader := bufio.NewReader(os.Stdin)
line, _, err := reader.ReadLine()
if err != nil {
log.Fatalf("Error reading input: %s", err)
}
input := strings.TrimSpace(strings.ToLower(string(line)))
return input == "yes" || input == "y"
}

func showVersion() {
fmt.Printf("Docker version %s, build %s\n", dockerversion.VERSION, dockerversion.GITCOMMIT)
}
Loading