Skip to content
Closed
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
1 change: 1 addition & 0 deletions .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ script:
# Setup the DockerInDocker environment.
- hack/dind
# Tests relying on StartWithBusybox make Drone time out.
- rm integration-cli/docker_cli_auth_test.go
- rm integration-cli/docker_cli_daemon_test.go
- rm integration-cli/docker_cli_exec_test.go
# Validate and test.
Expand Down
159 changes: 159 additions & 0 deletions api/client/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package client

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

"github.com/docker/libtrust"
)

// NewIdentityAuthTLSConfig creates a tls.Config for the client to use for
// libtrust identity authentication
func NewIdentityAuthTLSConfig(trustKey libtrust.PrivateKey, knownHostsPath, proto, addr string) (*tls.Config, error) {
tlsConfig := createTLSConfig()

// Load known hosts
knownHosts, err := libtrust.LoadKeySetFile(knownHostsPath)
if err != nil {
return nil, fmt.Errorf("Could not load trusted hosts file: %s", err)
}

// Generate CA pool from known hosts
allowedHosts, err := libtrust.FilterByHosts(knownHosts, addr, false)
if err != nil {
return nil, fmt.Errorf("Error filtering hosts: %s", err)
}
certPool, err := libtrust.GenerateCACertPool(trustKey, allowedHosts)
if err != nil {
return nil, fmt.Errorf("Could not create CA pool: %s", err)
}
tlsConfig.ServerName = "docker"
tlsConfig.RootCAs = certPool

// Generate client cert from trust key
x509Cert, err := libtrust.GenerateSelfSignedClientCert(trustKey)
if err != nil {
return nil, fmt.Errorf("Certificate generation error: %s", err)
}
tlsConfig.Certificates = []tls.Certificate{{
Certificate: [][]byte{x509Cert.Raw},
PrivateKey: trustKey.CryptoPrivateKey(),
Leaf: x509Cert,
}}

// Connect to server to see if it is a known host
tlsConfig.InsecureSkipVerify = true
testConn, err := tls.Dial(proto, addr, tlsConfig)
if err != nil {
return nil, fmt.Errorf("TLS Handshake error: %s", err)
}
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 {
return nil, fmt.Errorf("Error extracting public key from certificate: %s", err)
}

// If server is not a known host, prompt user to ask whether it should
// be trusted and add to the known hosts file
if promptUnknownKey(pubKey, addr) {
pubKey.AddExtendedField("hosts", []string{addr})
err = libtrust.AddKeySetFile(knownHostsPath, pubKey)
if err != nil {
return nil, fmt.Errorf("Error saving updated host keys file: %s", err)
}

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

testConn.Close()
tlsConfig.InsecureSkipVerify = false

return tlsConfig, nil
}

// NewCertAuthTLSConfig creates a tls.Config for the client to use for
// certificate authentication
func NewCertAuthTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) {
tlsConfig := createTLSConfig()

// Verify the server against a CA certificate?
if caPath != "" {
certPool := x509.NewCertPool()
file, err := ioutil.ReadFile(caPath)
if err != nil {
return nil, fmt.Errorf("Couldn't read ca cert %s: %s", caPath, err)
}
certPool.AppendCertsFromPEM(file)
tlsConfig.RootCAs = certPool
} else {
tlsConfig.InsecureSkipVerify = true
}

// Try to load and send client certificates
if certPath != "" && keyPath != "" {
_, errCert := os.Stat(certPath)
_, errKey := os.Stat(keyPath)
if errCert == nil && errKey == nil {
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, fmt.Errorf("Couldn't load X509 key pair: %s. Key encrypted?", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
}
return tlsConfig, nil
}

// createTLSConfig creates the base tls.Config used by auth methods with some
// sensible defaults
func createTLSConfig() *tls.Config {
return &tls.Config{
// Avoid fallback to SSL protocols < TLS1.0
MinVersion: tls.VersionTLS10,
}
}

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"
}
11 changes: 8 additions & 3 deletions api/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import (
"fmt"
"mime"
"os"
"path"
"path/filepath"
"strings"

log "github.com/Sirupsen/logrus"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/version"
"github.com/docker/docker/vendor/src/github.com/docker/libtrust"
"github.com/docker/libtrust"
)

const (
Expand Down Expand Up @@ -54,7 +54,7 @@ func MatchesContentType(contentType, expectedType string) bool {
// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
// otherwise generates a new one
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
err := os.MkdirAll(path.Dir(trustKeyPath), 0700)
err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700)
if err != nil {
return nil, err
}
Expand All @@ -67,6 +67,11 @@ func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil {
return nil, fmt.Errorf("Error saving key file: %s", err)
}
dir, file := filepath.Split(trustKeyPath)
// Save public key
if err := libtrust.SavePublicKey(filepath.Join(dir, "public-"+file), trustKey.PublicKey()); err != nil {
return nil, fmt.Errorf("Error saving public key file: %s", err)
}
} else if err != nil {
return nil, fmt.Errorf("Error loading key file: %s", err)
}
Expand Down
177 changes: 177 additions & 0 deletions api/server/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package server

import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"os"
"path"
"sync"

"github.com/docker/libtrust"
)

