Add support for external CAs
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
parent
9b65273836
commit
11085b2260
3 changed files with 120 additions and 0 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue