Add the CACert parameter to the ExternalCA object in order to match

swarmkit's API type.  Make sure this parameter gets propagated to
swarmkit, and also add an extra option to the CLI when providing
external CAs to parse the CA cert from a file.

Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
Ying Li 2017-04-25 15:40:46 -07:00
parent e1101b1295
commit b0401a71f7
6 changed files with 70 additions and 10 deletions

View File

@ -1835,6 +1835,9 @@ definitions:
type: "object"
additionalProperties:
type: "string"
CACert:
description: "The root CA certificate (in PEM format) this external CA uses to issue TLS certificates (assumed to be to the current swarm root CA certificate if not provided)."
type: "string"
EncryptionConfig:
description: "Parameters related to encryption-at-rest."
type: "object"

View File

@ -126,6 +126,10 @@ type ExternalCA struct {
// Options is a set of additional key/value pairs whose interpretation
// depends on the specified CA type.
Options map[string]string `json:",omitempty"`
// CACert specifies which root CA is used by this external CA. This certificate must
// be in PEM format.
CACert string
}
// InitRequest is the request used to init a swarm.

View File

@ -2,7 +2,9 @@ package swarm
import (
"encoding/csv"
"encoding/pem"
"fmt"
"io/ioutil"
"strings"
"time"
@ -155,6 +157,15 @@ func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) {
case "url":
hasURL = true
externalCA.URL = value
case "cacert":
cacontents, err := ioutil.ReadFile(value)
if err != nil {
return nil, errors.Wrap(err, "unable to read CA cert for external CA")
}
if pemBlock, _ := pem.Decode(cacontents); pemBlock == nil {
return nil, errors.New("CA cert for external CA must be in PEM format")
}
externalCA.CACert = string(cacontents)
default:
externalCA.Options[key] = value
}

View File

@ -47,6 +47,7 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
Protocol: types.ExternalCAProtocol(strings.ToLower(ca.Protocol.String())),
URL: ca.URL,
Options: ca.Options,
CACert: string(ca.CACert),
})
}
@ -112,6 +113,7 @@ func MergeSwarmSpecToGRPC(s types.Spec, spec swarmapi.ClusterSpec) (swarmapi.Clu
Protocol: swarmapi.ExternalCA_CAProtocol(protocol),
URL: ca.URL,
Options: ca.Options,
CACert: []byte(ca.CACert),
})
}

View File

@ -146,9 +146,6 @@ func (s *DockerSwarmSuite) TestAPISwarmJoinToken(c *check.C) {
}
func (s *DockerSwarmSuite) TestUpdateSwarmAddExternalCA(c *check.C) {
// TODO: when root rotation is in, convert to a series of root rotation tests instead.
// currently just makes sure that we don't have to provide a CA certificate when
// providing an external CA
d1 := s.AddDaemon(c, false, false)
c.Assert(d1.Init(swarm.InitRequest{}), checker.IsNil)
d1.UpdateSwarm(c, func(s *swarm.Spec) {
@ -157,11 +154,18 @@ func (s *DockerSwarmSuite) TestUpdateSwarmAddExternalCA(c *check.C) {
Protocol: swarm.ExternalCAProtocolCFSSL,
URL: "https://thishasnoca.org",
},
{
Protocol: swarm.ExternalCAProtocolCFSSL,
URL: "https://thishasacacert.org",
CACert: "cacert",
},
}
})
info, err := d1.SwarmInfo()
c.Assert(err, checker.IsNil)
c.Assert(info.Cluster.Spec.CAConfig.ExternalCAs, checker.HasLen, 1)
c.Assert(info.Cluster.Spec.CAConfig.ExternalCAs, checker.HasLen, 2)
c.Assert(info.Cluster.Spec.CAConfig.ExternalCAs[0].CACert, checker.Equals, "")
c.Assert(info.Cluster.Spec.CAConfig.ExternalCAs[1].CACert, checker.Equals, "cacert")
}
func (s *DockerSwarmSuite) TestAPISwarmCAHash(c *check.C) {

View File

@ -23,6 +23,7 @@ import (
"github.com/docker/docker/integration-cli/daemon"
"github.com/docker/docker/pkg/testutil"
icmd "github.com/docker/docker/pkg/testutil/cmd"
"github.com/docker/docker/pkg/testutil/tempfile"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/ipamapi"
remoteipam "github.com/docker/libnetwork/ipams/remote/api"
@ -53,11 +54,29 @@ func (s *DockerSwarmSuite) TestSwarmUpdate(c *check.C) {
c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour)
// passing an external CA (this is without starting a root rotation) does not fail
out, err = d.Cmd("swarm", "update", "--external-ca", "protocol=cfssl,url=https://something.org")
c.Assert(err, checker.IsNil, check.Commentf("out: %v", out))
cli.Docker(cli.Args("swarm", "update", "--external-ca", "protocol=cfssl,url=https://something.org",
"--external-ca", "protocol=cfssl,url=https://somethingelse.org,cacert=fixtures/https/ca.pem"),
cli.Daemon(d.Daemon)).Assert(c, icmd.Success)
expected, err := ioutil.ReadFile("fixtures/https/ca.pem")
c.Assert(err, checker.IsNil)
spec = getSpec()
c.Assert(spec.CAConfig.ExternalCAs, checker.HasLen, 1)
c.Assert(spec.CAConfig.ExternalCAs, checker.HasLen, 2)
c.Assert(spec.CAConfig.ExternalCAs[0].CACert, checker.Equals, "")
c.Assert(spec.CAConfig.ExternalCAs[1].CACert, checker.Equals, string(expected))
// passing an invalid external CA fails
tempFile := tempfile.NewTempFile(c, "testfile", "fakecert")
defer tempFile.Remove()
result := cli.Docker(cli.Args("swarm", "update",
"--external-ca", fmt.Sprintf("protocol=cfssl,url=https://something.org,cacert=%s", tempFile.Name())),
cli.Daemon(d.Daemon))
result.Assert(c, icmd.Expected{
ExitCode: 125,
Err: "must be in PEM format",
})
}
func (s *DockerSwarmSuite) TestSwarmInit(c *check.C) {
@ -68,17 +87,34 @@ func (s *DockerSwarmSuite) TestSwarmInit(c *check.C) {
return sw.Spec
}
// passing an invalid external CA fails
tempFile := tempfile.NewTempFile(c, "testfile", "fakecert")
defer tempFile.Remove()
result := cli.Docker(cli.Args("swarm", "init", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s",
"--external-ca", fmt.Sprintf("protocol=cfssl,url=https://somethingelse.org,cacert=%s", tempFile.Name())),
cli.Daemon(d.Daemon))
result.Assert(c, icmd.Expected{
ExitCode: 125,
Err: "must be in PEM format",
})
cli.Docker(cli.Args("swarm", "init", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s",
"--external-ca", "protocol=cfssl,url=https://something.org"),
"--external-ca", "protocol=cfssl,url=https://something.org",
"--external-ca", "protocol=cfssl,url=https://somethingelse.org,cacert=fixtures/https/ca.pem"),
cli.Daemon(d.Daemon)).Assert(c, icmd.Success)
expected, err := ioutil.ReadFile("fixtures/https/ca.pem")
c.Assert(err, checker.IsNil)
spec := getSpec()
c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour)
c.Assert(spec.Dispatcher.HeartbeatPeriod, checker.Equals, 11*time.Second)
c.Assert(spec.CAConfig.ExternalCAs, checker.HasLen, 1)
c.Assert(spec.CAConfig.ExternalCAs, checker.HasLen, 2)
c.Assert(spec.CAConfig.ExternalCAs[0].CACert, checker.Equals, "")
c.Assert(spec.CAConfig.ExternalCAs[1].CACert, checker.Equals, string(expected))
c.Assert(d.Leave(true), checker.IsNil)
time.Sleep(500 * time.Millisecond) // https://github.com/docker/swarmkit/issues/1421
cli.Docker(cli.Args("swarm", "init"), cli.Daemon(d.Daemon)).Assert(c, icmd.Success)
spec = getSpec()