Update trust bundle into proxy-state-template (#18550)

This commit is contained in:
Ashwin Venkatesh 2023-08-22 15:38:31 -04:00 committed by GitHub
parent 17667a1c75
commit 4f9955d91e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 140 additions and 14 deletions

View File

@ -19,9 +19,8 @@ import (
"sync/atomic"
"time"
"github.com/hashicorp/consul/internal/resource"
"github.com/armon/go-metrics"
"github.com/hashicorp/consul-net-rpc/net/rpc"
"github.com/hashicorp/go-connlimit"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-memdb"
@ -38,8 +37,6 @@ import (
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/reflection"
"github.com/hashicorp/consul-net-rpc/net/rpc"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/acl/resolver"
"github.com/hashicorp/consul/agent/blockingquery"
@ -75,6 +72,8 @@ import (
"github.com/hashicorp/consul/agent/token"
"github.com/hashicorp/consul/internal/catalog"
"github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/mesh"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/demo"
"github.com/hashicorp/consul/internal/resource/reaper"
raftstorage "github.com/hashicorp/consul/internal/storage/raft"
@ -82,6 +81,7 @@ import (
"github.com/hashicorp/consul/lib/routine"
"github.com/hashicorp/consul/lib/stringslice"
"github.com/hashicorp/consul/logging"
"github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1/pbproxystate"
"github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/proto/private/pbsubscribe"
"github.com/hashicorp/consul/tlsutil"
@ -876,6 +876,20 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server, incom
func (s *Server) registerControllers(deps Deps) {
if stringslice.Contains(deps.Experiments, catalogResourceExperimentName) {
catalog.RegisterControllers(s.controllerManager, catalog.DefaultControllerDependencies())
mesh.RegisterControllers(s.controllerManager, mesh.ControllerDependencies{
TrustBundleFetcher: func() (*pbproxystate.TrustBundle, error) {
var bundle pbproxystate.TrustBundle
roots, err := s.getCARoots(nil, s.GetState())
if err != nil {
return nil, err
}
bundle.TrustDomain = roots.TrustDomain
for _, root := range roots.Roots {
bundle.Roots = append(bundle.Roots, root.RootCert)
}
return &bundle, nil
},
})
}
reaper.RegisterControllers(s.controllerManager)

View File

@ -4,6 +4,8 @@
package mesh
import (
"github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/mesh/internal/controllers"
"github.com/hashicorp/consul/internal/mesh/internal/types"
"github.com/hashicorp/consul/internal/resource"
)
@ -57,3 +59,11 @@ var (
func RegisterTypes(r resource.Registry) {
types.Register(r)
}
// RegisterControllers registers controllers for the mesh types with
// the given controller Manager.
func RegisterControllers(mgr *controller.Manager, deps ControllerDependencies) {
controllers.Register(mgr, deps)
}
type ControllerDependencies = controllers.Dependencies

View File

@ -0,0 +1,22 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package controllers
import (
"github.com/hashicorp/consul/internal/catalog"
"github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/mesh/internal/controllers/xds"
"github.com/hashicorp/consul/internal/mesh/internal/types"
"github.com/hashicorp/consul/internal/resource/mappers/bimapper"
)
type Dependencies struct {
TrustBundleFetcher xds.TrustBundleFetcher
}
func Register(mgr *controller.Manager, deps Dependencies) {
mapper := bimapper.New(types.ProxyStateTemplateType, catalog.ServiceEndpointsType)
// TODO: Pass in a "real" updater once proxy tracker work has completed.
mgr.Register(xds.Controller(mapper, nil, deps.TrustBundleFetcher))
}

View File

@ -16,22 +16,26 @@ import (
const ControllerName = "consul.io/xds-controller"
func Controller(mapper *bimapper.Mapper, updater ProxyUpdater) controller.Controller {
if mapper == nil || updater == nil {
panic("mapper and updater are required")
func Controller(mapper *bimapper.Mapper, updater ProxyUpdater, fetcher TrustBundleFetcher) controller.Controller {
//if mapper == nil || updater == nil || fetcher == nil {
if mapper == nil || fetcher == nil {
panic("mapper, updater and fetcher are required")
}
return controller.ForType(types.ProxyStateTemplateType).
WithWatch(catalog.ServiceEndpointsType, mapper.MapLink).
WithPlacement(controller.PlacementEachServer).
WithReconciler(&xdsReconciler{bimapper: mapper, updater: updater})
WithReconciler(&xdsReconciler{bimapper: mapper, updater: updater, fetchTrustBundle: fetcher})
}
type xdsReconciler struct {
bimapper *bimapper.Mapper
updater ProxyUpdater
bimapper *bimapper.Mapper
updater ProxyUpdater
fetchTrustBundle TrustBundleFetcher
}
type TrustBundleFetcher func() (*pbproxystate.TrustBundle, error)
// ProxyUpdater is an interface that defines the ability to push proxy updates to the updater
// and also check its connectivity to the server.
type ProxyUpdater interface {
@ -78,6 +82,23 @@ func (r *xdsReconciler) Reconcile(ctx context.Context, rt controller.Runtime, re
return err
}
// TODO: Fetch trust bundles for all peers when peering is supported.
trustBundle, err := r.fetchTrustBundle()
if err != nil {
rt.Logger.Error("error fetching root trust bundle", "error", err)
// Set the status.
statusCondition = status.ConditionRejectedTrustBundleFetchFailed(status.KeyFromID(req.ID))
status.WriteStatusIfChanged(ctx, rt, pstResource, statusCondition)
return err
}
if proxyStateTemplate.Template.ProxyState.TrustBundles == nil {
proxyStateTemplate.Template.ProxyState.TrustBundles = make(map[string]*pbproxystate.TrustBundle)
}
// TODO: Figure out the correct key for the default trust bundle.
proxyStateTemplate.Template.ProxyState.TrustBundles["local"] = trustBundle
if proxyStateTemplate.Template.ProxyState.Endpoints == nil {
proxyStateTemplate.Template.ProxyState.Endpoints = make(map[string]*pbproxystate.Endpoints)
}

View File

@ -37,6 +37,7 @@ type xdsControllerTestSuite struct {
ctl *xdsReconciler
mapper *bimapper.Mapper
updater *mockUpdater
fetcher TrustBundleFetcher
fooProxyStateTemplate *pbresource.Resource
barProxyStateTemplate *pbresource.Resource
@ -48,6 +49,7 @@ type xdsControllerTestSuite struct {
fooBarService *pbresource.Resource
expectedFooProxyStateEndpoints map[string]*pbproxystate.Endpoints
expectedBarProxyStateEndpoints map[string]*pbproxystate.Endpoints
expectedTrustBundle map[string]*pbproxystate.TrustBundle
}
func (suite *xdsControllerTestSuite) SetupTest() {
@ -55,16 +57,27 @@ func (suite *xdsControllerTestSuite) SetupTest() {
resourceClient := svctest.RunResourceService(suite.T(), types.Register, catalog.RegisterTypes)
suite.runtime = controller.Runtime{Client: resourceClient, Logger: testutil.Logger(suite.T())}
suite.client = resourcetest.NewClient(resourceClient)
suite.fetcher = mockFetcher
suite.mapper = bimapper.New(types.ProxyStateTemplateType, catalog.ServiceEndpointsType)
suite.updater = NewMockUpdater()
suite.ctl = &xdsReconciler{
bimapper: suite.mapper,
updater: suite.updater,
bimapper: suite.mapper,
updater: suite.updater,
fetchTrustBundle: suite.fetcher,
}
}
func mockFetcher() (*pbproxystate.TrustBundle, error) {
var bundle pbproxystate.TrustBundle
bundle = pbproxystate.TrustBundle{
TrustDomain: "some-trust-domain",
Roots: []string{"some-root", "some-other-root"},
}
return &bundle, nil
}
// This test ensures when a ProxyState is deleted, it is no longer tracked in the mapper.
func (suite *xdsControllerTestSuite) TestReconcile_NoProxyStateTemplate() {
// Track the id of a non-existent ProxyStateTemplate.
@ -217,6 +230,25 @@ func (suite *xdsControllerTestSuite) TestReconcile_ProxyStateTemplateComputesEnd
prototest.AssertDeepEqual(suite.T(), suite.expectedFooProxyStateEndpoints, actualEndpoints)
}
func (suite *xdsControllerTestSuite) TestReconcile_ProxyStateTemplateSetsTrustBundles() {
// This test is a happy path creation test to make sure pbproxystate.Template.TrustBundles are created in the computed
// pbmesh.ProxyState from the TrustBundleFetcher.
suite.setupFooProxyStateTemplateAndEndpoints()
// Run the reconcile.
err := suite.ctl.Reconcile(context.Background(), suite.runtime, controller.Request{
ID: suite.fooProxyStateTemplate.Id,
})
require.NoError(suite.T(), err)
// Assert on the status.
suite.client.RequireStatusCondition(suite.T(), suite.fooProxyStateTemplate.Id, ControllerName, status.ConditionAccepted())
// Assert that the endpoints computed in the controller matches the expected endpoints.
actualTrustBundle := suite.updater.GetTrustBundle(suite.fooProxyStateTemplate.Id.Name)
prototest.AssertDeepEqual(suite.T(), suite.expectedTrustBundle, actualTrustBundle)
}
// This test is a happy path creation test that calls reconcile multiple times with a more complex setup. This
// scenario is trickier to test in the controller test because the end computation of the xds controller is not
// stored in the state store. So this test ensures that between multiple reconciles the correct ProxyStates are
@ -257,7 +289,7 @@ func (suite *xdsControllerTestSuite) TestReconcile_MultipleProxyStateTemplatesCo
func (suite *xdsControllerTestSuite) TestController_ComputeAddUpdateEndpoints() {
// Run the controller manager.
mgr := controller.NewManager(suite.client, suite.runtime.Logger)
mgr.Register(Controller(suite.mapper, suite.updater))
mgr.Register(Controller(suite.mapper, suite.updater, suite.fetcher))
mgr.SetRaftLeader(true)
go mgr.Run(suite.ctx)
@ -470,12 +502,20 @@ func (suite *xdsControllerTestSuite) setupFooProxyStateTemplateAndEndpoints() {
},
}},
}
expectedTrustBundle := map[string]*pbproxystate.TrustBundle{
"local": {
TrustDomain: "some-trust-domain",
Roots: []string{"some-root", "some-other-root"},
},
}
suite.fooService = fooService
suite.fooEndpoints = fooEndpoints
suite.fooEndpointRefs = fooRequiredEndpoints
suite.fooProxyStateTemplate = fooProxyStateTemplate
suite.expectedFooProxyStateEndpoints = expectedFooProxyStateEndpoints
suite.expectedTrustBundle = expectedTrustBundle
}
// Setup:

View File

@ -79,6 +79,16 @@ func (p *mockUpdater) GetEndpoints(name string) map[string]*pbproxystate.Endpoin
return nil
}
func (p *mockUpdater) GetTrustBundle(name string) map[string]*pbproxystate.TrustBundle {
p.lock.Lock()
defer p.lock.Unlock()
ps, ok := p.latestPs[name]
if ok {
return ps.TrustBundles
}
return nil
}
func (p *mockUpdater) Set(name string, ps *pbmesh.ProxyState) {
p.lock.Lock()
defer p.lock.Unlock()

View File

@ -16,6 +16,7 @@ const (
StatusReasonEndpointNotRead = "ProxyStateEndpointReferenceReadError"
StatusReasonCreatingProxyStateEndpointsFailed = "ProxyStateEndpointsNotComputed"
StatusReasonPushChangeFailed = "ProxyStatePushChangeFailed"
StatusReasonTrustBundleFetchFailed = "ProxyStateTrustBundleFetchFailed"
)
func KeyFromID(id *pbresource.ID) string {
@ -65,6 +66,14 @@ func ConditionRejectedPushChangeFailed(pstRef string) *pbresource.Condition {
Message: fmt.Sprintf("failed to push change for proxy state template %q", pstRef),
}
}
func ConditionRejectedTrustBundleFetchFailed(pstRef string) *pbresource.Condition {
return &pbresource.Condition{
Type: StatusConditionProxyStateAccepted,
State: pbresource.Condition_STATE_FALSE,
Reason: StatusReasonTrustBundleFetchFailed,
Message: fmt.Sprintf("failed to fetch trust bundle for proxy state template %q", pstRef),
}
}
// WriteStatusIfChanged updates the ProxyStateTemplate status if it has changed.
func WriteStatusIfChanged(ctx context.Context, rt controller.Runtime, res *pbresource.Resource, condition *pbresource.Condition) {