Browse code

Merge pull request #9201 from vieux/add_hostname_docker_info

Add hostname and ID docker info

Tibor Vass authored on 2014/11/21 03:11:16
Showing 19 changed files
... ...
@@ -508,6 +508,12 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
508 508
 	if remoteInfo.Exists("MemTotal") {
509 509
 		fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(remoteInfo.GetInt64("MemTotal"))))
510 510
 	}
511
+	if remoteInfo.Exists("Name") {
512
+		fmt.Fprintf(cli.out, "Name: %s\n", remoteInfo.Get("Name"))
513
+	}
514
+	if remoteInfo.Exists("ID") {
515
+		fmt.Fprintf(cli.out, "ID: %s\n", remoteInfo.Get("ID"))
516
+	}
511 517
 
512 518
 	if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" {
513 519
 		if remoteInfo.Exists("Debug") {
... ...
@@ -3,12 +3,15 @@ package api
3 3
 import (
4 4
 	"fmt"
5 5
 	"mime"
6
+	"os"
7
+	"path"
6 8
 	"strings"
7 9
 
8 10
 	log "github.com/Sirupsen/logrus"
9 11
 	"github.com/docker/docker/engine"
10 12
 	"github.com/docker/docker/pkg/parsers"
11 13
 	"github.com/docker/docker/pkg/version"
14
+	"github.com/docker/docker/vendor/src/github.com/docker/libtrust"
12 15
 )
13 16
 
14 17
 const (
... ...
@@ -47,3 +50,25 @@ func MatchesContentType(contentType, expectedType string) bool {
47 47
 	}
48 48
 	return err == nil && mimetype == expectedType
49 49
 }
50
+
51
+// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
52
+// otherwise generates a new one
53
+func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
54
+	err := os.MkdirAll(path.Dir(trustKeyPath), 0700)
55
+	if err != nil {
56
+		return nil, err
57
+	}
58
+	trustKey, err := libtrust.LoadKeyFile(trustKeyPath)
59
+	if err == libtrust.ErrKeyFileDoesNotExist {
60
+		trustKey, err = libtrust.GenerateECP256PrivateKey()
61
+		if err != nil {
62
+			return nil, fmt.Errorf("Error generating key: %s", err)
63
+		}
64
+		if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil {
65
+			return nil, fmt.Errorf("Error saving key file: %s", err)
66
+		}
67
+	} else if err != nil {
68
+		return nil, fmt.Errorf("Error loading key file: %s", err)
69
+	}
70
+	return trustKey, nil
71
+}
... ...
@@ -40,6 +40,7 @@ type Config struct {
40 40
 	DisableNetwork              bool
41 41
 	EnableSelinuxSupport        bool
42 42
 	Context                     map[string][]string
43
+	TrustKeyPath                string
43 44
 }
44 45
 
45 46
 // InstallFlags adds command-line options to the top-level flag parser for
