mirror of https://github.com/hashicorp/consul
Configure upstream TLS context with peer root certs (#13321)
For mTLS to work between two proxies in peered clusters with different root CAs, proxies need to configure their outbound listener to use different root certificates for validation. Up until peering was introduced proxies would only ever use one set of root certificates to validate all mesh traffic, both inbound and outbound. Now an upstream proxy may have a leaf certificate signed by a CA that's different from the dialing proxy's. This PR makes changes to proxycfg and xds so that the upstream TLS validation uses different root certificates depending on which cluster is being dialed.pull/13343/head
parent
143dc75e0d
commit
74ca6406ea
@ -0,0 +1,60 @@
|
||||
// Code generated by mockery v2.12.2. DO NOT EDIT.
|
||||
|
||||
package cachetype
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
pbpeering "github.com/hashicorp/consul/proto/pbpeering"
|
||||
|
||||
testing "testing"
|
||||
)
|
||||
|
||||
// MockTrustBundleReader is an autogenerated mock type for the TrustBundleReader type
|
||||
type MockTrustBundleReader struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// TrustBundleRead provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *MockTrustBundleReader) TrustBundleRead(ctx context.Context, in *pbpeering.TrustBundleReadRequest, opts ...grpc.CallOption) (*pbpeering.TrustBundleReadResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *pbpeering.TrustBundleReadResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *pbpeering.TrustBundleReadRequest, ...grpc.CallOption) *pbpeering.TrustBundleReadResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*pbpeering.TrustBundleReadResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *pbpeering.TrustBundleReadRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewMockTrustBundleReader creates a new instance of MockTrustBundleReader. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewMockTrustBundleReader(t testing.TB) *MockTrustBundleReader {
|
||||
mock := &MockTrustBundleReader{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package cachetype
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/consul/agent/cache"
|
||||
"github.com/hashicorp/consul/proto/pbpeering"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Recommended name for registration.
|
||||
const TrustBundleReadName = "peer-trust-bundle"
|
||||
|
||||
// TrustBundle supports fetching discovering service instances via prepared
|
||||
// queries.
|
||||
type TrustBundle struct {
|
||||
RegisterOptionsNoRefresh
|
||||
Client TrustBundleReader
|
||||
}
|
||||
|
||||
//go:generate mockery --name TrustBundleReader --inpackage --testonly
|
||||
type TrustBundleReader interface {
|
||||
TrustBundleRead(
|
||||
ctx context.Context, in *pbpeering.TrustBundleReadRequest, opts ...grpc.CallOption,
|
||||
) (*pbpeering.TrustBundleReadResponse, error)
|
||||
}
|
||||
|
||||
func (t *TrustBundle) Fetch(_ cache.FetchOptions, req cache.Request) (cache.FetchResult, error) {
|
||||
var result cache.FetchResult
|
||||
|
||||
// The request should be a TrustBundleReadRequest.
|
||||
// We do not need to make a copy of this request type like in other cache types
|
||||
// because the RequestInfo is synthetic.
|
||||
reqReal, ok := req.(*pbpeering.TrustBundleReadRequest)
|
||||
if !ok {
|
||||
return result, fmt.Errorf(
|
||||
"Internal cache failure: request wrong type: %T", req)
|
||||
}
|
||||
|
||||
// Fetch
|
||||
reply, err := t.Client.TrustBundleRead(context.Background(), reqReal)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Value = reply
|
||||
result.Index = reply.Index
|
||||
|
||||
return result, nil
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package cachetype
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/consul/agent/cache"
|
||||
"github.com/hashicorp/consul/proto/pbpeering"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTrustBundles(t *testing.T) {
|
||||
client := NewMockTrustBundleReader(t)
|
||||
typ := &TrustBundle{Client: client}
|
||||
|
||||
resp := &pbpeering.TrustBundleReadResponse{
|
||||
Index: 48,
|
||||
Bundle: &pbpeering.PeeringTrustBundle{
|
||||
PeerName: "peer1",
|
||||
RootPEMs: []string{"peer1-roots"},
|
||||
},
|
||||
}
|
||||
|
||||
// Expect the proper call.
|
||||
// This also returns the canned response above.
|
||||
client.On("TrustBundleRead", mock.Anything, mock.Anything).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*pbpeering.TrustBundleReadRequest)
|
||||
require.Equal(t, "foo", req.Name)
|
||||
}).
|
||||
Return(resp, nil)
|
||||
|
||||
// Fetch and assert against the result.
|
||||
result, err := typ.Fetch(cache.FetchOptions{}, &pbpeering.TrustBundleReadRequest{
|
||||
Name: "foo",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cache.FetchResult{
|
||||
Value: resp,
|
||||
Index: 48,
|
||||
}, result)
|
||||
}
|
||||
|
||||
func TestTrustBundles_badReqType(t *testing.T) {
|
||||
client := pbpeering.NewPeeringServiceClient(nil)
|
||||
typ := &TrustBundle{Client: client}
|
||||
|
||||
// Fetch
|
||||
_, err := typ.Fetch(cache.FetchOptions{}, cache.TestRequest(
|
||||
t, cache.RequestInfo{Key: "foo", MinIndex: 64}))
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "wrong type")
|
||||
}
|
||||
|
||||
// This test asserts that we can continuously poll this cache type, given that it doesn't support blocking.
|
||||
func TestTrustBundles_MultipleUpdates(t *testing.T) {
|
||||
c := cache.New(cache.Options{})
|
||||
|
||||
client := NewMockTrustBundleReader(t)
|
||||
|
||||
// On each mock client call to TrustBundleList by service we will increment the index by 1
|
||||
// to simulate new data arriving.
|
||||
resp := &pbpeering.TrustBundleReadResponse{
|
||||
Index: uint64(0),
|
||||
}
|
||||
|
||||
client.On("TrustBundleRead", mock.Anything, mock.Anything).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*pbpeering.TrustBundleReadRequest)
|
||||
require.Equal(t, "foo", req.Name)
|
||||
|
||||
// Increment on each call.
|
||||
resp.Index++
|
||||
}).
|
||||
Return(resp, nil)
|
||||
|
||||
c.RegisterType(TrustBundleReadName, &TrustBundle{Client: client})
|
||||
|
||||
ch := make(chan cache.UpdateEvent)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
err := c.Notify(ctx, TrustBundleReadName, &pbpeering.TrustBundleReadRequest{Name: "foo"}, "updates", ch)
|
||||
require.NoError(t, err)
|
||||
|
||||
i := uint64(1)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case update := <-ch:
|
||||
// Expect to receive updates for increasing indexes serially.
|
||||
resp := update.Result.(*pbpeering.TrustBundleReadResponse)
|
||||
require.Equal(t, i, resp.Index)
|
||||
i++
|
||||
|
||||
if i > 3 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EnsureTrailingNewline adds a newline suffix to the input if not present.
|
||||
// This is typically used to fix a case where the CA provider does not return a new line
|
||||
// after certificates as per the specification. See GH-8178 for more context.
|
||||
func EnsureTrailingNewline(str string) string {
|
||||
if str == "" {
|
||||
return str
|
||||
}
|
||||
if strings.HasSuffix(str, "\n") {
|
||||
return str
|
||||
}
|
||||
return str + "\n"
|
||||
}
|
Loading…
Reference in new issue