瀏覽代碼

Merge pull request #32828 from cyli/external-ca-cert

Add the `CACert` parameter to the `ExternalCA` object
Brian Goff 8 年之前
父節點
當前提交
25058d9b0c

+ 3 - 0
api/swagger.yaml

@@ -1837,6 +1837,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"

+ 4 - 0
api/types/swarm/swarm.go

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

+ 11 - 0
cli/command/swarm/opts.go

@@ -2,7 +2,9 @@ package swarm
 
 import (
 	"encoding/csv"
+	"encoding/pem"
 	"fmt"
+	"io/ioutil"
 	"strings"
 	"time"
 
@@ -156,6 +158,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
 		}

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

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

+ 8 - 4
integration-cli/docker_api_swarm_test.go

@@ -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) {

+ 42 - 6
integration-cli/docker_cli_swarm_test.go

@@ -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()