github.com/outbrain/consul@v1.4.5/agent/connect/ca/provider_consul_test.go (about) 1 package ca 2 3 import ( 4 "crypto/x509" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/consul/agent/connect" 10 "github.com/hashicorp/consul/agent/consul/state" 11 "github.com/hashicorp/consul/agent/structs" 12 "github.com/stretchr/testify/require" 13 ) 14 15 type consulCAMockDelegate struct { 16 state *state.Store 17 } 18 19 func (c *consulCAMockDelegate) State() *state.Store { 20 return c.state 21 } 22 23 func (c *consulCAMockDelegate) ApplyCARequest(req *structs.CARequest) error { 24 idx, _, err := c.state.CAConfig() 25 if err != nil { 26 return err 27 } 28 29 switch req.Op { 30 case structs.CAOpSetProviderState: 31 _, err := c.state.CASetProviderState(idx+1, req.ProviderState) 32 if err != nil { 33 return err 34 } 35 36 return nil 37 case structs.CAOpDeleteProviderState: 38 if err := c.state.CADeleteProviderState(req.ProviderState.ID); err != nil { 39 return err 40 } 41 42 return nil 43 default: 44 return fmt.Errorf("Invalid CA operation '%s'", req.Op) 45 } 46 } 47 48 func newMockDelegate(t *testing.T, conf *structs.CAConfiguration) *consulCAMockDelegate { 49 s, err := state.NewStateStore(nil) 50 if err != nil { 51 t.Fatalf("err: %s", err) 52 } 53 if s == nil { 54 t.Fatalf("missing state store") 55 } 56 if err := s.CASetConfig(conf.RaftIndex.CreateIndex, conf); err != nil { 57 t.Fatalf("err: %s", err) 58 } 59 60 return &consulCAMockDelegate{s} 61 } 62 63 func testConsulCAConfig() *structs.CAConfiguration { 64 return &structs.CAConfiguration{ 65 ClusterID: "asdf", 66 Provider: "consul", 67 Config: map[string]interface{}{ 68 // Tests duration parsing after msgpack type mangling during raft apply. 69 "LeafCertTTL": []uint8("72h"), 70 }, 71 } 72 } 73 74 func TestConsulCAProvider_Bootstrap(t *testing.T) { 75 t.Parallel() 76 77 require := require.New(t) 78 conf := testConsulCAConfig() 79 delegate := newMockDelegate(t, conf) 80 81 provider := &ConsulProvider{Delegate: delegate} 82 require.NoError(provider.Configure(conf.ClusterID, true, conf.Config)) 83 require.NoError(provider.GenerateRoot()) 84 85 root, err := provider.ActiveRoot() 86 require.NoError(err) 87 88 // Intermediate should be the same cert. 89 inter, err := provider.ActiveIntermediate() 90 require.NoError(err) 91 require.Equal(root, inter) 92 93 // Should be a valid cert 94 parsed, err := connect.ParseCert(root) 95 require.NoError(err) 96 require.Equal(parsed.URIs[0].String(), fmt.Sprintf("spiffe://%s.consul", conf.ClusterID)) 97 } 98 99 func TestConsulCAProvider_Bootstrap_WithCert(t *testing.T) { 100 t.Parallel() 101 102 // Make sure setting a custom private key/root cert works. 103 require := require.New(t) 104 rootCA := connect.TestCA(t, nil) 105 conf := testConsulCAConfig() 106 conf.Config = map[string]interface{}{ 107 "PrivateKey": rootCA.SigningKey, 108 "RootCert": rootCA.RootCert, 109 } 110 delegate := newMockDelegate(t, conf) 111 112 provider := &ConsulProvider{Delegate: delegate} 113 require.NoError(provider.Configure(conf.ClusterID, true, conf.Config)) 114 require.NoError(provider.GenerateRoot()) 115 116 root, err := provider.ActiveRoot() 117 require.NoError(err) 118 require.Equal(root, rootCA.RootCert) 119 } 120 121 func TestConsulCAProvider_SignLeaf(t *testing.T) { 122 t.Parallel() 123 124 require := require.New(t) 125 conf := testConsulCAConfig() 126 conf.Config["LeafCertTTL"] = "1h" 127 delegate := newMockDelegate(t, conf) 128 129 provider := &ConsulProvider{Delegate: delegate} 130 require.NoError(provider.Configure(conf.ClusterID, true, conf.Config)) 131 require.NoError(provider.GenerateRoot()) 132 133 spiffeService := &connect.SpiffeIDService{ 134 Host: "node1", 135 Namespace: "default", 136 Datacenter: "dc1", 137 Service: "foo", 138 } 139 140 // Generate a leaf cert for the service. 141 { 142 raw, _ := connect.TestCSR(t, spiffeService) 143 144 csr, err := connect.ParseCSR(raw) 145 require.NoError(err) 146 147 cert, err := provider.Sign(csr) 148 require.NoError(err) 149 150 parsed, err := connect.ParseCert(cert) 151 require.NoError(err) 152 require.Equal(parsed.URIs[0], spiffeService.URI()) 153 require.Equal(parsed.Subject.CommonName, "foo") 154 require.Equal(uint64(2), parsed.SerialNumber.Uint64()) 155 156 // Ensure the cert is valid now and expires within the correct limit. 157 now := time.Now() 158 require.True(parsed.NotAfter.Sub(now) < time.Hour) 159 require.True(parsed.NotBefore.Before(now)) 160 } 161 162 // Generate a new cert for another service and make sure 163 // the serial number is incremented. 164 spiffeService.Service = "bar" 165 { 166 raw, _ := connect.TestCSR(t, spiffeService) 167 168 csr, err := connect.ParseCSR(raw) 169 require.NoError(err) 170 171 cert, err := provider.Sign(csr) 172 require.NoError(err) 173 174 parsed, err := connect.ParseCert(cert) 175 require.NoError(err) 176 require.Equal(parsed.URIs[0], spiffeService.URI()) 177 require.Equal(parsed.Subject.CommonName, "bar") 178 require.Equal(parsed.SerialNumber.Uint64(), uint64(2)) 179 180 // Ensure the cert is valid now and expires within the correct limit. 181 require.True(parsed.NotAfter.Sub(time.Now()) < 3*24*time.Hour) 182 require.True(parsed.NotBefore.Before(time.Now())) 183 } 184 } 185 186 func TestConsulCAProvider_CrossSignCA(t *testing.T) { 187 t.Parallel() 188 require := require.New(t) 189 190 conf1 := testConsulCAConfig() 191 delegate1 := newMockDelegate(t, conf1) 192 provider1 := &ConsulProvider{Delegate: delegate1} 193 require.NoError(provider1.Configure(conf1.ClusterID, true, conf1.Config)) 194 require.NoError(provider1.GenerateRoot()) 195 196 conf2 := testConsulCAConfig() 197 conf2.CreateIndex = 10 198 delegate2 := newMockDelegate(t, conf2) 199 provider2 := &ConsulProvider{Delegate: delegate2} 200 require.NoError(provider2.Configure(conf2.ClusterID, true, conf2.Config)) 201 require.NoError(provider2.GenerateRoot()) 202 203 testCrossSignProviders(t, provider1, provider2) 204 } 205 206 func testCrossSignProviders(t *testing.T, provider1, provider2 Provider) { 207 require := require.New(t) 208 209 // Get the root from the new provider to be cross-signed. 210 newRootPEM, err := provider2.ActiveRoot() 211 require.NoError(err) 212 newRoot, err := connect.ParseCert(newRootPEM) 213 require.NoError(err) 214 oldSubject := newRoot.Subject.CommonName 215 216 newInterPEM, err := provider2.ActiveIntermediate() 217 require.NoError(err) 218 newIntermediate, err := connect.ParseCert(newInterPEM) 219 require.NoError(err) 220 221 // Have provider1 cross sign our new root cert. 222 xcPEM, err := provider1.CrossSignCA(newRoot) 223 require.NoError(err) 224 xc, err := connect.ParseCert(xcPEM) 225 require.NoError(err) 226 227 oldRootPEM, err := provider1.ActiveRoot() 228 require.NoError(err) 229 oldRoot, err := connect.ParseCert(oldRootPEM) 230 require.NoError(err) 231 232 // AuthorityKeyID should now be the signing root's, SubjectKeyId should be kept. 233 require.Equal(oldRoot.AuthorityKeyId, xc.AuthorityKeyId) 234 require.Equal(newRoot.SubjectKeyId, xc.SubjectKeyId) 235 236 // Subject name should not have changed. 237 require.Equal(oldSubject, xc.Subject.CommonName) 238 239 // Issuer should be the signing root. 240 require.Equal(oldRoot.Issuer.CommonName, xc.Issuer.CommonName) 241 242 // Get a leaf cert so we can verify against the cross-signed cert. 243 spiffeService := &connect.SpiffeIDService{ 244 Host: "node1", 245 Namespace: "default", 246 Datacenter: "dc1", 247 Service: "foo", 248 } 249 raw, _ := connect.TestCSR(t, spiffeService) 250 251 leafCsr, err := connect.ParseCSR(raw) 252 require.NoError(err) 253 254 leafPEM, err := provider2.Sign(leafCsr) 255 require.NoError(err) 256 257 cert, err := connect.ParseCert(leafPEM) 258 require.NoError(err) 259 260 // Check that the leaf signed by the new cert can be verified by either root 261 // certificate by using the new intermediate + cross-signed cert. 262 intermediatePool := x509.NewCertPool() 263 intermediatePool.AddCert(newIntermediate) 264 intermediatePool.AddCert(xc) 265 266 for _, root := range []*x509.Certificate{oldRoot, newRoot} { 267 rootPool := x509.NewCertPool() 268 rootPool.AddCert(root) 269 270 _, err = cert.Verify(x509.VerifyOptions{ 271 Intermediates: intermediatePool, 272 Roots: rootPool, 273 }) 274 require.NoError(err) 275 } 276 } 277 278 func TestConsulProvider_SignIntermediate(t *testing.T) { 279 t.Parallel() 280 require := require.New(t) 281 282 conf1 := testConsulCAConfig() 283 delegate1 := newMockDelegate(t, conf1) 284 provider1 := &ConsulProvider{Delegate: delegate1} 285 require.NoError(provider1.Configure(conf1.ClusterID, true, conf1.Config)) 286 require.NoError(provider1.GenerateRoot()) 287 288 conf2 := testConsulCAConfig() 289 conf2.CreateIndex = 10 290 delegate2 := newMockDelegate(t, conf2) 291 provider2 := &ConsulProvider{Delegate: delegate2} 292 require.NoError(provider2.Configure(conf2.ClusterID, false, conf2.Config)) 293 294 testSignIntermediateCrossDC(t, provider1, provider2) 295 } 296 297 func testSignIntermediateCrossDC(t *testing.T, provider1, provider2 Provider) { 298 require := require.New(t) 299 300 // Get the intermediate CSR from provider2. 301 csrPEM, err := provider2.GenerateIntermediateCSR() 302 require.NoError(err) 303 csr, err := connect.ParseCSR(csrPEM) 304 require.NoError(err) 305 306 // Sign the CSR with provider1. 307 intermediatePEM, err := provider1.SignIntermediate(csr) 308 require.NoError(err) 309 rootPEM, err := provider1.ActiveRoot() 310 require.NoError(err) 311 312 // Give the new intermediate to provider2 to use. 313 require.NoError(provider2.SetIntermediate(intermediatePEM, rootPEM)) 314 315 // Have provider2 sign a leaf cert and make sure the chain is correct. 316 spiffeService := &connect.SpiffeIDService{ 317 Host: "node1", 318 Namespace: "default", 319 Datacenter: "dc1", 320 Service: "foo", 321 } 322 raw, _ := connect.TestCSR(t, spiffeService) 323 324 leafCsr, err := connect.ParseCSR(raw) 325 require.NoError(err) 326 327 leafPEM, err := provider2.Sign(leafCsr) 328 require.NoError(err) 329 330 cert, err := connect.ParseCert(leafPEM) 331 require.NoError(err) 332 333 // Check that the leaf signed by the new cert can be verified using the 334 // returned cert chain (signed intermediate + remote root). 335 intermediatePool := x509.NewCertPool() 336 intermediatePool.AppendCertsFromPEM([]byte(intermediatePEM)) 337 rootPool := x509.NewCertPool() 338 rootPool.AppendCertsFromPEM([]byte(rootPEM)) 339 340 _, err = cert.Verify(x509.VerifyOptions{ 341 Intermediates: intermediatePool, 342 Roots: rootPool, 343 }) 344 require.NoError(err) 345 } 346 347 func TestConsulCAProvider_MigrateOldID(t *testing.T) { 348 t.Parallel() 349 350 require := require.New(t) 351 conf := testConsulCAConfig() 352 delegate := newMockDelegate(t, conf) 353 354 // Create an entry with an old-style ID. 355 err := delegate.ApplyCARequest(&structs.CARequest{ 356 Op: structs.CAOpSetProviderState, 357 ProviderState: &structs.CAConsulProviderState{ 358 ID: ",", 359 }, 360 }) 361 require.NoError(err) 362 _, providerState, err := delegate.state.CAProviderState(",") 363 require.NoError(err) 364 require.NotNil(providerState) 365 366 provider := &ConsulProvider{Delegate: delegate} 367 require.NoError(provider.Configure(conf.ClusterID, true, conf.Config)) 368 require.NoError(provider.GenerateRoot()) 369 370 // After running Configure, the old ID entry should be gone. 371 _, providerState, err = delegate.state.CAProviderState(",") 372 require.NoError(err) 373 require.Nil(providerState) 374 }