| ... | ... |
@@ -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 |
+} |
| ... | ... |
@@ -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 |
} |
| ... | ... |
@@ -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 |
+} |