Explorar el Código

Add support for external CAs

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Tonis Tiigi hace 9 años
padre
commit
11085b2260
Se han modificado 3 ficheros con 120 adiciones y 0 borrados
  1. 95 0
      api/client/swarm/opts.go
  2. 5 0
      api/client/swarm/update.go
  3. 20 0
      daemon/cluster/convert/swarm.go

+ 95 - 0
api/client/swarm/opts.go

@@ -1,6 +1,8 @@
 package swarm
 
 import (
+	"encoding/csv"
+	"errors"
 	"fmt"
 	"strings"
 	"time"
@@ -23,6 +25,7 @@ const (
 	flagListenAddr          = "listen-addr"
 	flagSecret              = "secret"
 	flagTaskHistoryLimit    = "task-history-limit"
+	flagExternalCA          = "external-ca"
 )
 
 var (
@@ -38,6 +41,7 @@ type swarmOptions struct {
 	taskHistoryLimit    int64
 	dispatcherHeartbeat time.Duration
 	nodeCertExpiry      time.Duration
+	externalCA          ExternalCAOption
 }
 
 // NodeAddrOption is a pflag.Value for listen and remote addresses
@@ -142,12 +146,102 @@ func NewAutoAcceptOption() AutoAcceptOption {
 	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) {
 	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.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.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 {
@@ -160,5 +254,6 @@ func (opts *swarmOptions) ToSpec() swarm.Spec {
 	spec.Orchestration.TaskHistoryRetentionLimit = opts.taskHistoryLimit
 	spec.Dispatcher.HeartbeatPeriod = uint64(opts.dispatcherHeartbeat.Nanoseconds())
 	spec.CAConfig.NodeCertExpiry = opts.nodeCertExpiry
+	spec.CAConfig.ExternalCAs = opts.externalCA.Value()
 	return spec
 }

+ 5 - 0
api/client/swarm/update.go

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

+ 20 - 0
daemon/cluster/convert/swarm.go

@@ -35,6 +35,14 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
 
 	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
 	swarm.Version.Index = c.Meta.Version.Index
 	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 {
 		return swarmapi.ClusterSpec{}, err
 	}