9836162446
The libtrust keyfile which is used to set the "ID" property of a daemon must be generated or loaded on every startup. If the process crashes during startup this could cause the file to be incomplete causing future startup errors. Ensure that the file is written atomically to ensure the file is never in an incomplete state. Fixes #23985 Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
169 lines
4.6 KiB
Go
169 lines
4.6 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"mime"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/pkg/ioutils"
|
|
"github.com/docker/docker/pkg/system"
|
|
"github.com/docker/engine-api/types"
|
|
"github.com/docker/libtrust"
|
|
)
|
|
|
|
// Common constants for daemon and client.
|
|
const (
|
|
// Version of Current REST API
|
|
DefaultVersion string = "1.25"
|
|
|
|
// MinVersion represents Minimum REST API version supported
|
|
MinVersion string = "1.12"
|
|
|
|
// NoBaseImageSpecifier is the symbol used by the FROM
|
|
// command to specify that no base image is to be used.
|
|
NoBaseImageSpecifier string = "scratch"
|
|
)
|
|
|
|
// byPortInfo is a temporary type used to sort types.Port by its fields
|
|
type byPortInfo []types.Port
|
|
|
|
func (r byPortInfo) Len() int { return len(r) }
|
|
func (r byPortInfo) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
|
func (r byPortInfo) Less(i, j int) bool {
|
|
if r[i].PrivatePort != r[j].PrivatePort {
|
|
return r[i].PrivatePort < r[j].PrivatePort
|
|
}
|
|
|
|
if r[i].IP != r[j].IP {
|
|
return r[i].IP < r[j].IP
|
|
}
|
|
|
|
if r[i].PublicPort != r[j].PublicPort {
|
|
return r[i].PublicPort < r[j].PublicPort
|
|
}
|
|
|
|
return r[i].Type < r[j].Type
|
|
}
|
|
|
|
// DisplayablePorts returns formatted string representing open ports of container
|
|
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
|
// it's used by command 'docker ps'
|
|
func DisplayablePorts(ports []types.Port) string {
|
|
type portGroup struct {
|
|
first int
|
|
last int
|
|
}
|
|
groupMap := make(map[string]*portGroup)
|
|
var result []string
|
|
var hostMappings []string
|
|
var groupMapKeys []string
|
|
sort.Sort(byPortInfo(ports))
|
|
for _, port := range ports {
|
|
current := port.PrivatePort
|
|
portKey := port.Type
|
|
if port.IP != "" {
|
|
if port.PublicPort != current {
|
|
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
|
continue
|
|
}
|
|
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
|
|
}
|
|
group := groupMap[portKey]
|
|
|
|
if group == nil {
|
|
groupMap[portKey] = &portGroup{first: current, last: current}
|
|
// record order that groupMap keys are created
|
|
groupMapKeys = append(groupMapKeys, portKey)
|
|
continue
|
|
}
|
|
if current == (group.last + 1) {
|
|
group.last = current
|
|
continue
|
|
}
|
|
|
|
result = append(result, formGroup(portKey, group.first, group.last))
|
|
groupMap[portKey] = &portGroup{first: current, last: current}
|
|
}
|
|
for _, portKey := range groupMapKeys {
|
|
g := groupMap[portKey]
|
|
result = append(result, formGroup(portKey, g.first, g.last))
|
|
}
|
|
result = append(result, hostMappings...)
|
|
return strings.Join(result, ", ")
|
|
}
|
|
|
|
func formGroup(key string, start, last int) string {
|
|
parts := strings.Split(key, "/")
|
|
groupType := parts[0]
|
|
var ip string
|
|
if len(parts) > 1 {
|
|
ip = parts[0]
|
|
groupType = parts[1]
|
|
}
|
|
group := strconv.Itoa(start)
|
|
if start != last {
|
|
group = fmt.Sprintf("%s-%d", group, last)
|
|
}
|
|
if ip != "" {
|
|
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
|
|
}
|
|
return fmt.Sprintf("%s/%s", group, groupType)
|
|
}
|
|
|
|
// MatchesContentType validates the content type against the expected one
|
|
func MatchesContentType(contentType, expectedType string) bool {
|
|
mimetype, _, err := mime.ParseMediaType(contentType)
|
|
if err != nil {
|
|
logrus.Errorf("Error parsing media type: %s error: %v", contentType, err)
|
|
}
|
|
return err == nil && mimetype == expectedType
|
|
}
|
|
|
|
// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
|
|
// otherwise generates a new one
|
|
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
|
|
err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700)
|
|
if 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)
|
|
}
|
|
encodedKey, err := serializePrivateKey(trustKey, filepath.Ext(trustKeyPath))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error serializing key: %s", err)
|
|
}
|
|
if err := ioutils.AtomicWriteFile(trustKeyPath, encodedKey, os.FileMode(0600)); err != nil {
|
|
return nil, fmt.Errorf("Error saving key file: %s", err)
|
|
}
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("Error loading key file %s: %s", trustKeyPath, err)
|
|
}
|
|
return trustKey, nil
|
|
}
|
|
|
|
func serializePrivateKey(key libtrust.PrivateKey, ext string) (encoded []byte, err error) {
|
|
if ext == ".json" || ext == ".jwk" {
|
|
encoded, err = json.Marshal(key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to encode private key JWK: %s", err)
|
|
}
|
|
} else {
|
|
pemBlock, err := key.PEMBlock()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to encode private key PEM: %s", err)
|
|
}
|
|
encoded = pem.EncodeToMemory(pemBlock)
|
|
}
|
|
return
|
|
}
|