// ClientKeyManager manages client keys on the filesystem
type ClientKeyManager struct {
key libtrust.PrivateKey
clientFile string
clientDir string

clientLock sync.RWMutex
clients []libtrust.PublicKey

configLock sync.Mutex
configs []*tls.Config
}

// NewClientKeyManager loads a new manager from a set of key files
// and managed by the given private key.
func NewClientKeyManager(trustKey libtrust.PrivateKey, clientFile, clientDir string) (*ClientKeyManager, error) {
m := &ClientKeyManager{
key: trustKey,
clientFile: clientFile,
clientDir: clientDir,
}
if err := m.loadKeys(); err != nil {
return nil, err
}
// TODO Start watching file and directory

return m, nil
}
func (c *ClientKeyManager) loadKeys() error {
// Load authorized keys file
var clients []libtrust.PublicKey
if c.clientFile != "" {
fileClients, err := libtrust.LoadKeySetFile(c.clientFile)
if err != nil {
return fmt.Errorf("unable to load authorized keys: %s", err)
}
clients = fileClients
}

// Add clients from authorized keys directory
files, err := ioutil.ReadDir(c.clientDir)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("unable to open authorized keys directory: %s", err)
}
for _, f := range files {
if !f.IsDir() {
publicKey, err := libtrust.LoadPublicKeyFile(path.Join(c.clientDir, f.Name()))
if err != nil {
return fmt.Errorf("unable to load authorized key file: %s", err)
}
clients = append(clients, publicKey)
}
}

c.clientLock.Lock()
c.clients = clients
c.clientLock.Unlock()

return nil
}

// RegisterTLSConfig registers a tls configuration to manager
// such that any changes to the keys may be reflected in
// the tls client CA pool
func (c *ClientKeyManager) RegisterTLSConfig(tlsConfig *tls.Config) error {
c.clientLock.RLock()
certPool, err := libtrust.GenerateCACertPool(c.key, c.clients)
if err != nil {
return fmt.Errorf("CA pool generation error: %s", err)
}
c.clientLock.RUnlock()

tlsConfig.ClientCAs = certPool

c.configLock.Lock()
c.configs = append(c.configs, tlsConfig)
c.configLock.Unlock()

return nil
}

// NewIdentityAuthTLSConfig creates a tls.Config for the server to use for
// libtrust identity authentication
func NewIdentityAuthTLSConfig(trustKey libtrust.PrivateKey, clients *ClientKeyManager, addr string) (*tls.Config, error) {
tlsConfig := createTLSConfig()

tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
if err := clients.RegisterTLSConfig(tlsConfig); err != nil {
return nil, err
}

// Generate cert
ips, domains, err := parseAddr(addr)
if err != nil {
return nil, 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 nil, fmt.Errorf("certificate generation error: %s", err)
}
tlsConfig.Certificates = []tls.Certificate{{
Certificate: [][]byte{x509Cert.Raw},
PrivateKey: trustKey.CryptoPrivateKey(),
Leaf: x509Cert,
}}

return tlsConfig, nil
}

// NewCertAuthTLSConfig creates a tls.Config for the server to use for
// certificate authentication
func NewCertAuthTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) {
tlsConfig := createTLSConfig()

cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", certPath, keyPath, err)
}
tlsConfig.Certificates = []tls.Certificate{cert}

// Verify client certificates against a CA?
if caPath != "" {
certPool := x509.NewCertPool()
file, err := ioutil.ReadFile(caPath)
if err != nil {
return nil, fmt.Errorf("Couldn't read CA certificate: %s", err)
}
certPool.AppendCertsFromPEM(file)

tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
tlsConfig.ClientCAs = certPool
}

return tlsConfig, nil
}

func createTLSConfig() *tls.Config {
return &tls.Config{
NextProtos: []string{"http/1.1"},
// Avoid fallback on insecure SSL protocols
MinVersion: tls.VersionTLS10,
}
}

// 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
}
Loading