Skip to content

Commit

Permalink
sidecar-controller: Support explicit destinations for L4 protocols an…
Browse files Browse the repository at this point in the history
…d single ports.

* This controller generates and saves ProxyStateTemplate for sidecar proxies.
* It currently supports single-port L4 ports only.
* It keeps a cache of all destinations to make it easier to compute and retrieve destinations.
* It will update the status of the pbmesh.Upstreams resource if anything is invalid.
  • Loading branch information
ishustava committed Aug 2, 2023
1 parent 42be993 commit 75cb013
Show file tree
Hide file tree
Showing 36 changed files with 2,909 additions and 612 deletions.
30 changes: 30 additions & 0 deletions agent/connect/uri_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/url"

"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/proto-public/pbresource"
)

// SpiffeIDService is the structure to represent the SPIFFE ID for a service.
Expand Down Expand Up @@ -52,3 +53,32 @@ func (id SpiffeIDService) uriPath() string {
}
return path
}

// SpiffeIDService is the structure to represent the SPIFFE ID for a service.
type SpiffeIDIdentity struct {
Host string
Partition string
Namespace string
Identity string
}

func (id SpiffeIDIdentity) URI() *url.URL {
var result url.URL
result.Scheme = "spiffe"
result.Host = id.Host
result.Path = fmt.Sprintf("/ap/%s/ns/%s/identity/%s",
id.Partition,
id.Namespace,
id.Identity,
)
return &result
}

func SpiffeIDFromIdentityRef(trustDomain string, ref *pbresource.Reference) string {
return SpiffeIDIdentity{
Host: trustDomain,
Partition: ref.Tenancy.Partition,
Namespace: ref.Tenancy.Namespace,
Identity: ref.Name,
}.URI().String()
}
20 changes: 19 additions & 1 deletion agent/consul/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"sync/atomic"
"time"

"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/internal/mesh"
"github.com/hashicorp/consul/internal/resource"

Expand Down Expand Up @@ -876,7 +877,24 @@ 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.RegisterControllers(s.controllerManager, mesh.ControllerDependencies{
TrustDomainFetcher: func() (string, error) {
if s.config.CAConfig == nil || s.config.CAConfig.ClusterID == "" {
return "", fmt.Errorf("CA has not finished initializing")
}

// Build TrustDomain based on the ClusterID stored.
signingID := connect.SpiffeIDSigningForCluster(s.config.CAConfig.ClusterID)
if signingID == nil {
// If CA is bootstrapped at all then this should never happen but be
// defensive.
return "", fmt.Errorf("no cluster trust domain setup")
}

return signingID.Host(), nil
},
})
connect.SpiffeIDSigningForCluster(s.config.CAConfig.ClusterID)
}

reaper.RegisterControllers(s.controllerManager)
Expand Down
16 changes: 14 additions & 2 deletions internal/mesh/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package mesh
import (
"github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/mesh/internal/controllers"
"github.com/hashicorp/consul/internal/mesh/internal/controllers/mesh"
"github.com/hashicorp/consul/internal/mesh/internal/types"
"github.com/hashicorp/consul/internal/resource"
)
Expand All @@ -29,6 +30,13 @@ var (
UpstreamsV1Alpha1Type = types.UpstreamsV1Alpha1Type
UpstreamsConfigurationV1Alpha1Type = types.UpstreamsConfigurationV1Alpha1Type
ProxyStateTemplateV1Alpha1Type = types.ProxyStateTemplateV1Alpha1Type

// Resource Types for the latest version.

ProxyConfigurationType = types.ProxyConfigurationV1Alpha1Type
UpstreamsType = types.UpstreamsV1Alpha1Type
UpstreamsConfigurationType = types.UpstreamsConfigurationV1Alpha1Type
ProxyStateTemplateType = types.ProxyStateTemplateV1Alpha1Type
)

// RegisterTypes adds all resource types within the "mesh" API group
Expand All @@ -39,6 +47,10 @@ func RegisterTypes(r resource.Registry) {

// RegisterControllers registers controllers for the mesh types with
// the given controller Manager.
func RegisterControllers(mgr *controller.Manager) {
controllers.Register(mgr)
func RegisterControllers(mgr *controller.Manager, deps ControllerDependencies) {
controllers.Register(mgr, deps)
}

type TrustDomainFetcher = mesh.TrustDomainFetcher

type ControllerDependencies = controllers.Dependencies
153 changes: 13 additions & 140 deletions internal/mesh/internal/controllers/mesh/builder/builder.go
Original file line number Diff line number Diff line change
@@ -1,169 +1,42 @@
package builder

import (
"fmt"

"github.com/hashicorp/consul/envoyextensions/xdscommon"
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1"
pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1"
"github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1/pbproxystate"
"github.com/hashicorp/consul/proto-public/pbresource"
)

// Builder builds a ProxyStateTemplate.
type Builder struct {
id *pbresource.ID
proxyStateTemplate *pbmesh.ProxyStateTemplate

lastBuiltListener lastListenerData
trustDomain string
}

type lastListenerData struct {
index int
}

func New(id *pbresource.ID, identity *pbresource.Reference) *Builder {
func New(id *pbresource.ID, identity *pbresource.Reference, trustDomain string) *Builder {
return &Builder{
id: id,
id: id,
trustDomain: trustDomain,
proxyStateTemplate: &pbmesh.ProxyStateTemplate{
ProxyState: &pbmesh.ProxyState{
Identity: identity,
Clusters: make(map[string]*pbmesh.Cluster),
Endpoints: make(map[string]*pbmesh.Endpoints),
Clusters: make(map[string]*pbproxystate.Cluster),
Endpoints: make(map[string]*pbproxystate.Endpoints),
},
RequiredEndpoints: make(map[string]*pbmesh.EndpointRef),
RequiredLeafCertificates: make(map[string]*pbmesh.LeafCertificateRef),
RequiredTrustBundles: make(map[string]*pbmesh.TrustBundleRef),
RequiredEndpoints: make(map[string]*pbproxystate.EndpointRef),
RequiredLeafCertificates: make(map[string]*pbproxystate.LeafCertificateRef),
RequiredTrustBundles: make(map[string]*pbproxystate.TrustBundleRef),
},
}
}

func (b *Builder) Build() *pbmesh.ProxyStateTemplate {
b.lastBuiltListener = lastListenerData{}
return b.proxyStateTemplate
}

func (b *Builder) AddInboundListener(name string, workload *pbcatalog.Workload) *Builder {
listener := &pbmesh.Listener{
Name: name,
Direction: pbmesh.Direction_DIRECTION_INBOUND,
}

// We will take listener bind port from the workload for now.
// Find mesh port.
var meshPort string
for portName, port := range workload.Ports {
if port.Protocol == pbcatalog.Protocol_PROTOCOL_MESH {
meshPort = portName
break
}
}

// Check if the workload has a specific address for the mesh port.
var meshAddress string
for _, address := range workload.Addresses {
for _, port := range address.Ports {
if port == meshPort {
meshAddress = address.Host
}
}
}
// Otherwise, assume the first address in the addresses list.
if meshAddress == "" {
// It is safe to assume that there's at least one address because we validate it when creating the workload.
meshAddress = workload.Addresses[0].Host
}

listener.BindAddress = &pbmesh.Listener_IpPort{
IpPort: &pbmesh.IPPortAddress{
Ip: meshAddress,
Port: workload.Ports[meshPort].Port,
},
}

// Track the last added listener.
b.lastBuiltListener.index = len(b.proxyStateTemplate.ProxyState.Listeners)
func (b *Builder) addListener(l *pbproxystate.Listener) *Builder {
// Add listener to proxy state template
b.proxyStateTemplate.ProxyState.Listeners = append(b.proxyStateTemplate.ProxyState.Listeners, listener)

return b
}
b.proxyStateTemplate.ProxyState.Listeners = append(b.proxyStateTemplate.ProxyState.Listeners, l)

func (b *Builder) AddInboundRouters(workload *pbcatalog.Workload) *Builder {
listener := b.proxyStateTemplate.ProxyState.Listeners[b.lastBuiltListener.index]

// Go through workload ports and add the first non-mesh port we see.
// todo (ishustava): Note we will need to support multiple ports in the future.
// todo (ishustava): make sure we always iterate through ports in the same order so we don't need to send more updates to envoy.
for portName, port := range workload.Ports {
clusterName := fmt.Sprintf("%s:%s", xdscommon.LocalAppClusterName, portName)
if port.Protocol == pbcatalog.Protocol_PROTOCOL_TCP {
r := &pbmesh.Router{
Destination: &pbmesh.Router_L4{
L4: &pbmesh.L4Destination{
Name: clusterName,
StatPrefix: listener.Name,
},
},
}
listener.Routers = append(listener.Routers, r)

// Make cluster for this router destination.
b.proxyStateTemplate.ProxyState.Clusters[clusterName] = &pbmesh.Cluster{
Group: &pbmesh.Cluster_EndpointGroup{
EndpointGroup: &pbmesh.EndpointGroup{
Group: &pbmesh.EndpointGroup_Static{
Static: &pbmesh.StaticEndpointGroup{
Name: clusterName,
},
},
},
},
}

// Finally, add static endpoints. We're adding it statically as opposed to creating an endpoint ref
// because this endpoint is less likely to change as we're not tracking the health.
endpoint := &pbmesh.Endpoint{
Address: &pbmesh.Endpoint_HostPort{
HostPort: &pbmesh.HostPortAddress{
Host: "127.0.0.1",
Port: port.Port,
},
},
}
b.proxyStateTemplate.ProxyState.Endpoints[clusterName] = &pbmesh.Endpoints{
Name: clusterName,
Endpoints: []*pbmesh.Endpoint{endpoint},
}
break
}
}
return b
}

func (b *Builder) AddInboundTLS() *Builder {
listener := b.proxyStateTemplate.ProxyState.Listeners[b.lastBuiltListener.index]
// For inbound TLS, we want to use this proxy's identity.
workloadIdentity := b.proxyStateTemplate.ProxyState.Identity.Name

inboundTLS := &pbmesh.TransportSocket{
ConnectionTls: &pbmesh.TransportSocket_InboundMesh{
InboundMesh: &pbmesh.InboundMeshMTLS{
IdentityKey: workloadIdentity,
ValidationContext: &pbmesh.MeshInboundValidationContext{TrustBundlePeerNameKeys: []string{b.id.Tenancy.PeerName}},
},
},
}
b.proxyStateTemplate.RequiredLeafCertificates[workloadIdentity] = &pbmesh.LeafCertificateRef{
Name: workloadIdentity,
Namespace: b.id.Tenancy.Namespace,
Partition: b.id.Tenancy.Partition,
}

b.proxyStateTemplate.RequiredTrustBundles[b.id.Tenancy.PeerName] = &pbmesh.TrustBundleRef{
Peer: b.id.Tenancy.PeerName,
}

for i := range listener.Routers {
listener.Routers[i].InboundTls = inboundTLS
}
return b
}
Loading

0 comments on commit 75cb013

Please sign in to comment.