github.com/lmb/consul@v1.4.1/connect/tls_test.go (about) 1 package connect 2 3 import ( 4 "crypto/tls" 5 "crypto/x509" 6 "encoding/pem" 7 "testing" 8 9 "github.com/hashicorp/consul/testrpc" 10 11 "github.com/hashicorp/consul/agent" 12 "github.com/hashicorp/consul/agent/connect" 13 "github.com/hashicorp/consul/api" 14 "github.com/stretchr/testify/require" 15 ) 16 17 func Test_verifyServerCertMatchesURI(t *testing.T) { 18 ca1 := connect.TestCA(t, nil) 19 20 tests := []struct { 21 name string 22 certs []*x509.Certificate 23 expected connect.CertURI 24 wantErr bool 25 }{ 26 { 27 name: "simple match", 28 certs: TestPeerCertificates(t, "web", ca1), 29 expected: connect.TestSpiffeIDService(t, "web"), 30 wantErr: false, 31 }, 32 { 33 // Could happen during migration of secondary DC to multi-DC. Trust domain 34 // validity is enforced with x509 name constraints where needed. 35 name: "different trust-domain allowed", 36 certs: TestPeerCertificates(t, "web", ca1), 37 expected: connect.TestSpiffeIDServiceWithHost(t, "web", "other.consul"), 38 wantErr: false, 39 }, 40 { 41 name: "mismatch", 42 certs: TestPeerCertificates(t, "web", ca1), 43 expected: connect.TestSpiffeIDService(t, "db"), 44 wantErr: true, 45 }, 46 { 47 name: "no certs", 48 certs: []*x509.Certificate{}, 49 expected: connect.TestSpiffeIDService(t, "db"), 50 wantErr: true, 51 }, 52 { 53 name: "nil certs", 54 certs: nil, 55 expected: connect.TestSpiffeIDService(t, "db"), 56 wantErr: true, 57 }, 58 } 59 for _, tt := range tests { 60 t.Run(tt.name, func(t *testing.T) { 61 err := verifyServerCertMatchesURI(tt.certs, tt.expected) 62 if tt.wantErr { 63 require.NotNil(t, err) 64 } else { 65 require.Nil(t, err) 66 } 67 }) 68 } 69 } 70 71 func testCertPEMBlock(t *testing.T, pemValue string) []byte { 72 t.Helper() 73 // The _ result below is not an error but the remaining PEM bytes. 74 block, _ := pem.Decode([]byte(pemValue)) 75 require.NotNil(t, block) 76 require.Equal(t, "CERTIFICATE", block.Type) 77 return block.Bytes 78 } 79 80 func TestClientSideVerifier(t *testing.T) { 81 ca1 := connect.TestCA(t, nil) 82 ca2 := connect.TestCA(t, ca1) 83 84 webCA1PEM, _ := connect.TestLeaf(t, "web", ca1) 85 webCA2PEM, _ := connect.TestLeaf(t, "web", ca2) 86 87 webCA1 := testCertPEMBlock(t, webCA1PEM) 88 xcCA2 := testCertPEMBlock(t, ca2.SigningCert) 89 webCA2 := testCertPEMBlock(t, webCA2PEM) 90 91 tests := []struct { 92 name string 93 tlsCfg *tls.Config 94 rawCerts [][]byte 95 wantErr string 96 }{ 97 { 98 name: "ok service ca1", 99 tlsCfg: TestTLSConfig(t, "web", ca1), 100 rawCerts: [][]byte{webCA1}, 101 wantErr: "", 102 }, 103 { 104 name: "untrusted CA", 105 tlsCfg: TestTLSConfig(t, "web", ca2), // only trust ca2 106 rawCerts: [][]byte{webCA1}, // present ca1 107 wantErr: "unknown authority", 108 }, 109 { 110 name: "cross signed intermediate", 111 tlsCfg: TestTLSConfig(t, "web", ca1), // only trust ca1 112 rawCerts: [][]byte{webCA2, xcCA2}, // present ca2 signed cert, and xc 113 wantErr: "", 114 }, 115 { 116 name: "cross signed without intermediate", 117 tlsCfg: TestTLSConfig(t, "web", ca1), // only trust ca1 118 rawCerts: [][]byte{webCA2}, // present ca2 signed cert only 119 wantErr: "unknown authority", 120 }, 121 } 122 for _, tt := range tests { 123 t.Run(tt.name, func(t *testing.T) { 124 require := require.New(t) 125 err := clientSideVerifier(tt.tlsCfg, tt.rawCerts) 126 if tt.wantErr == "" { 127 require.Nil(err) 128 } else { 129 require.NotNil(err) 130 require.Contains(err.Error(), tt.wantErr) 131 } 132 }) 133 } 134 } 135 136 func TestServerSideVerifier(t *testing.T) { 137 ca1 := connect.TestCA(t, nil) 138 ca2 := connect.TestCA(t, ca1) 139 140 webCA1PEM, _ := connect.TestLeaf(t, "web", ca1) 141 webCA2PEM, _ := connect.TestLeaf(t, "web", ca2) 142 143 apiCA1PEM, _ := connect.TestLeaf(t, "api", ca1) 144 apiCA2PEM, _ := connect.TestLeaf(t, "api", ca2) 145 146 webCA1 := testCertPEMBlock(t, webCA1PEM) 147 xcCA2 := testCertPEMBlock(t, ca2.SigningCert) 148 webCA2 := testCertPEMBlock(t, webCA2PEM) 149 150 apiCA1 := testCertPEMBlock(t, apiCA1PEM) 151 apiCA2 := testCertPEMBlock(t, apiCA2PEM) 152 153 // Setup a local test agent to query 154 agent := agent.NewTestAgent("test-consul", "") 155 defer agent.Shutdown() 156 testrpc.WaitForTestAgent(t, agent.RPC, "dc1") 157 158 cfg := api.DefaultConfig() 159 cfg.Address = agent.HTTPAddr() 160 client, err := api.NewClient(cfg) 161 require.NoError(t, err) 162 163 // Setup intentions to validate against. We actually default to allow so first 164 // setup a blanket deny rule for db, then only allow web. 165 connect := client.Connect() 166 ixn := &api.Intention{ 167 SourceNS: "default", 168 SourceName: "*", 169 DestinationNS: "default", 170 DestinationName: "db", 171 Action: api.IntentionActionDeny, 172 SourceType: api.IntentionSourceConsul, 173 Meta: map[string]string{}, 174 } 175 id, _, err := connect.IntentionCreate(ixn, nil) 176 require.NoError(t, err) 177 require.NotEmpty(t, id) 178 179 ixn = &api.Intention{ 180 SourceNS: "default", 181 SourceName: "web", 182 DestinationNS: "default", 183 DestinationName: "db", 184 Action: api.IntentionActionAllow, 185 SourceType: api.IntentionSourceConsul, 186 Meta: map[string]string{}, 187 } 188 id, _, err = connect.IntentionCreate(ixn, nil) 189 require.NoError(t, err) 190 require.NotEmpty(t, id) 191 192 tests := []struct { 193 name string 194 service string 195 tlsCfg *tls.Config 196 rawCerts [][]byte 197 wantErr string 198 }{ 199 { 200 name: "ok service ca1, allow", 201 service: "db", 202 tlsCfg: TestTLSConfig(t, "db", ca1), 203 rawCerts: [][]byte{webCA1}, 204 wantErr: "", 205 }, 206 { 207 name: "untrusted CA", 208 service: "db", 209 tlsCfg: TestTLSConfig(t, "db", ca2), // only trust ca2 210 rawCerts: [][]byte{webCA1}, // present ca1 211 wantErr: "unknown authority", 212 }, 213 { 214 name: "cross signed intermediate, allow", 215 service: "db", 216 tlsCfg: TestTLSConfig(t, "db", ca1), // only trust ca1 217 rawCerts: [][]byte{webCA2, xcCA2}, // present ca2 signed cert, and xc 218 wantErr: "", 219 }, 220 { 221 name: "cross signed without intermediate", 222 service: "db", 223 tlsCfg: TestTLSConfig(t, "db", ca1), // only trust ca1 224 rawCerts: [][]byte{webCA2}, // present ca2 signed cert only 225 wantErr: "unknown authority", 226 }, 227 { 228 name: "ok service ca1, deny", 229 service: "db", 230 tlsCfg: TestTLSConfig(t, "db", ca1), 231 rawCerts: [][]byte{apiCA1}, 232 wantErr: "denied", 233 }, 234 { 235 name: "cross signed intermediate, deny", 236 service: "db", 237 tlsCfg: TestTLSConfig(t, "db", ca1), // only trust ca1 238 rawCerts: [][]byte{apiCA2, xcCA2}, // present ca2 signed cert, and xc 239 wantErr: "denied", 240 }, 241 } 242 for _, tt := range tests { 243 t.Run(tt.name, func(t *testing.T) { 244 v := newServerSideVerifier(client, tt.service) 245 err := v(tt.tlsCfg, tt.rawCerts) 246 if tt.wantErr == "" { 247 require.Nil(t, err) 248 } else { 249 require.NotNil(t, err) 250 require.Contains(t, err.Error(), tt.wantErr) 251 } 252 }) 253 } 254 } 255 256 // requireEqualTLSConfig compares tlsConfig fields we care about. Equal and even 257 // cmp.Diff fail on tls.Config due to unexported fields in each. expectLeaf 258 // allows expecting a leaf cert different from the one in expect 259 func requireEqualTLSConfig(t *testing.T, expect, got *tls.Config) { 260 require := require.New(t) 261 require.Equal(expect.RootCAs, got.RootCAs) 262 require.Equal(expect.ClientCAs, got.ClientCAs) 263 require.Equal(expect.InsecureSkipVerify, got.InsecureSkipVerify) 264 require.Equal(expect.MinVersion, got.MinVersion) 265 require.Equal(expect.CipherSuites, got.CipherSuites) 266 require.NotNil(got.GetCertificate) 267 require.NotNil(got.GetClientCertificate) 268 require.NotNil(got.GetConfigForClient) 269 require.Contains(got.NextProtos, "h2") 270 271 var expectLeaf *tls.Certificate 272 var err error 273 if expect.GetCertificate != nil { 274 expectLeaf, err = expect.GetCertificate(nil) 275 require.Nil(err) 276 } else if len(expect.Certificates) > 0 { 277 expectLeaf = &expect.Certificates[0] 278 } 279 280 gotLeaf, err := got.GetCertificate(nil) 281 require.Nil(err) 282 require.Equal(expectLeaf, gotLeaf) 283 284 gotLeaf, err = got.GetClientCertificate(nil) 285 require.Nil(err) 286 require.Equal(expectLeaf, gotLeaf) 287 } 288 289 // requireCorrectVerifier invokes got.VerifyPeerCertificate and expects the 290 // tls.Config arg to be returned on the provided channel. This ensures the 291 // correct verifier func was attached to got. 292 // 293 // It then ensures that the tls.Config passed to the verifierFunc was actually 294 // the same as the expected current value. 295 func requireCorrectVerifier(t *testing.T, expect, got *tls.Config, 296 ch chan *tls.Config) { 297 298 err := got.VerifyPeerCertificate(nil, nil) 299 require.Nil(t, err) 300 verifierCfg := <-ch 301 // The tls.Cfg passed to verifyFunc should be the expected (current) value. 302 requireEqualTLSConfig(t, expect, verifierCfg) 303 } 304 305 func TestDynamicTLSConfig(t *testing.T) { 306 require := require.New(t) 307 308 ca1 := connect.TestCA(t, nil) 309 ca2 := connect.TestCA(t, nil) 310 baseCfg := TestTLSConfig(t, "web", ca1) 311 newCfg := TestTLSConfig(t, "web", ca2) 312 313 c := newDynamicTLSConfig(baseCfg, nil) 314 315 // Should set them from the base config 316 require.Equal(c.Leaf(), &baseCfg.Certificates[0]) 317 require.Equal(c.Roots(), baseCfg.RootCAs) 318 319 // Create verifiers we can assert are set and run correctly. 320 v1Ch := make(chan *tls.Config, 1) 321 v2Ch := make(chan *tls.Config, 1) 322 v3Ch := make(chan *tls.Config, 1) 323 verify1 := func(cfg *tls.Config, rawCerts [][]byte) error { 324 v1Ch <- cfg 325 return nil 326 } 327 verify2 := func(cfg *tls.Config, rawCerts [][]byte) error { 328 v2Ch <- cfg 329 return nil 330 } 331 verify3 := func(cfg *tls.Config, rawCerts [][]byte) error { 332 v3Ch <- cfg 333 return nil 334 } 335 336 // The dynamic config should be the one we loaded (with some different hooks) 337 gotBefore := c.Get(verify1) 338 requireEqualTLSConfig(t, baseCfg, gotBefore) 339 requireCorrectVerifier(t, baseCfg, gotBefore, v1Ch) 340 341 // Now change the roots as if we just loaded new roots from Consul 342 err := c.SetRoots(newCfg.RootCAs) 343 require.Nil(err) 344 345 // The dynamic config should have the new roots, but old leaf 346 gotAfter := c.Get(verify2) 347 expect := newCfg.Clone() 348 expect.GetCertificate = func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) { 349 return &baseCfg.Certificates[0], nil 350 } 351 requireEqualTLSConfig(t, expect, gotAfter) 352 requireCorrectVerifier(t, expect, gotAfter, v2Ch) 353 354 // The old config fetched before should still call it's own verify func, but 355 // that verifier should be passed the new config (expect). 356 requireCorrectVerifier(t, expect, gotBefore, v1Ch) 357 358 // Now change the leaf 359 err = c.SetLeaf(&newCfg.Certificates[0]) 360 require.Nil(err) 361 362 // The dynamic config should have the new roots, AND new leaf 363 gotAfterLeaf := c.Get(verify3) 364 requireEqualTLSConfig(t, newCfg, gotAfterLeaf) 365 requireCorrectVerifier(t, newCfg, gotAfterLeaf, v3Ch) 366 367 // Both older configs should still call their own verify funcs, but those 368 // verifiers should be passed the new config. 369 requireCorrectVerifier(t, newCfg, gotBefore, v1Ch) 370 requireCorrectVerifier(t, newCfg, gotAfter, v2Ch) 371 } 372 373 func TestDynamicTLSConfig_Ready(t *testing.T) { 374 require := require.New(t) 375 376 ca1 := connect.TestCA(t, nil) 377 baseCfg := TestTLSConfig(t, "web", ca1) 378 379 c := newDynamicTLSConfig(defaultTLSConfig(), nil) 380 readyCh := c.ReadyWait() 381 assertBlocked(t, readyCh) 382 require.False(c.Ready(), "no roots or leaf, should not be ready") 383 384 err := c.SetLeaf(&baseCfg.Certificates[0]) 385 require.NoError(err) 386 assertBlocked(t, readyCh) 387 require.False(c.Ready(), "no roots, should not be ready") 388 389 err = c.SetRoots(baseCfg.RootCAs) 390 require.NoError(err) 391 assertNotBlocked(t, readyCh) 392 require.True(c.Ready(), "should be ready") 393 394 ca2 := connect.TestCA(t, nil) 395 ca2cfg := TestTLSConfig(t, "web", ca2) 396 397 require.NoError(c.SetRoots(ca2cfg.RootCAs)) 398 assertNotBlocked(t, readyCh) 399 require.False(c.Ready(), "invalid leaf, should not be ready") 400 401 require.NoError(c.SetRoots(baseCfg.RootCAs)) 402 assertNotBlocked(t, readyCh) 403 require.True(c.Ready(), "should be ready") 404 } 405 406 func assertBlocked(t *testing.T, ch <-chan struct{}) { 407 t.Helper() 408 select { 409 case <-ch: 410 t.Fatalf("want blocked chan") 411 default: 412 return 413 } 414 } 415 416 func assertNotBlocked(t *testing.T, ch <-chan struct{}) { 417 t.Helper() 418 select { 419 case <-ch: 420 return 421 default: 422 t.Fatalf("want unblocked chan but it blocked") 423 } 424 }