... ...
@@ -15,6 +15,7 @@ import (
15 15
 	"github.com/docker/libcontainer/label"
16 16
 
17 17
 	log "github.com/Sirupsen/logrus"
18
+	"github.com/docker/docker/api"
18 19
 	"github.com/docker/docker/daemon/execdriver"
19 20
 	"github.com/docker/docker/daemon/execdriver/execdrivers"
20 21
 	"github.com/docker/docker/daemon/execdriver/lxc"
... ...
@@ -83,6 +84,7 @@ func (c *contStore) List() []*Container {
83 83
 }
84 84
 
85 85
 type Daemon struct {
86
+	ID             string
86 87
 	repository     string
87 88
 	sysInitPath    string
88 89
 	containers     *contStore
... ...
@@ -893,7 +895,13 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
893 893
 		return nil, err
894 894
 	}
895 895
 
896
+	trustKey, err := api.LoadOrCreateTrustKey(config.TrustKeyPath)
897
+	if err != nil {
898
+		return nil, err
899
+	}
900
+
896 901
 	daemon := &Daemon{
902
+		ID:             trustKey.PublicKey().KeyID(),
897 903
 		repository:     daemonRepo,
898 904
 		containers:     &contStore{s: make(map[string]*Container)},
899 905
 		execCommands:   newExecStore(),
... ...
@@ -56,6 +56,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status {
56 56
 		return job.Error(err)
57 57
 	}
58 58
 	v := &engine.Env{}
59
+	v.Set("ID", daemon.ID)
59 60
 	v.SetInt("Containers", len(daemon.List()))
60 61
 	v.SetInt("Images", imgcount)
61 62
 	v.Set("Driver", daemon.GraphDriver().String())
... ...
@@ -75,6 +76,9 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status {
75 75
 	v.Set("InitPath", initPath)
76 76
 	v.SetInt("NCPU", runtime.NumCPU())
77 77
 	v.SetInt64("MemTotal", meminfo.MemTotal)
78
+	if hostname, err := os.Hostname(); err == nil {
79
+		v.Set("Name", hostname)
80
+	}
78 81
 	if _, err := v.WriteTo(job.Stdout); err != nil {
79 82
 		return job.Error(err)
80 83
 	}
... ...
@@ -34,6 +34,8 @@ func mainDaemon() {
34 34
 	eng := engine.New()
35 35
 	signal.Trap(eng.Shutdown)
36 36
 
37
+	daemonCfg.TrustKeyPath = *flTrustKey
38
+
37 39
 	// Load builtins
38 40
 	if err := builtins.Register(eng); err != nil {
39 41
 		log.Fatal(err)
... ...
@@ -49,8 +49,8 @@ You can still call an old version of the API using
49 49
 `GET /info`
50 50
 
51 51
 **New!**
52
-`info` now returns the number of CPUs available on the machine (`NCPU`) and
53
-total memory available (`MemTotal`).
52
+`info` now returns the number of CPUs available on the machine (`NCPU`),
53
+total memory available (`MemTotal`), a user-friendly name describing the running Docker daemon (`Name`), and a unique ID identifying the daemon (`ID`).
54 54
 
55 55
 `POST /containers/create`
56 56
 
... ...
@@ -1220,6 +1220,8 @@ Display system-wide information
1220 1220
              "KernelVersion":"3.12.0-1-amd64"
1221 1221
              "NCPU":1,
1222 1222
              "MemTotal":2099236864,
1223
+             "Name":"prod-server-42",
1224
+             "ID":"7TRN:IPZB:QYBB:VPBQ:UMPP:KARE:6ZNR:XE6T:7EWV:PKF4:ZOJD:TPYS",
1223 1225
              "Debug":false,
1224 1226
              "NFd": 11,
1225 1227
              "NGoroutines":21,
... ...
@@ -856,6 +856,8 @@ For example:
856 856
     Kernel Version: 3.13.0-24-generic
857 857
     Operating System: Ubuntu 14.04 LTS
858 858
     CPUs: 1
859
+    Name: prod-server-42
860
+    ID: 7TRN:IPZB:QYBB:VPBQ:UMPP:KARE:6ZNR:XE6T:7EWV:PKF4:ZOJD:TPYS
859 861
     Total Memory: 2 GiB
860 862
     Debug mode (server): false
861 863
     Debug mode (client): true
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"net/http/httptest"
10 10
 	"os"
11 11
 	"path"
12
+	"path/filepath"
12 13
 	"strings"
13 14
 	"testing"
14 15
 	"time"
... ...
@@ -187,6 +188,7 @@ func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine {
187 187
 		// Either InterContainerCommunication or EnableIptables must be set,
188 188
 		// otherwise NewDaemon will fail because of conflicting settings.
189 189
 		InterContainerCommunication: true,
190
+		TrustKeyPath:                filepath.Join(root, "key.json"),
190 191
 	}
191 192
 	d, err := daemon.NewDaemon(cfg, eng)
192 193
 	if err != nil {
... ...
@@ -51,7 +51,7 @@ clone hg code.google.com/p/go.net 84a4013f96e0
51 51
 
52 52
 clone hg code.google.com/p/gosqlite 74691fb6f837
53 53
 
54
-clone git github.com/docker/libtrust d273ef2565ca
54
+clone git github.com/docker/libtrust 230dfd18c232
55 55
 
56 56
 clone git github.com/Sirupsen/logrus v0.6.0
57 57
 
... ...
@@ -55,16 +55,7 @@ func (k *ecPublicKey) CurveName() string {
55 55
 
56 56
 // KeyID returns a distinct identifier which is unique to this Public Key.
57 57
 func (k *ecPublicKey) KeyID() string {
58
-	// Generate and return a libtrust fingerprint of the EC public key.
59
-	// For an EC key this should be:
60
-	//   SHA256("EC"+curveName+bytes(X)+bytes(Y))
61
-	// Then truncated to 240 bits and encoded into 12 base32 groups like so:
62
-	//   ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
63
-	hasher := crypto.SHA256.New()
64
-	hasher.Write([]byte(k.KeyType() + k.CurveName()))
65
-	hasher.Write(k.X.Bytes())
66
-	hasher.Write(k.Y.Bytes())
67
-	return keyIDEncode(hasher.Sum(nil)[:30])
58
+	return keyIDFromCryptoKey(k)
68 59
 }
69 60
 
70 61
 func (k *ecPublicKey) String() string {
... ...
@@ -11,9 +11,21 @@ func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKe
11 11
 	filtered := make([]PublicKey, 0, len(keys))
12 12
 
13 13
 	for _, pubKey := range keys {
14
-		hosts, ok := pubKey.GetExtendedField("hosts").([]interface{})
14
+		var hosts []string
15
+		switch v := pubKey.GetExtendedField("hosts").(type) {
16
+		case []string:
17
+			hosts = v
18
+		case []interface{}:
19
+			for _, value := range v {
20
+				h, ok := value.(string)
21
+				if !ok {
22
+					continue
23
+				}
24
+				hosts = append(hosts, h)
25
+			}
26
+		}
15 27
 
16
-		if !ok || (ok && len(hosts) == 0) {
28
+		if len(hosts) == 0 {
17 29
 			if includeEmpty {
18 30
 				filtered = append(filtered, pubKey)
19 31
 			}
... ...
@@ -21,12 +33,7 @@ func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKe
21 21
 		}
22 22
 
23 23
 		// Check if any hosts match pattern
24
-		for _, hostVal := range hosts {
25
-			hostPattern, ok := hostVal.(string)
26
-			if !ok {
27
-				continue
28
-			}
29
-
24
+		for _, hostPattern := range hosts {
30 25
 			match, err := filepath.Match(hostPattern, host)
31 26
 			if err != nil {
32 27
 				return nil, err
... ...
@@ -37,7 +44,6 @@ func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKe
37 37
 				continue
38 38
 			}
39 39
 		}
40
-
41 40
 	}
42 41
 
43 42
 	return filtered, nil
... ...
@@ -27,6 +27,8 @@ func TestFilter(t *testing.T) {
27 27
 			t.Fatal(err)
28 28
 		}
29 29
 
30
+		// we use both []interface{} and []string here because jwt uses
31
+		// []interface{} format, while PEM uses []string
30 32
 		switch {
31 33
 		case i == 0:
32 34
 			// Don't add entries for this key, key 0.
... ...
@@ -36,10 +38,10 @@ func TestFilter(t *testing.T) {
36 36
 			key.AddExtendedField("hosts", []interface{}{"*.even.example.com"})
37 37
 		case i == 7:
38 38
 			// Should catch only the last key, and make it match any hostname.
39
-			key.AddExtendedField("hosts", []interface{}{"*"})
39
+			key.AddExtendedField("hosts", []string{"*"})
40 40
 		default:
41 41
 			// should catch keys 1, 3, 5.
42
-			key.AddExtendedField("hosts", []interface{}{"*.example.com"})
42
+			key.AddExtendedField("hosts", []string{"*.example.com"})
43 43
 		}
44 44
 
45 45
 		keys = append(keys, key)
... ...
@@ -138,7 +138,7 @@ func testTrustedHostKeysFile(t *testing.T, trustedHostKeysFilename string) {
138 138
 	}
139 139
 
140 140
 	for addr, hostKey := range trustedHostKeysMapping {
141
-		t.Logf("Host Address: %s\n", addr)
141
+		t.Logf("Host Address: %d\n", addr)
142 142
 		t.Logf("Host Key: %s\n\n", hostKey)
143 143
 	}
144 144
 
... ...
@@ -160,7 +160,7 @@ func testTrustedHostKeysFile(t *testing.T, trustedHostKeysFilename string) {
160 160
 	}
161 161
 
162 162
 	for addr, hostKey := range trustedHostKeysMapping {
163
-		t.Logf("Host Address: %s\n", addr)
163
+		t.Logf("Host Address: %d\n", addr)
164 164
 		t.Logf("Host Key: %s\n\n", hostKey)
165 165
 	}
166 166
 
... ...
@@ -34,16 +34,7 @@ func (k *rsaPublicKey) KeyType() string {
34 34
 
35 35
 // KeyID returns a distinct identifier which is unique to this Public Key.
36 36
 func (k *rsaPublicKey) KeyID() string {
37
-	// Generate and return a 'libtrust' fingerprint of the RSA public key.
38
-	// For an RSA key this should be:
39
-	//   SHA256("RSA"+bytes(N)+bytes(E))
40
-	// Then truncated to 240 bits and encoded into 12 base32 groups like so:
41
-	//   ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
42
-	hasher := crypto.SHA256.New()
43
-	hasher.Write([]byte(k.KeyType()))
44
-	hasher.Write(k.N.Bytes())
45
-	hasher.Write(serializeRSAPublicExponentParam(k.E))
46
-	return keyIDEncode(hasher.Sum(nil)[:30])
37
+	return keyIDFromCryptoKey(k)
47 38
 }
48 39
 
49 40
 func (k *rsaPublicKey) String() string {
... ...
@@ -201,7 +201,7 @@ func TestCollapseGrants(t *testing.T) {
201 201
 
202 202
 	collapsedGrants, expiration, err := CollapseStatements(statements, false)
203 203
 	if len(collapsedGrants) != 12 {
204
-		t.Fatalf("Unexpected number of grants\n\tExpected: %d\n\tActual: %s", 12, len(collapsedGrants))
204
+		t.Fatalf("Unexpected number of grants\n\tExpected: %d\n\tActual: %d", 12, len(collapsedGrants))
205 205
 	}
206 206
 	if expiration.After(time.Now().Add(time.Hour*5)) || expiration.Before(time.Now()) {
207 207
 		t.Fatalf("Unexpected expiration time: %s", expiration.String())
... ...
@@ -261,7 +261,7 @@ func TestCollapseGrants(t *testing.T) {
261 261
 
262 262
 	collapsedGrants, expiration, err = CollapseStatements(statements, false)
263 263
 	if len(collapsedGrants) != 12 {
264
-		t.Fatalf("Unexpected number of grants\n\tExpected: %d\n\tActual: %s", 12, len(collapsedGrants))
264
+		t.Fatalf("Unexpected number of grants\n\tExpected: %d\n\tActual: %d", 12, len(collapsedGrants))
265 265
 	}
266 266
 	if expiration.After(time.Now().Add(time.Hour*5)) || expiration.Before(time.Now()) {
267 267
 		t.Fatalf("Unexpected expiration time: %s", expiration.String())
... ...
@@ -2,6 +2,7 @@ package libtrust
2 2
 
3 3
 import (
4 4
 	"bytes"
5
+	"crypto"
5 6
 	"crypto/elliptic"
6 7
 	"crypto/x509"
7 8
 	"encoding/base32"
... ...
@@ -52,6 +53,21 @@ func keyIDEncode(b []byte) string {
52 52
 	return buf.String()
53 53
 }
54 54
 
55
+func keyIDFromCryptoKey(pubKey PublicKey) string {
56
+	// Generate and return a 'libtrust' fingerprint of the public key.
57
+	// For an RSA key this should be:
58
+	//   SHA256(DER encoded ASN1)
59
+	// Then truncated to 240 bits and encoded into 12 base32 groups like so:
60
+	//   ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
61
+	derBytes, err := x509.MarshalPKIXPublicKey(pubKey.CryptoPublicKey())
62
+	if err != nil {
63
+		return ""
64
+	}
65
+	hasher := crypto.SHA256.New()
66
+	hasher.Write(derBytes)
67
+	return keyIDEncode(hasher.Sum(nil)[:30])
68
+}
69
+
55 70
 func stringFromMap(m map[string]interface{}, key string) (string, error) {
56 71
 	val, ok := m[key]
57 72
 	if !ok {
58 73
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package libtrust
1
+
2
+import (
3
+	"encoding/pem"
4
+	"reflect"
5
+	"testing"
6
+)
7
+
8
+func TestAddPEMHeadersToKey(t *testing.T) {
9
+	pk := &rsaPublicKey{nil, map[string]interface{}{}}
10
+	blk := &pem.Block{Headers: map[string]string{"hosts": "localhost,127.0.0.1"}}
11
+	addPEMHeadersToKey(blk, pk)
12
+
13
+	val := pk.GetExtendedField("hosts")
14
+	hosts, ok := val.([]string)
15
+	if !ok {
16
+		t.Fatalf("hosts type(%v), expected []string", reflect.TypeOf(val))
17
+	}
18
+	expected := []string{"localhost", "127.0.0.1"}
19
+	if !reflect.DeepEqual(hosts, expected) {
20
+		t.Errorf("hosts(%v), expected %v", hosts, expected)
21
+	}
22
+}