Signed-off-by: Ying Li <ying.li@docker.com>
Ying Li authored on 2017/04/26 07:40:46... | ... |
@@ -1835,6 +1835,9 @@ definitions: |
1835 | 1835 |
type: "object" |
1836 | 1836 |
additionalProperties: |
1837 | 1837 |
type: "string" |
1838 |
+ CACert: |
|
1839 |
+ 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)." |
|
1840 |
+ type: "string" |
|
1838 | 1841 |
EncryptionConfig: |
1839 | 1842 |
description: "Parameters related to encryption-at-rest." |
1840 | 1843 |
type: "object" |
... | ... |
@@ -126,6 +126,10 @@ type ExternalCA struct { |
126 | 126 |
// Options is a set of additional key/value pairs whose interpretation |
127 | 127 |
// depends on the specified CA type. |
128 | 128 |
Options map[string]string `json:",omitempty"` |
129 |
+ |
|
130 |
+ // CACert specifies which root CA is used by this external CA. This certificate must |
|
131 |
+ // be in PEM format. |
|
132 |
+ CACert string |
|
129 | 133 |
} |
130 | 134 |
|
131 | 135 |
// InitRequest is the request used to init a swarm. |
... | ... |
@@ -2,7 +2,9 @@ package swarm |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"encoding/csv" |
5 |
+ "encoding/pem" |
|
5 | 6 |
"fmt" |
7 |
+ "io/ioutil" |
|
6 | 8 |
"strings" |
7 | 9 |
"time" |
8 | 10 |
|
... | ... |
@@ -155,6 +157,15 @@ func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) { |
155 | 155 |
case "url": |
156 | 156 |
hasURL = true |
157 | 157 |
externalCA.URL = value |
158 |
+ case "cacert": |
|
159 |
+ cacontents, err := ioutil.ReadFile(value) |
|
160 |
+ if err != nil { |
|
161 |
+ return nil, errors.Wrap(err, "unable to read CA cert for external CA") |
|
162 |
+ } |
|
163 |
+ if pemBlock, _ := pem.Decode(cacontents); pemBlock == nil { |
|
164 |
+ return nil, errors.New("CA cert for external CA must be in PEM format") |
|
165 |
+ } |
|
166 |
+ externalCA.CACert = string(cacontents) |
|
158 | 167 |
default: |
159 | 168 |
externalCA.Options[key] = value |
160 | 169 |
} |
... | ... |
@@ -47,6 +47,7 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm { |
47 | 47 |
Protocol: types.ExternalCAProtocol(strings.ToLower(ca.Protocol.String())), |
48 | 48 |
URL: ca.URL, |
49 | 49 |
Options: ca.Options, |
50 |
+ CACert: string(ca.CACert), |
|
50 | 51 |
}) |
51 | 52 |
} |
52 | 53 |
|
... | ... |
@@ -112,6 +113,7 @@ func MergeSwarmSpecToGRPC(s types.Spec, spec swarmapi.ClusterSpec) (swarmapi.Clu |
112 | 112 |
Protocol: swarmapi.ExternalCA_CAProtocol(protocol), |
113 | 113 |
URL: ca.URL, |
114 | 114 |
Options: ca.Options, |
115 |
+ CACert: []byte(ca.CACert), |
|
115 | 116 |
}) |
116 | 117 |
} |
117 | 118 |
|
... | ... |
@@ -146,9 +146,6 @@ func (s *DockerSwarmSuite) TestAPISwarmJoinToken(c *check.C) { |
146 | 146 |
} |
147 | 147 |
|
148 | 148 |
func (s *DockerSwarmSuite) TestUpdateSwarmAddExternalCA(c *check.C) { |
149 |
- // TODO: when root rotation is in, convert to a series of root rotation tests instead. |
|
150 |
- // currently just makes sure that we don't have to provide a CA certificate when |
|
151 |
- // providing an external CA |
|
152 | 149 |
d1 := s.AddDaemon(c, false, false) |
153 | 150 |
c.Assert(d1.Init(swarm.InitRequest{}), checker.IsNil) |
154 | 151 |
d1.UpdateSwarm(c, func(s *swarm.Spec) { |
... | ... |
@@ -157,11 +154,18 @@ func (s *DockerSwarmSuite) TestUpdateSwarmAddExternalCA(c *check.C) { |
157 | 157 |
Protocol: swarm.ExternalCAProtocolCFSSL, |
158 | 158 |
URL: "https://thishasnoca.org", |
159 | 159 |
}, |
160 |
+ { |
|
161 |
+ Protocol: swarm.ExternalCAProtocolCFSSL, |
|
162 |
+ URL: "https://thishasacacert.org", |
|
163 |
+ CACert: "cacert", |
|
164 |
+ }, |
|
160 | 165 |
} |
161 | 166 |
}) |
162 | 167 |
info, err := d1.SwarmInfo() |
163 | 168 |
c.Assert(err, checker.IsNil) |
164 |
- c.Assert(info.Cluster.Spec.CAConfig.ExternalCAs, checker.HasLen, 1) |
|
169 |
+ c.Assert(info.Cluster.Spec.CAConfig.ExternalCAs, checker.HasLen, 2) |
|
170 |
+ c.Assert(info.Cluster.Spec.CAConfig.ExternalCAs[0].CACert, checker.Equals, "") |
|
171 |
+ c.Assert(info.Cluster.Spec.CAConfig.ExternalCAs[1].CACert, checker.Equals, "cacert") |
|
165 | 172 |
} |
166 | 173 |
|
167 | 174 |
func (s *DockerSwarmSuite) TestAPISwarmCAHash(c *check.C) { |
... | ... |
@@ -23,6 +23,7 @@ import ( |
23 | 23 |
"github.com/docker/docker/integration-cli/daemon" |
24 | 24 |
"github.com/docker/docker/pkg/testutil" |
25 | 25 |
icmd "github.com/docker/docker/pkg/testutil/cmd" |
26 |
+ "github.com/docker/docker/pkg/testutil/tempfile" |
|
26 | 27 |
"github.com/docker/libnetwork/driverapi" |
27 | 28 |
"github.com/docker/libnetwork/ipamapi" |
28 | 29 |
remoteipam "github.com/docker/libnetwork/ipams/remote/api" |
... | ... |
@@ -53,11 +54,29 @@ func (s *DockerSwarmSuite) TestSwarmUpdate(c *check.C) { |
53 | 53 |
c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour) |
54 | 54 |
|
55 | 55 |
// passing an external CA (this is without starting a root rotation) does not fail |
56 |
- out, err = d.Cmd("swarm", "update", "--external-ca", "protocol=cfssl,url=https://something.org") |
|
57 |
- c.Assert(err, checker.IsNil, check.Commentf("out: %v", out)) |
|
56 |
+ cli.Docker(cli.Args("swarm", "update", "--external-ca", "protocol=cfssl,url=https://something.org", |
|
57 |
+ "--external-ca", "protocol=cfssl,url=https://somethingelse.org,cacert=fixtures/https/ca.pem"), |
|
58 |
+ cli.Daemon(d.Daemon)).Assert(c, icmd.Success) |
|
59 |
+ |
|
60 |
+ expected, err := ioutil.ReadFile("fixtures/https/ca.pem") |
|
61 |
+ c.Assert(err, checker.IsNil) |
|
58 | 62 |
|
59 | 63 |
spec = getSpec() |
60 |
- c.Assert(spec.CAConfig.ExternalCAs, checker.HasLen, 1) |
|
64 |
+ c.Assert(spec.CAConfig.ExternalCAs, checker.HasLen, 2) |
|
65 |
+ c.Assert(spec.CAConfig.ExternalCAs[0].CACert, checker.Equals, "") |
|
66 |
+ c.Assert(spec.CAConfig.ExternalCAs[1].CACert, checker.Equals, string(expected)) |
|
67 |
+ |
|
68 |
+ // passing an invalid external CA fails |
|
69 |
+ tempFile := tempfile.NewTempFile(c, "testfile", "fakecert") |
|
70 |
+ defer tempFile.Remove() |
|
71 |
+ |
|
72 |
+ result := cli.Docker(cli.Args("swarm", "update", |
|
73 |
+ "--external-ca", fmt.Sprintf("protocol=cfssl,url=https://something.org,cacert=%s", tempFile.Name())), |
|
74 |
+ cli.Daemon(d.Daemon)) |
|
75 |
+ result.Assert(c, icmd.Expected{ |
|
76 |
+ ExitCode: 125, |
|
77 |
+ Err: "must be in PEM format", |
|
78 |
+ }) |
|
61 | 79 |
} |
62 | 80 |
|
63 | 81 |
func (s *DockerSwarmSuite) TestSwarmInit(c *check.C) { |
... | ... |
@@ -68,17 +87,34 @@ func (s *DockerSwarmSuite) TestSwarmInit(c *check.C) { |
68 | 68 |
return sw.Spec |
69 | 69 |
} |
70 | 70 |
|
71 |
+ // passing an invalid external CA fails |
|
72 |
+ tempFile := tempfile.NewTempFile(c, "testfile", "fakecert") |
|
73 |
+ defer tempFile.Remove() |
|
74 |
+ |
|
75 |
+ result := cli.Docker(cli.Args("swarm", "init", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s", |
|
76 |
+ "--external-ca", fmt.Sprintf("protocol=cfssl,url=https://somethingelse.org,cacert=%s", tempFile.Name())), |
|
77 |
+ cli.Daemon(d.Daemon)) |
|
78 |
+ result.Assert(c, icmd.Expected{ |
|
79 |
+ ExitCode: 125, |
|
80 |
+ Err: "must be in PEM format", |
|
81 |
+ }) |
|
82 |
+ |
|
71 | 83 |
cli.Docker(cli.Args("swarm", "init", "--cert-expiry", "30h", "--dispatcher-heartbeat", "11s", |
72 |
- "--external-ca", "protocol=cfssl,url=https://something.org"), |
|
84 |
+ "--external-ca", "protocol=cfssl,url=https://something.org", |
|
85 |
+ "--external-ca", "protocol=cfssl,url=https://somethingelse.org,cacert=fixtures/https/ca.pem"), |
|
73 | 86 |
cli.Daemon(d.Daemon)).Assert(c, icmd.Success) |
74 | 87 |
|
88 |
+ expected, err := ioutil.ReadFile("fixtures/https/ca.pem") |
|
89 |
+ c.Assert(err, checker.IsNil) |
|
90 |
+ |
|
75 | 91 |
spec := getSpec() |
76 | 92 |
c.Assert(spec.CAConfig.NodeCertExpiry, checker.Equals, 30*time.Hour) |
77 | 93 |
c.Assert(spec.Dispatcher.HeartbeatPeriod, checker.Equals, 11*time.Second) |
78 |
- c.Assert(spec.CAConfig.ExternalCAs, checker.HasLen, 1) |
|
94 |
+ c.Assert(spec.CAConfig.ExternalCAs, checker.HasLen, 2) |
|
95 |
+ c.Assert(spec.CAConfig.ExternalCAs[0].CACert, checker.Equals, "") |
|
96 |
+ c.Assert(spec.CAConfig.ExternalCAs[1].CACert, checker.Equals, string(expected)) |
|
79 | 97 |
|
80 | 98 |
c.Assert(d.Leave(true), checker.IsNil) |
81 |
- time.Sleep(500 * time.Millisecond) // https://github.com/docker/swarmkit/issues/1421 |
|
82 | 99 |
cli.Docker(cli.Args("swarm", "init"), cli.Daemon(d.Daemon)).Assert(c, icmd.Success) |
83 | 100 |
|
84 | 101 |
spec = getSpec() |