mirror of https://github.com/docker/docs.git
Add support for external CAs
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com> (cherry picked from commit 11085b2260a78b3248f3e98e0a1e3203431fae22)
This commit is contained in:
parent
4905c858db
commit
f9f7abfffe
|
|
@ -1,6 +1,8 @@
|
||||||
package swarm
|
package swarm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -23,6 +25,7 @@ const (
|
||||||
flagListenAddr = "listen-addr"
|
flagListenAddr = "listen-addr"
|
||||||
flagSecret = "secret"
|
flagSecret = "secret"
|
||||||
flagTaskHistoryLimit = "task-history-limit"
|
flagTaskHistoryLimit = "task-history-limit"
|
||||||
|
flagExternalCA = "external-ca"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -38,6 +41,7 @@ type swarmOptions struct {
|
||||||
taskHistoryLimit int64
|
taskHistoryLimit int64
|
||||||
dispatcherHeartbeat time.Duration
|
dispatcherHeartbeat time.Duration
|
||||||
nodeCertExpiry time.Duration
|
nodeCertExpiry time.Duration
|
||||||
|
externalCA ExternalCAOption
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeAddrOption is a pflag.Value for listen and remote addresses
|
// NodeAddrOption is a pflag.Value for listen and remote addresses
|
||||||
|
|
@ -142,12 +146,102 @@ func NewAutoAcceptOption() AutoAcceptOption {
|
||||||
return AutoAcceptOption{values: make(map[string]bool)}
|
return AutoAcceptOption{values: make(map[string]bool)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExternalCAOption is a Value type for parsing external CA specifications.
|
||||||
|
type ExternalCAOption struct {
|
||||||
|
values []*swarm.ExternalCA
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set parses an external CA option.
|
||||||
|
func (m *ExternalCAOption) Set(value string) error {
|
||||||
|
parsed, err := parseExternalCA(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.values = append(m.values, parsed)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the type of this option.
|
||||||
|
func (m *ExternalCAOption) Type() string {
|
||||||
|
return "external-ca"
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string repr of this option.
|
||||||
|
func (m *ExternalCAOption) String() string {
|
||||||
|
externalCAs := []string{}
|
||||||
|
for _, externalCA := range m.values {
|
||||||
|
repr := fmt.Sprintf("%s: %s", externalCA.Protocol, externalCA.URL)
|
||||||
|
externalCAs = append(externalCAs, repr)
|
||||||
|
}
|
||||||
|
return strings.Join(externalCAs, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the external CAs
|
||||||
|
func (m *ExternalCAOption) Value() []*swarm.ExternalCA {
|
||||||
|
return m.values
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExternalCA parses an external CA specification from the command line,
|
||||||
|
// such as protocol=cfssl,url=https://example.com.
|
||||||
|
func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) {
|
||||||
|
csvReader := csv.NewReader(strings.NewReader(caSpec))
|
||||||
|
fields, err := csvReader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
externalCA := swarm.ExternalCA{
|
||||||
|
Options: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
hasProtocol bool
|
||||||
|
hasURL bool
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
parts := strings.SplitN(field, "=", 2)
|
||||||
|
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, value := parts[0], parts[1]
|
||||||
|
|
||||||
|
switch strings.ToLower(key) {
|
||||||
|
case "protocol":
|
||||||
|
hasProtocol = true
|
||||||
|
if strings.ToLower(value) == string(swarm.ExternalCAProtocolCFSSL) {
|
||||||
|
externalCA.Protocol = swarm.ExternalCAProtocolCFSSL
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("unrecognized external CA protocol %s", value)
|
||||||
|
}
|
||||||
|
case "url":
|
||||||
|
hasURL = true
|
||||||
|
externalCA.URL = value
|
||||||
|
default:
|
||||||
|
externalCA.Options[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasProtocol {
|
||||||
|
return nil, errors.New("the external-ca option needs a protocol= parameter")
|
||||||
|
}
|
||||||
|
if !hasURL {
|
||||||
|
return nil, errors.New("the external-ca option needs a url= parameter")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &externalCA, nil
|
||||||
|
}
|
||||||
|
|
||||||
func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
|
func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
|
||||||
flags.Var(&opts.autoAccept, flagAutoAccept, "Auto acceptance policy (worker, manager or none)")
|
flags.Var(&opts.autoAccept, flagAutoAccept, "Auto acceptance policy (worker, manager or none)")
|
||||||
flags.StringVar(&opts.secret, flagSecret, "", "Set secret value needed to accept nodes into cluster")
|
flags.StringVar(&opts.secret, flagSecret, "", "Set secret value needed to accept nodes into cluster")
|
||||||
flags.Int64Var(&opts.taskHistoryLimit, flagTaskHistoryLimit, 10, "Task history retention limit")
|
flags.Int64Var(&opts.taskHistoryLimit, flagTaskHistoryLimit, 10, "Task history retention limit")
|
||||||
flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, time.Duration(5*time.Second), "Dispatcher heartbeat period")
|
flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, time.Duration(5*time.Second), "Dispatcher heartbeat period")
|
||||||
flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, time.Duration(90*24*time.Hour), "Validity period for node certificates")
|
flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, time.Duration(90*24*time.Hour), "Validity period for node certificates")
|
||||||
|
flags.Var(&opts.externalCA, flagExternalCA, "Specifications of one or more certificate signing endpoints")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *swarmOptions) ToSpec() swarm.Spec {
|
func (opts *swarmOptions) ToSpec() swarm.Spec {
|
||||||
|
|
@ -160,5 +254,6 @@ func (opts *swarmOptions) ToSpec() swarm.Spec {
|
||||||
spec.Orchestration.TaskHistoryRetentionLimit = opts.taskHistoryLimit
|
spec.Orchestration.TaskHistoryRetentionLimit = opts.taskHistoryLimit
|
||||||
spec.Dispatcher.HeartbeatPeriod = uint64(opts.dispatcherHeartbeat.Nanoseconds())
|
spec.Dispatcher.HeartbeatPeriod = uint64(opts.dispatcherHeartbeat.Nanoseconds())
|
||||||
spec.CAConfig.NodeCertExpiry = opts.nodeCertExpiry
|
spec.CAConfig.NodeCertExpiry = opts.nodeCertExpiry
|
||||||
|
spec.CAConfig.ExternalCAs = opts.externalCA.Value()
|
||||||
return spec
|
return spec
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,5 +85,10 @@ func mergeSwarm(swarm *swarm.Swarm, flags *pflag.FlagSet) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags.Changed(flagExternalCA) {
|
||||||
|
value := flags.Lookup(flagExternalCA).Value.(*ExternalCAOption)
|
||||||
|
spec.CAConfig.ExternalCAs = value.Value()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,14 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
|
||||||
|
|
||||||
swarm.Spec.CAConfig.NodeCertExpiry, _ = ptypes.Duration(c.Spec.CAConfig.NodeCertExpiry)
|
swarm.Spec.CAConfig.NodeCertExpiry, _ = ptypes.Duration(c.Spec.CAConfig.NodeCertExpiry)
|
||||||
|
|
||||||
|
for _, ca := range c.Spec.CAConfig.ExternalCAs {
|
||||||
|
swarm.Spec.CAConfig.ExternalCAs = append(swarm.Spec.CAConfig.ExternalCAs, &types.ExternalCA{
|
||||||
|
Protocol: types.ExternalCAProtocol(strings.ToLower(ca.Protocol.String())),
|
||||||
|
URL: ca.URL,
|
||||||
|
Options: ca.Options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Meta
|
// Meta
|
||||||
swarm.Version.Index = c.Meta.Version.Index
|
swarm.Version.Index = c.Meta.Version.Index
|
||||||
swarm.CreatedAt, _ = ptypes.Timestamp(c.Meta.CreatedAt)
|
swarm.CreatedAt, _ = ptypes.Timestamp(c.Meta.CreatedAt)
|
||||||
|
|
@ -84,6 +92,18 @@ func SwarmSpecToGRPCandMerge(s types.Spec, existingSpec *swarmapi.ClusterSpec) (
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ca := range s.CAConfig.ExternalCAs {
|
||||||
|
protocol, ok := swarmapi.ExternalCA_CAProtocol_value[strings.ToUpper(string(ca.Protocol))]
|
||||||
|
if !ok {
|
||||||
|
return swarmapi.ClusterSpec{}, fmt.Errorf("invalid protocol: %q", ca.Protocol)
|
||||||
|
}
|
||||||
|
spec.CAConfig.ExternalCAs = append(spec.CAConfig.ExternalCAs, &swarmapi.ExternalCA{
|
||||||
|
Protocol: swarmapi.ExternalCA_CAProtocol(protocol),
|
||||||
|
URL: ca.URL,
|
||||||
|
Options: ca.Options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if err := SwarmSpecUpdateAcceptancePolicy(&spec, s.AcceptancePolicy, existingSpec); err != nil {
|
if err := SwarmSpecUpdateAcceptancePolicy(&spec, s.AcceptancePolicy, existingSpec); err != nil {
|
||||||
return swarmapi.ClusterSpec{}, err
|
return swarmapi.ClusterSpec{}, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue