mirror of https://github.com/docker/docs.git
306 lines
6.7 KiB
Go
306 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"time"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
"github.com/docker/libtrust"
|
|
"github.com/docker/machine/drivers"
|
|
)
|
|
|
|
var (
|
|
validHostNameChars = `[a-zA-Z0-9_]`
|
|
validHostNamePattern = regexp.MustCompile(`^` + validHostNameChars + `+$`)
|
|
)
|
|
|
|
type Host struct {
|
|
Name string `json:"-"`
|
|
DriverName string
|
|
Driver drivers.Driver
|
|
storePath string
|
|
}
|
|
|
|
type hostConfig struct {
|
|
DriverName string
|
|
}
|
|
|
|
func waitForDocker(addr string) error {
|
|
for {
|
|
conn, err := net.DialTimeout("tcp", addr, time.Second*5)
|
|
if err != nil {
|
|
time.Sleep(time.Second * 5)
|
|
continue
|
|
}
|
|
conn.Close()
|
|
break
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func NewHost(name, driverName, storePath string) (*Host, error) {
|
|
driver, err := drivers.NewDriver(driverName, name, storePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Host{
|
|
Name: name,
|
|
DriverName: driverName,
|
|
Driver: driver,
|
|
storePath: storePath,
|
|
}, nil
|
|
}
|
|
|
|
func LoadHost(name string, storePath string) (*Host, error) {
|
|
if _, err := os.Stat(storePath); os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("Host %q does not exist", name)
|
|
}
|
|
|
|
host := &Host{Name: name, storePath: storePath}
|
|
if err := host.LoadConfig(); err != nil {
|
|
return nil, err
|
|
}
|
|
return host, nil
|
|
}
|
|
|
|
func ValidateHostName(name string) (string, error) {
|
|
if !validHostNamePattern.MatchString(name) {
|
|
return name, fmt.Errorf("Invalid host name %q, it must match %s", name, validHostNamePattern)
|
|
}
|
|
return name, nil
|
|
}
|
|
|
|
func loadTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
|
|
if err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700); err != nil {
|
|
return nil, err
|
|
|
|
}
|
|
|
|
trustKey, err := libtrust.LoadKeyFile(trustKeyPath)
|
|
if err == libtrust.ErrKeyFileDoesNotExist {
|
|
trustKey, err = libtrust.GenerateECP256PrivateKey()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error generating key: %s", err)
|
|
}
|
|
|
|
if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil {
|
|
return nil, fmt.Errorf("error saving key file: %s", err)
|
|
|
|
}
|
|
|
|
dir, file := filepath.Split(trustKeyPath)
|
|
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)
|
|
|
|
}
|
|
return trustKey, nil
|
|
}
|
|
|
|
func (h *Host) addHostToKnownHosts() error {
|
|
tlsConfig := &tls.Config{
|
|
MinVersion: tls.VersionTLS10,
|
|
}
|
|
|
|
trustKeyPath := filepath.Join(drivers.GetDockerDir(), "key.json")
|
|
knownHostsPath := filepath.Join(drivers.GetDockerDir(), "known-hosts.json")
|
|
|
|
driverUrl, err := h.GetURL()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get machine url: %s", err)
|
|
}
|
|
|
|
u, err := url.Parse(driverUrl)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to parse machine url")
|
|
}
|
|
|
|
if u.Scheme == "unix" {
|
|
return nil
|
|
}
|
|
|
|
addr := u.Host
|
|
proto := "tcp"
|
|
|
|
trustKey, err := loadTrustKey(trustKeyPath)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to load trust key: %s", err)
|
|
}
|
|
|
|
knownHosts, err := libtrust.LoadKeySetFile(knownHostsPath)
|
|
if err != nil {
|
|
return fmt.Errorf("could not load trusted hosts file: %s", err)
|
|
}
|
|
|
|
allowedHosts, err := libtrust.FilterByHosts(knownHosts, addr, false)
|
|
if err != nil {
|
|
return fmt.Errorf("error filtering hosts: %s", err)
|
|
}
|
|
|
|
certPool, err := libtrust.GenerateCACertPool(trustKey, allowedHosts)
|
|
if err != nil {
|
|
return fmt.Errorf("Could not create CA pool: %s", err)
|
|
}
|
|
|
|
tlsConfig.ServerName = "docker"
|
|
tlsConfig.RootCAs = certPool
|
|
|
|
x509Cert, err := libtrust.GenerateSelfSignedClientCert(trustKey)
|
|
if err != nil {
|
|
return fmt.Errorf("certificate generation error: %s", err)
|
|
}
|
|
|
|
tlsConfig.Certificates = []tls.Certificate{{
|
|
Certificate: [][]byte{x509Cert.Raw},
|
|
PrivateKey: trustKey.CryptoPrivateKey(),
|
|
Leaf: x509Cert,
|
|
}}
|
|
|
|
tlsConfig.InsecureSkipVerify = true
|
|
|
|
log.Debugf("waiting for Docker to become available on %s", addr)
|
|
if err := waitForDocker(addr); err != nil {
|
|
return fmt.Errorf("unable to connect to Docker daemon: %s", err)
|
|
}
|
|
testConn, err := tls.Dial(proto, addr, tlsConfig)
|
|
if err != nil {
|
|
return 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)
|
|
}
|
|
|
|
if _, err := certs[0].Verify(opts); err != nil {
|
|
if _, ok := err.(x509.UnknownAuthorityError); ok {
|
|
pubKey, err := libtrust.FromCryptoPublicKey(certs[0].PublicKey)
|
|
if err != nil {
|
|
return fmt.Errorf("error extracting public key from cert: %s", err)
|
|
}
|
|
|
|
pubKey.AddExtendedField("hosts", []string{addr})
|
|
|
|
log.Debugf("Adding machine to known hosts: %s", addr)
|
|
|
|
if err := libtrust.AddKeySetFile(knownHostsPath, pubKey); err != nil {
|
|
return fmt.Errorf("error adding machine to known hosts: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
testConn.Close()
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) Create(name string) error {
|
|
if err := h.Driver.Create(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := h.SaveConfig(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := h.addHostToKnownHosts(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) Start() error {
|
|
return h.Driver.Start()
|
|
}
|
|
|
|
func (h *Host) Stop() error {
|
|
return h.Driver.Stop()
|
|
}
|
|
|
|
func (h *Host) Upgrade() error {
|
|
return h.Driver.Upgrade()
|
|
}
|
|
|
|
func (h *Host) Remove(force bool) error {
|
|
if err := h.Driver.Remove(); err != nil {
|
|
if !force {
|
|
return err
|
|
}
|
|
}
|
|
return h.removeStorePath()
|
|
}
|
|
|
|
func (h *Host) removeStorePath() error {
|
|
file, err := os.Stat(h.storePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !file.IsDir() {
|
|
return fmt.Errorf("%q is not a directory", h.storePath)
|
|
}
|
|
return os.RemoveAll(h.storePath)
|
|
}
|
|
|
|
func (h *Host) GetURL() (string, error) {
|
|
return h.Driver.GetURL()
|
|
}
|
|
|
|
func (h *Host) LoadConfig() error {
|
|
data, err := ioutil.ReadFile(filepath.Join(h.storePath, "config.json"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// First pass: find the driver name and load the driver
|
|
var config hostConfig
|
|
if err := json.Unmarshal(data, &config); err != nil {
|
|
return err
|
|
}
|
|
|
|
driver, err := drivers.NewDriver(config.DriverName, h.Name, h.storePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h.Driver = driver
|
|
|
|
// Second pass: unmarshal driver config into correct driver
|
|
if err := json.Unmarshal(data, &h); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) SaveConfig() error {
|
|
data, err := json.Marshal(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := ioutil.WriteFile(filepath.Join(h.storePath, "config.json"), data, 0600); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|