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