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  }