Browse Source

[NET-8091] Add file-system-certificate config entry for API gateway (#20873)

* Define file-system-certificate config entry

* Collect file-system-certificate(s) referenced by api-gateway onto snapshot

* Add file-system-certificate to config entry kind allow lists

* Remove inapplicable validation

This validation makes sense for inline certificates since Consul server is holding the certificate; however, for file system certificates, Consul server never actually sees the certificate.

* Support file-system-certificate as source for listener TLS certificate

* Add more required mappings for the new config entry type

* Construct proper TLS context based on certificate kind

* Add support or SDS in xdscommon

* Remove unused param

* Adds back verification of certs for inline-certificates

* Undo tangential changes to TLS config consumption

* Remove stray curly braces

* Undo some more tangential changes

* Improve function name for generating API gateway secrets

* Add changelog entry

* Update .changelog/20873.txt

Co-authored-by: Jared Kirschner <85913323+jkirschner-hashicorp@users.noreply.github.com>

* Add some nil-checking, remove outdated TODO

* Update test assertions to include file-system-certificate

* Add documentation for file-system-certificate config entry

Add new doc to nav

* Fix grammar mistake

* Rename watchmaps, remove outdated TODO

---------

Co-authored-by: Melisa Griffin <melisa.griffin@hashicorp.com>
Co-authored-by: Jared Kirschner <85913323+jkirschner-hashicorp@users.noreply.github.com>
pull/20984/head
Nathan Coleman 7 months ago committed by GitHub
parent
commit
5e9f02d4be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      .changelog/20873.txt
  2. 11
      agent/consul/fsm/decode_downgrade.go
  3. 5
      agent/consul/fsm/fsm.go
  4. 2
      agent/consul/gateways/controller_gateways.go
  5. 1
      agent/consul/state/config_entry.go
  6. 7
      agent/consul/state/config_entry_events.go
  7. 3
      agent/consul/state/events.go
  8. 1
      agent/consul/state/memdb.go
  9. 16
      agent/consul/usagemetrics/usagemetrics_test.go
  10. 2
      agent/proxycfg-glue/config_entry.go
  11. 61
      agent/proxycfg/api_gateway.go
  12. 3
      agent/proxycfg/proxycfg.deepcopy.go
  13. 7
      agent/proxycfg/snapshot.go
  14. 1
      agent/proxycfg/state.go
  15. 4
      agent/structs/config_entry.go
  16. 73
      agent/structs/config_entry_file_system_certificate.go
  17. 13
      agent/structs/config_entry_gateways.go
  18. 1
      agent/structs/deep-copy.sh
  19. 14
      agent/structs/structs.deepcopy.go
  20. 67
      agent/xds/listeners_apigateway.go
  21. 23
      agent/xds/listeners_ingress.go
  22. 38
      agent/xds/secrets.go
  23. 3
      api/config_entry.go
  24. 44
      api/config_entry_file_system_certificate.go
  25. 6
      envoyextensions/xdscommon/xdscommon.go
  26. 18
      proto/private/pbconfigentry/config_entry.gen.go
  27. 16
      proto/private/pbconfigentry/config_entry.go
  28. 10
      proto/private/pbconfigentry/config_entry.pb.binary.go
  29. 3914
      proto/private/pbconfigentry/config_entry.pb.go
  30. 15
      proto/private/pbconfigentry/config_entry.proto
  31. 48
      proto/private/pbsubscribe/subscribe.pb.go
  32. 3
      proto/private/pbsubscribe/subscribe.proto
  33. 12
      website/content/api-docs/config.mdx
  34. 20
      website/content/docs/connect/config-entries/api-gateway.mdx
  35. 127
      website/content/docs/connect/config-entries/file-system-certificate.mdx
  36. 3
      website/content/docs/connect/gateways/api-gateway/configuration/index.mdx
  37. 4
      website/data/docs-nav-data.json

3
.changelog/20873.txt

@ -0,0 +1,3 @@
```release-note:feature
gateways: api-gateway can leverage listener TLS certificates available on the gateway's local filesystem by specifying the public certificate and private key path in the new file-system-certificate configuration entry
```

11
agent/consul/fsm/decode_downgrade.go

@ -619,6 +619,8 @@ func MakeShadowConfigEntry(kind, name string) (structs.ConfigEntry, error) {
return &ShadowAPIGatewayConfigEntry{APIGatewayConfigEntry: &structs.APIGatewayConfigEntry{Name: name}}, nil
case structs.BoundAPIGateway:
return &ShadowBoundAPIGatewayConfigEntry{BoundAPIGatewayConfigEntry: &structs.BoundAPIGatewayConfigEntry{Name: name}}, nil
case structs.FileSystemCertificate:
return &ShadowFileSystemCertificateConfigEntry{FileSystemCertificateConfigEntry: &structs.FileSystemCertificateConfigEntry{Name: name}}, nil
case structs.InlineCertificate:
return &ShadowInlineCertificateConfigEntry{InlineCertificateConfigEntry: &structs.InlineCertificateConfigEntry{Name: name}}, nil
case structs.HTTPRoute:
@ -931,6 +933,15 @@ func (s ShadowBoundAPIGatewayConfigEntry) GetRealConfigEntry() structs.ConfigEnt
return s.BoundAPIGatewayConfigEntry
}
type ShadowFileSystemCertificateConfigEntry struct {
ShadowBase
*structs.FileSystemCertificateConfigEntry
}
func (s ShadowFileSystemCertificateConfigEntry) GetRealConfigEntry() structs.ConfigEntry {
return s.FileSystemCertificateConfigEntry
}
type ShadowInlineCertificateConfigEntry struct {
ShadowBase
*structs.InlineCertificateConfigEntry

5
agent/consul/fsm/fsm.go

@ -403,6 +403,11 @@ func (c *FSM) registerStreamSnapshotHandlers() {
}, true)
panicIfErr(err)
err = c.deps.Publisher.RegisterHandler(state.EventTopicFileSystemCertificate, func(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return c.State().FileSystemCertificateSnapshot(req, buf)
}, true)
panicIfErr(err)
err = c.deps.Publisher.RegisterHandler(state.EventTopicInlineCertificate, func(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return c.State().InlineCertificateSnapshot(req, buf)
}, true)

2
agent/consul/gateways/controller_gateways.go

@ -61,7 +61,7 @@ func (r *apiGatewayReconciler) Reconcile(ctx context.Context, req controller.Req
return reconcileEntry(r.fsm.State(), r.logger, ctx, req, r.reconcileHTTPRoute, r.cleanupRoute)
case structs.TCPRoute:
return reconcileEntry(r.fsm.State(), r.logger, ctx, req, r.reconcileTCPRoute, r.cleanupRoute)
case structs.InlineCertificate:
case structs.InlineCertificate, structs.FileSystemCertificate:
return r.enqueueCertificateReferencedGateways(r.fsm.State(), ctx, req)
case structs.JWTProvider:
return r.enqueueJWTProviderReferencedGatewaysAndHTTPRoutes(r.fsm.State(), ctx, req)

1
agent/consul/state/config_entry.go

@ -646,6 +646,7 @@ func validateProposedConfigEntryInGraph(
case structs.ExportedServices:
case structs.APIGateway: // TODO Consider checkGatewayClash
case structs.BoundAPIGateway:
case structs.FileSystemCertificate:
case structs.InlineCertificate:
case structs.HTTPRoute:
case structs.TCPRoute:

7
agent/consul/state/config_entry_events.go

@ -23,6 +23,7 @@ var configEntryKindToTopic = map[string]stream.Topic{
structs.APIGateway: EventTopicAPIGateway,
structs.TCPRoute: EventTopicTCPRoute,
structs.HTTPRoute: EventTopicHTTPRoute,
structs.FileSystemCertificate: EventTopicFileSystemCertificate,
structs.InlineCertificate: EventTopicInlineCertificate,
structs.BoundAPIGateway: EventTopicBoundAPIGateway,
structs.RateLimitIPConfig: EventTopicIPRateLimit,
@ -147,6 +148,12 @@ func (s *Store) HTTPRouteSnapshot(req stream.SubscribeRequest, buf stream.Snapsh
return s.configEntrySnapshot(structs.HTTPRoute, req, buf)
}
// FileSystemCertificateSnapshot is a stream.SnapshotFunc that returns a snapshot of
// inline-certificate config entries.
func (s *Store) FileSystemCertificateSnapshot(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return s.configEntrySnapshot(structs.FileSystemCertificate, req, buf)
}
// InlineCertificateSnapshot is a stream.SnapshotFunc that returns a snapshot of
// inline-certificate config entries.
func (s *Store) InlineCertificateSnapshot(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {

3
agent/consul/state/events.go

@ -46,7 +46,8 @@ func PBToStreamSubscribeRequest(req *pbsubscribe.SubscribeRequest, entMeta acl.E
case EventTopicMeshConfig, EventTopicServiceResolver, EventTopicIngressGateway,
EventTopicServiceIntentions, EventTopicServiceDefaults, EventTopicAPIGateway,
EventTopicTCPRoute, EventTopicHTTPRoute, EventTopicJWTProvider, EventTopicInlineCertificate,
EventTopicBoundAPIGateway, EventTopicSamenessGroup, EventTopicExportedServices:
EventTopicBoundAPIGateway, EventTopicSamenessGroup, EventTopicExportedServices,
EventTopicFileSystemCertificate:
subject = EventSubjectConfigEntry{
Name: named.Key,
EnterpriseMeta: &entMeta,

1
agent/consul/state/memdb.go

@ -207,6 +207,7 @@ var (
EventTopicAPIGateway = pbsubscribe.Topic_APIGateway
EventTopicTCPRoute = pbsubscribe.Topic_TCPRoute
EventTopicHTTPRoute = pbsubscribe.Topic_HTTPRoute
EventTopicFileSystemCertificate = pbsubscribe.Topic_FileSystemCertificate
EventTopicInlineCertificate = pbsubscribe.Topic_InlineCertificate
EventTopicBoundAPIGateway = pbsubscribe.Topic_BoundAPIGateway
EventTopicIPRateLimit = pbsubscribe.Topic_IPRateLimit

16
agent/consul/usagemetrics/usagemetrics_test.go

@ -334,6 +334,14 @@ var baseCases = map[string]testCase{
{Name: "kind", Value: "bound-api-gateway"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=file-system-certificate": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "file-system-certificate"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=inline-certificate": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
@ -607,6 +615,14 @@ var baseCases = map[string]testCase{
{Name: "kind", Value: "bound-api-gateway"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=file-system-certificate": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "file-system-certificate"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=inline-certificate": {
Name: "consul.usage.test.state.config_entries",
Value: 0,

2
agent/proxycfg-glue/config_entry.go

@ -69,6 +69,8 @@ func newConfigEntryRequest(req *structs.ConfigEntryQuery, deps ServerDataSourceD
topic = pbsubscribe.Topic_HTTPRoute
case structs.TCPRoute:
topic = pbsubscribe.Topic_TCPRoute
case structs.FileSystemCertificate:
topic = pbsubscribe.Topic_FileSystemCertificate
case structs.InlineCertificate:
topic = pbsubscribe.Topic_InlineCertificate
case structs.BoundAPIGateway:

61
agent/proxycfg/api_gateway.go

@ -63,7 +63,8 @@ func (h *handlerAPIGateway) initialize(ctx context.Context) (ConfigSnapshot, err
snap.APIGateway.BoundListeners = make(map[string]structs.BoundAPIGatewayListener)
snap.APIGateway.HTTPRoutes = watch.NewMap[structs.ResourceReference, *structs.HTTPRouteConfigEntry]()
snap.APIGateway.TCPRoutes = watch.NewMap[structs.ResourceReference, *structs.TCPRouteConfigEntry]()
snap.APIGateway.Certificates = watch.NewMap[structs.ResourceReference, *structs.InlineCertificateConfigEntry]()
snap.APIGateway.InlineCertificates = watch.NewMap[structs.ResourceReference, *structs.InlineCertificateConfigEntry]()
snap.APIGateway.FileSystemCertificates = watch.NewMap[structs.ResourceReference, *structs.FileSystemCertificateConfigEntry]()
snap.APIGateway.Upstreams = make(listenerRouteUpstreams)
snap.APIGateway.UpstreamsSet = make(routeUpstreamSet)
@ -96,7 +97,8 @@ func (h *handlerAPIGateway) subscribeToConfigEntry(ctx context.Context, kind, na
// handleUpdate responds to changes in the api-gateway. In general, we want
// to crawl the various resources related to or attached to the gateway and
// collect the list of things need to generate xDS. This list of resources
// includes the bound-api-gateway, http-routes, tcp-routes, and inline-certificates.
// includes the bound-api-gateway, http-routes, tcp-routes,
// file-system-certificates and inline-certificates.
func (h *handlerAPIGateway) handleUpdate(ctx context.Context, u UpdateEvent, snap *ConfigSnapshot) error {
if u.Err != nil {
return fmt.Errorf("error filling agent cache: %v", u.Err)
@ -113,6 +115,11 @@ func (h *handlerAPIGateway) handleUpdate(ctx context.Context, u UpdateEvent, sna
if err := h.handleGatewayConfigUpdate(ctx, u, snap, u.CorrelationID); err != nil {
return err
}
case fileSystemCertificateConfigWatchID:
// Handle change in an attached file-system-certificate config entry
if err := h.handleFileSystemCertConfigUpdate(ctx, u, snap); err != nil {
return err
}
case inlineCertificateConfigWatchID:
// Handle change in an attached inline-certificate config entry
if err := h.handleInlineCertConfigUpdate(ctx, u, snap); err != nil {
@ -205,15 +212,24 @@ func (h *handlerAPIGateway) handleGatewayConfigUpdate(ctx context.Context, u Upd
for _, ref := range listener.Certificates {
ctx, cancel := context.WithCancel(ctx)
seenRefs[ref] = struct{}{}
snap.APIGateway.Certificates.InitWatch(ref, cancel)
if ref.Kind == structs.FileSystemCertificate {
snap.APIGateway.FileSystemCertificates.InitWatch(ref, cancel)
err := h.subscribeToConfigEntry(ctx, ref.Kind, ref.Name, ref.EnterpriseMeta, fileSystemCertificateConfigWatchID)
if err != nil {
return err
}
} else {
snap.APIGateway.InlineCertificates.InitWatch(ref, cancel)
err := h.subscribeToConfigEntry(ctx, ref.Kind, ref.Name, ref.EnterpriseMeta, inlineCertificateConfigWatchID)
if err != nil {
// TODO May want to continue
return err
}
}
}
}
// Unsubscribe from any config entries that are no longer attached
snap.APIGateway.HTTPRoutes.ForEachKey(func(ref structs.ResourceReference) bool {
@ -234,9 +250,16 @@ func (h *handlerAPIGateway) handleGatewayConfigUpdate(ctx context.Context, u Upd
return true
})
snap.APIGateway.Certificates.ForEachKey(func(ref structs.ResourceReference) bool {
snap.APIGateway.InlineCertificates.ForEachKey(func(ref structs.ResourceReference) bool {
if _, ok := seenRefs[ref]; !ok {
snap.APIGateway.Certificates.CancelWatch(ref)
snap.APIGateway.InlineCertificates.CancelWatch(ref)
}
return true
})
snap.APIGateway.FileSystemCertificates.ForEachKey(func(ref structs.ResourceReference) bool {
if _, ok := seenRefs[ref]; !ok {
snap.APIGateway.FileSystemCertificates.CancelWatch(ref)
}
return true
})
@ -265,6 +288,30 @@ func (h *handlerAPIGateway) handleGatewayConfigUpdate(ctx context.Context, u Upd
return h.watchIngressLeafCert(ctx, snap)
}
func (h *handlerAPIGateway) handleFileSystemCertConfigUpdate(_ context.Context, u UpdateEvent, snap *ConfigSnapshot) error {
resp, ok := u.Result.(*structs.ConfigEntryResponse)
if !ok {
return fmt.Errorf("invalid type for response: %T", u.Result)
} else if resp == nil || resp.Entry == nil {
return nil
}
cfg, ok := resp.Entry.(*structs.FileSystemCertificateConfigEntry)
if !ok || cfg == nil {
return fmt.Errorf("invalid type for config entry: %T", resp.Entry)
}
ref := structs.ResourceReference{
Kind: cfg.GetKind(),
Name: cfg.GetName(),
EnterpriseMeta: *cfg.GetEnterpriseMeta(),
}
snap.APIGateway.FileSystemCertificates.Set(ref, cfg)
return nil
}
// handleInlineCertConfigUpdate stores the certificate for the gateway
func (h *handlerAPIGateway) handleInlineCertConfigUpdate(_ context.Context, u UpdateEvent, snap *ConfigSnapshot) error {
resp, ok := u.Result.(*structs.ConfigEntryResponse)
@ -285,7 +332,7 @@ func (h *handlerAPIGateway) handleInlineCertConfigUpdate(_ context.Context, u Up
EnterpriseMeta: *cfg.GetEnterpriseMeta(),
}
snap.APIGateway.Certificates.Set(ref, cfg)
snap.APIGateway.InlineCertificates.Set(ref, cfg)
return nil
}

3
agent/proxycfg/proxycfg.deepcopy.go

@ -421,7 +421,8 @@ func (o *configSnapshotAPIGateway) DeepCopy() *configSnapshotAPIGateway {
}
cp.HTTPRoutes = o.HTTPRoutes.DeepCopy()
cp.TCPRoutes = o.TCPRoutes.DeepCopy()
cp.Certificates = o.Certificates.DeepCopy()
cp.InlineCertificates = o.InlineCertificates.DeepCopy()
cp.FileSystemCertificates = o.FileSystemCertificates.DeepCopy()
if o.Listeners != nil {
cp.Listeners = make(map[string]structs.APIGatewayListener, len(o.Listeners))
for k2, v2 := range o.Listeners {

7
agent/proxycfg/snapshot.go

@ -9,6 +9,8 @@ import (
"sort"
"strings"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/consul/discoverychain"
@ -17,7 +19,6 @@ import (
"github.com/hashicorp/consul/agent/xds/config"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/proto/private/pbpeering"
"github.com/hashicorp/go-hclog"
)
// TODO(ingress): Can we think of a better for this bag of data?
@ -737,7 +738,9 @@ type configSnapshotAPIGateway struct {
HTTPRoutes watch.Map[structs.ResourceReference, *structs.HTTPRouteConfigEntry]
TCPRoutes watch.Map[structs.ResourceReference, *structs.TCPRouteConfigEntry]
Certificates watch.Map[structs.ResourceReference, *structs.InlineCertificateConfigEntry]
InlineCertificates watch.Map[structs.ResourceReference, *structs.InlineCertificateConfigEntry]
FileSystemCertificates watch.Map[structs.ResourceReference, *structs.FileSystemCertificateConfigEntry]
// LeafCertWatchCancel is a CancelFunc to use when refreshing this gateway's
// leaf cert watch with different parameters.

1
agent/proxycfg/state.go

@ -38,6 +38,7 @@ const (
gatewayConfigWatchID = "gateway-config"
apiGatewayConfigWatchID = "api-gateway-config"
boundGatewayConfigWatchID = "bound-gateway-config"
fileSystemCertificateConfigWatchID = "file-system-certificate-config"
inlineCertificateConfigWatchID = "inline-certificate-config"
routeConfigWatchID = "route-config"
externalServiceIDPrefix = "external-service:"

4
agent/structs/config_entry.go

@ -40,6 +40,7 @@ const (
SamenessGroup string = "sameness-group"
APIGateway string = "api-gateway"
BoundAPIGateway string = "bound-api-gateway"
FileSystemCertificate string = "file-system-certificate"
InlineCertificate string = "inline-certificate"
HTTPRoute string = "http-route"
TCPRoute string = "tcp-route"
@ -71,6 +72,7 @@ var AllConfigEntryKinds = []string{
BoundAPIGateway,
HTTPRoute,
TCPRoute,
FileSystemCertificate,
InlineCertificate,
RateLimitIPConfig,
JWTProvider,
@ -832,6 +834,8 @@ func MakeConfigEntry(kind, name string) (ConfigEntry, error) {
return &APIGatewayConfigEntry{Name: name}, nil
case BoundAPIGateway:
return &BoundAPIGatewayConfigEntry{Name: name}, nil
case FileSystemCertificate:
return &FileSystemCertificateConfigEntry{Name: name}, nil
case InlineCertificate:
return &InlineCertificateConfigEntry{Name: name}, nil
case HTTPRoute:

73
agent/structs/config_entry_file_system_certificate.go

@ -0,0 +1,73 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package structs
import (
"github.com/hashicorp/consul/acl"
)
// FileSystemCertificateConfigEntry manages the configuration for a certificate
// and private key located in the local file system.
type FileSystemCertificateConfigEntry struct {
// Kind of config entry. This will be set to structs.FileSystemCertificate.
Kind string
// Name is used to match the config entry with its associated file system certificate.
Name string
// Certificate is the optional path to a client certificate to use for TLS connections.
Certificate string
// PrivateKey is the optional path to a private key to use for TLS connections.
PrivateKey string
Meta map[string]string `json:",omitempty"`
Hash uint64 `json:",omitempty" hash:"ignore"`
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex `hash:"ignore"`
}
func (e *FileSystemCertificateConfigEntry) SetHash(h uint64) {
e.Hash = h
}
func (e *FileSystemCertificateConfigEntry) GetHash() uint64 {
return e.Hash
}
func (e *FileSystemCertificateConfigEntry) GetKind() string { return FileSystemCertificate }
func (e *FileSystemCertificateConfigEntry) GetName() string { return e.Name }
func (e *FileSystemCertificateConfigEntry) Normalize() error {
h, err := HashConfigEntry(e)
if err != nil {
return err
}
e.Hash = h
return nil
}
func (e *FileSystemCertificateConfigEntry) GetMeta() map[string]string { return e.Meta }
func (e *FileSystemCertificateConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
return &e.EnterpriseMeta
}
func (e *FileSystemCertificateConfigEntry) GetRaftIndex() *RaftIndex { return &e.RaftIndex }
func (e *FileSystemCertificateConfigEntry) Validate() error {
return validateConfigEntryMeta(e.Meta)
}
func (e *FileSystemCertificateConfigEntry) Hosts() ([]string, error) {
return []string{}, nil
}
func (e *FileSystemCertificateConfigEntry) CanRead(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshReadAllowed(&authzContext)
}
func (e *FileSystemCertificateConfigEntry) CanWrite(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext)
}

13
agent/structs/config_entry_gateways.go

@ -870,6 +870,7 @@ func (e *APIGatewayConfigEntry) validateListeners() error {
ListenerProtocolTCP: true,
}
allowedCertificateKinds := map[string]bool{
FileSystemCertificate: true,
InlineCertificate: true,
}
@ -889,7 +890,7 @@ func (e *APIGatewayConfigEntry) validateListeners() error {
}
for _, certificate := range listener.TLS.Certificates {
if !allowedCertificateKinds[certificate.Kind] {
return fmt.Errorf("unsupported certificate kind: %q, must be 'inline-certificate'", certificate.Kind)
return fmt.Errorf("unsupported certificate kind: %q, must be 'file-system-certificate' or 'inline-certificate'", certificate.Kind)
}
if certificate.Name == "" {
return fmt.Errorf("certificate reference must have a name")
@ -978,6 +979,16 @@ func (a *APIGatewayTLSConfiguration) IsEmpty() bool {
return len(a.Certificates) == 0 && len(a.MaxVersion) == 0 && len(a.MinVersion) == 0 && len(a.CipherSuites) == 0
}
func (a *APIGatewayTLSConfiguration) ToGatewayTLSConfig() GatewayTLSConfig {
return GatewayTLSConfig{
Enabled: true,
SDS: nil,
TLSMinVersion: a.MinVersion,
TLSMaxVersion: a.MaxVersion,
CipherSuites: a.CipherSuites,
}
}
// ServiceRouteReferences is a map with a key of ServiceName type for a routed to service from a
// bound gateway listener with a value being a slice of resource references of the routes that reference the service
type ServiceRouteReferences map[ServiceName][]ResourceReference

1
agent/structs/deep-copy.sh

@ -24,6 +24,7 @@ deep-copy \
-type DiscoverySplit \
-type ExposeConfig \
-type ExportedServicesConfigEntry \
-type FileSystemCertificateConfigEntry \
-type GatewayService \
-type GatewayServiceTLSConfig \
-type HTTPHeaderModifiers \

14
agent/structs/structs.deepcopy.go

@ -1,4 +1,4 @@
// generated by deep-copy -pointer-receiver -o ./structs.deepcopy.go -type APIGatewayListener -type BoundAPIGatewayListener -type CARoot -type CheckServiceNode -type CheckType -type CompiledDiscoveryChain -type ConnectProxyConfig -type DiscoveryFailover -type DiscoveryGraphNode -type DiscoveryResolver -type DiscoveryRoute -type DiscoverySplit -type ExposeConfig -type ExportedServicesConfigEntry -type GatewayService -type GatewayServiceTLSConfig -type HTTPHeaderModifiers -type HTTPRouteConfigEntry -type HashPolicy -type HealthCheck -type IndexedCARoots -type IngressListener -type InlineCertificateConfigEntry -type Intention -type IntentionPermission -type LoadBalancer -type MeshConfigEntry -type MeshDirectionalTLSConfig -type MeshTLSConfig -type Node -type NodeService -type PeeringServiceMeta -type ServiceConfigEntry -type ServiceConfigResponse -type ServiceConnect -type ServiceDefinition -type ServiceResolverConfigEntry -type ServiceResolverFailover -type ServiceRoute -type ServiceRouteDestination -type ServiceRouteMatch -type TCPRouteConfigEntry -type Upstream -type UpstreamConfiguration -type Status -type BoundAPIGatewayConfigEntry ./; DO NOT EDIT.
// generated by deep-copy -pointer-receiver -o ./structs.deepcopy.go -type APIGatewayListener -type BoundAPIGatewayListener -type CARoot -type CheckServiceNode -type CheckType -type CompiledDiscoveryChain -type ConnectProxyConfig -type DiscoveryFailover -type DiscoveryGraphNode -type DiscoveryResolver -type DiscoveryRoute -type DiscoverySplit -type ExposeConfig -type ExportedServicesConfigEntry -type FileSystemCertificateConfigEntry -type GatewayService -type GatewayServiceTLSConfig -type HTTPHeaderModifiers -type HTTPRouteConfigEntry -type HashPolicy -type HealthCheck -type IndexedCARoots -type IngressListener -type InlineCertificateConfigEntry -type Intention -type IntentionPermission -type LoadBalancer -type MeshConfigEntry -type MeshDirectionalTLSConfig -type MeshTLSConfig -type Node -type NodeService -type PeeringServiceMeta -type ServiceConfigEntry -type ServiceConfigResponse -type ServiceConnect -type ServiceDefinition -type ServiceResolverConfigEntry -type ServiceResolverFailover -type ServiceRoute -type ServiceRouteDestination -type ServiceRouteMatch -type TCPRouteConfigEntry -type Upstream -type UpstreamConfiguration -type Status -type BoundAPIGatewayConfigEntry ./; DO NOT EDIT.
package structs
@ -318,6 +318,18 @@ func (o *ExportedServicesConfigEntry) DeepCopy() *ExportedServicesConfigEntry {
return &cp
}
// DeepCopy generates a deep copy of *FileSystemCertificateConfigEntry
func (o *FileSystemCertificateConfigEntry) DeepCopy() *FileSystemCertificateConfigEntry {
var cp FileSystemCertificateConfigEntry = *o
if o.Meta != nil {
cp.Meta = make(map[string]string, len(o.Meta))
for k2, v2 := range o.Meta {
cp.Meta[k2] = v2
}
}
return &cp
}
// DeepCopy generates a deep copy of *GatewayService
func (o *GatewayService) DeepCopy() *GatewayService {
var cp GatewayService = *o

67
agent/xds/listeners_apigateway.go

@ -6,22 +6,19 @@ package xds
import (
"fmt"
"golang.org/x/exp/maps"
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
envoy_http_jwt_authn_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/jwt_authn/v3"
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"github.com/hashicorp/consul/agent/consul/discoverychain"
"github.com/hashicorp/consul/agent/xds/naming"
"golang.org/x/exp/maps"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/hashicorp/consul/agent/consul/discoverychain"
"github.com/hashicorp/consul/agent/proxycfg"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/xds/naming"
"github.com/hashicorp/consul/types"
)
@ -35,13 +32,19 @@ func (s *ResourceGenerator) makeAPIGatewayListeners(address string, cfgSnap *pro
listenerKey := readyListener.listenerKey
boundListener := readyListener.boundListenerCfg
var certs []structs.InlineCertificateConfigEntry
// Collect the referenced certificate config entries
var certs []structs.ConfigEntry
for _, certRef := range boundListener.Certificates {
cert, ok := cfgSnap.APIGateway.Certificates.Get(certRef)
if !ok {
continue
switch certRef.Kind {
case structs.InlineCertificate:
if cert, ok := cfgSnap.APIGateway.InlineCertificates.Get(certRef); ok {
certs = append(certs, cert)
}
case structs.FileSystemCertificate:
if cert, ok := cfgSnap.APIGateway.FileSystemCertificates.Get(certRef); ok {
certs = append(certs, cert)
}
}
certs = append(certs, *cert)
}
isAPIGatewayWithTLS := len(boundListener.Certificates) > 0
@ -111,7 +114,7 @@ func (s *ResourceGenerator) makeAPIGatewayListeners(address string, cfgSnap *pro
if isAPIGatewayWithTLS {
// construct SNI filter chains
l.FilterChains, err = makeInlineOverrideFilterChains(
l.FilterChains, err = s.makeInlineOverrideFilterChains(
cfgSnap,
cfgSnap.APIGateway.TLSConfig,
listenerKey.Protocol,
@ -225,7 +228,7 @@ func (s *ResourceGenerator) makeAPIGatewayListeners(address string, cfgSnap *pro
sniFilterChains := []*envoy_listener_v3.FilterChain{}
if isAPIGatewayWithTLS {
sniFilterChains, err = makeInlineOverrideFilterChains(cfgSnap, cfgSnap.IngressGateway.TLSConfig, listenerKey.Protocol, filterOpts, certs)
sniFilterChains, err = s.makeInlineOverrideFilterChains(cfgSnap, cfgSnap.IngressGateway.TLSConfig, listenerKey.Protocol, filterOpts, certs)
if err != nil {
return nil, err
}
@ -393,11 +396,11 @@ func resolveAPIListenerTLSConfig(listenerTLSCfg structs.APIGatewayTLSConfigurati
// when we have multiple certificates on a single listener, we need
// to duplicate the filter chains with multiple TLS contexts
func makeInlineOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot,
func (s *ResourceGenerator) makeInlineOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot,
tlsCfg structs.GatewayTLSConfig,
protocol string,
filterOpts listenerFilterOpts,
certs []structs.InlineCertificateConfigEntry,
certs []structs.ConfigEntry,
) ([]*envoy_listener_v3.FilterChain, error) {
var chains []*envoy_listener_v3.FilterChain
@ -438,7 +441,9 @@ func makeInlineOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot,
// we only need to prune out overlapping hosts if we have more than
// one certificate
for _, cert := range certs {
hosts, err := cert.Hosts()
switch tce := cert.(type) {
case *structs.InlineCertificateConfigEntry:
hosts, err := tce.Hosts()
if err != nil {
return nil, fmt.Errorf("unable to parse hosts from x509 certificate: %v", hosts)
}
@ -448,6 +453,20 @@ func makeInlineOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot,
}
allCertHosts[host] = struct{}{}
}
default:
// do nothing for FileSystemCertificates because we don't actually have the certificate available
}
}
}
constructTLSContext := func(certConfig structs.ConfigEntry) (*envoy_tls_v3.CommonTlsContext, error) {
switch tce := certConfig.(type) {
case *structs.InlineCertificateConfigEntry:
return makeInlineTLSContextFromGatewayTLSConfig(tlsCfg, tce), nil
case *structs.FileSystemCertificateConfigEntry:
return makeFileSystemTLSContextFromGatewayTLSConfig(tlsCfg, tce), nil
default:
return nil, fmt.Errorf("unsupported config entry kind %s", tce.GetKind())
}
}
@ -456,9 +475,9 @@ func makeInlineOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot,
// if we only have one cert, we just use it for all ingress
if multipleCerts {
// otherwise, we need an SNI per cert and to fallback to our ingress
// gateway certificate signed by our Consul CA
certHosts, err := cert.Hosts()
switch tce := cert.(type) {
case *structs.InlineCertificateConfigEntry:
certHosts, err := tce.Hosts()
if err != nil {
return nil, fmt.Errorf("unable to parse hosts from x509 certificate: %v", hosts)
}
@ -475,13 +494,19 @@ func makeInlineOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot,
continue
}
}
}
if err := constructChain(cert.Name, hosts, makeInlineTLSContextFromGatewayTLSConfig(tlsCfg, cert)); err != nil {
tlsContext, err := constructTLSContext(cert)
if err != nil {
continue
}
if err := constructChain(cert.GetName(), hosts, tlsContext); err != nil {
return nil, err
}
}
if multipleCerts {
if len(certs) > 1 {
// if we have more than one cert, add a default handler that uses the leaf cert from connect
if err := constructChain("default", nil, makeCommonTLSContext(cfgSnap.Leaf(), cfgSnap.RootPEMs(), makeTLSParametersFromGatewayTLSConfig(tlsCfg))); err != nil {
return nil, err

23
agent/xds/listeners_ingress.go

@ -9,7 +9,6 @@ import (
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"github.com/hashicorp/consul/agent/xds/naming"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
@ -17,6 +16,7 @@ import (
"github.com/hashicorp/consul/agent/proxycfg"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/xds/naming"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/types"
)
@ -391,7 +391,26 @@ func makeTLSParametersFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig) *env
return makeTLSParametersFromTLSConfig(tlsCfg.TLSMinVersion, tlsCfg.TLSMaxVersion, tlsCfg.CipherSuites)
}
func makeInlineTLSContextFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig, cert structs.InlineCertificateConfigEntry) *envoy_tls_v3.CommonTlsContext {
func makeFileSystemTLSContextFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig, cert *structs.FileSystemCertificateConfigEntry) *envoy_tls_v3.CommonTlsContext {
return &envoy_tls_v3.CommonTlsContext{
TlsParams: makeTLSParametersFromGatewayTLSConfig(tlsCfg),
TlsCertificateSdsSecretConfigs: []*envoy_tls_v3.SdsSecretConfig{
{
// Delivering via SDS is required in order to get file system watches today.
// https://github.com/envoyproxy/envoy/issues/10387
Name: cert.Name, // Reference the secret returned in xds/secrets.go by name here
SdsConfig: &envoy_core_v3.ConfigSource{
ConfigSourceSpecifier: &envoy_core_v3.ConfigSource_Ads{
Ads: &envoy_core_v3.AggregatedConfigSource{},
},
ResourceApiVersion: envoy_core_v3.ApiVersion_V3,
},
},
},
}
}
func makeInlineTLSContextFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig, cert *structs.InlineCertificateConfigEntry) *envoy_tls_v3.CommonTlsContext {
return &envoy_tls_v3.CommonTlsContext{
TlsParams: makeTLSParametersFromGatewayTLSConfig(tlsCfg),
TlsCertificates: []*envoy_tls_v3.TlsCertificate{{

38
agent/xds/secrets.go

@ -7,6 +7,8 @@ import (
"errors"
"fmt"
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"google.golang.org/protobuf/proto"
"github.com/hashicorp/consul/agent/proxycfg"
@ -21,13 +23,45 @@ func (s *ResourceGenerator) secretsFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot
}
switch cfgSnap.Kind {
case structs.ServiceKindAPIGateway:
return s.secretsFromSnapshotAPIGateway(cfgSnap), nil // return any attached certs
case structs.ServiceKindConnectProxy,
structs.ServiceKindTerminatingGateway,
structs.ServiceKindMeshGateway,
structs.ServiceKindIngressGateway,
structs.ServiceKindAPIGateway:
structs.ServiceKindIngressGateway:
return nil, nil
default:
return nil, fmt.Errorf("Invalid service kind: %v", cfgSnap.Kind)
}
}
// secretsFromSnapshotAPIGateway returns the "secrets" for an api-gateway service
func (s *ResourceGenerator) secretsFromSnapshotAPIGateway(cfgSnap *proxycfg.ConfigSnapshot) []proto.Message {
var resources []proto.Message
cfgSnap.APIGateway.FileSystemCertificates.ForEachKey(func(ref structs.ResourceReference) bool {
cert, ok := cfgSnap.APIGateway.FileSystemCertificates.Get(ref)
if !ok || cert == nil {
return true
}
resources = append(resources, &envoy_tls_v3.Secret{
Name: ref.Name,
Type: &envoy_tls_v3.Secret_TlsCertificate{
TlsCertificate: &envoy_tls_v3.TlsCertificate{
CertificateChain: &envoy_core_v3.DataSource{
Specifier: &envoy_core_v3.DataSource_Filename{
Filename: cert.Certificate,
}},
PrivateKey: &envoy_core_v3.DataSource{
Specifier: &envoy_core_v3.DataSource_Filename{
Filename: cert.PrivateKey,
},
},
},
},
})
return true
})
return resources
}

3
api/config_entry.go

@ -33,6 +33,7 @@ const (
MeshConfigMesh string = "mesh"
APIGateway string = "api-gateway"
TCPRoute string = "tcp-route"
FileSystemCertificate string = "file-system-certificate"
InlineCertificate string = "inline-certificate"
HTTPRoute string = "http-route"
JWTProvider string = "jwt-provider"
@ -447,6 +448,8 @@ func makeConfigEntry(kind, name string) (ConfigEntry, error) {
return &APIGatewayConfigEntry{Kind: kind, Name: name}, nil
case TCPRoute:
return &TCPRouteConfigEntry{Kind: kind, Name: name}, nil
case FileSystemCertificate:
return &FileSystemCertificateConfigEntry{Kind: kind, Name: name}, nil
case InlineCertificate:
return &InlineCertificateConfigEntry{Kind: kind, Name: name}, nil
case HTTPRoute:

44
api/config_entry_file_system_certificate.go

@ -0,0 +1,44 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
type FileSystemCertificateConfigEntry struct {
// Kind of the config entry. This should be set to api.FileSystemCertificate.
Kind string
Name string
// Certificate is the path to a client certificate to use for TLS connections.
Certificate string `json:",omitempty" alias:"certificate"`
// PrivateKey is the path to a private key to use for TLS connections.
PrivateKey string `json:",omitempty" alias:"private_key"`
Meta map[string]string `json:",omitempty"`
// CreateIndex is the Raft index this entry was created at. This is a
// read-only field.
CreateIndex uint64
// ModifyIndex is used for the Check-And-Set operations and can also be fed
// back into the WaitIndex of the QueryOptions in order to perform blocking
// queries.
ModifyIndex uint64
// Partition is the partition the config entry is associated with.
// Partitioning is a Consul Enterprise feature.
Partition string `json:",omitempty"`
// Namespace is the namespace the config entry is associated with.
// Namespacing is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
}
func (a *FileSystemCertificateConfigEntry) GetKind() string { return FileSystemCertificate }
func (a *FileSystemCertificateConfigEntry) GetName() string { return a.Name }
func (a *FileSystemCertificateConfigEntry) GetPartition() string { return a.Partition }
func (a *FileSystemCertificateConfigEntry) GetNamespace() string { return a.Namespace }
func (a *FileSystemCertificateConfigEntry) GetMeta() map[string]string { return a.Meta }
func (a *FileSystemCertificateConfigEntry) GetCreateIndex() uint64 { return a.CreateIndex }
func (a *FileSystemCertificateConfigEntry) GetModifyIndex() uint64 { return a.ModifyIndex }

6
envoyextensions/xdscommon/xdscommon.go

@ -8,6 +8,7 @@ import (
envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"github.com/hashicorp/go-hclog"
"google.golang.org/protobuf/proto"
)
@ -111,7 +112,7 @@ func IndexResources(logger hclog.Logger, resources map[string][]proto.Message) *
}
func GetResourceName(res proto.Message) string {
// NOTE: this only covers types that we currently care about for LDS/RDS/CDS/EDS
// NOTE: this only covers types that we currently care about for LDS/RDS/CDS/EDS/SDS
switch x := res.(type) {
case *envoy_listener_v3.Listener: // LDS
return x.Name
@ -121,6 +122,8 @@ func GetResourceName(res proto.Message) string {
return x.Name
case *envoy_endpoint_v3.ClusterLoadAssignment: // EDS
return x.ClusterName
case *envoy_tls_v3.Secret: // SDS
return x.Name
default:
return ""
}
@ -133,6 +136,7 @@ func EmptyIndexedResources() *IndexedResources {
RouteType: make(map[string]proto.Message),
ClusterType: make(map[string]proto.Message),
EndpointType: make(map[string]proto.Message),
SecretType: make(map[string]proto.Message),
},
ChildIndex: map[string]map[string][]string{
ListenerType: make(map[string][]string),

18
proto/private/pbconfigentry/config_entry.gen.go

@ -408,6 +408,24 @@ func ExposePathFromStructs(t *structs.ExposePath, s *ExposePath) {
s.Protocol = t.Protocol
s.ParsedFromCheck = t.ParsedFromCheck
}
func FileSystemCertificateToStructs(s *FileSystemCertificate, t *structs.FileSystemCertificateConfigEntry) {
if s == nil {
return
}
t.Certificate = s.Certificate
t.PrivateKey = s.PrivateKey
t.Meta = s.Meta
t.Hash = s.Hash
}
func FileSystemCertificateFromStructs(t *structs.FileSystemCertificateConfigEntry, s *FileSystemCertificate) {
if s == nil {
return
}
s.Certificate = t.Certificate
s.PrivateKey = t.PrivateKey
s.Meta = t.Meta
s.Hash = t.Hash
}
func GatewayServiceTLSConfigToStructs(s *GatewayServiceTLSConfig, t *structs.GatewayServiceTLSConfig) {
if s == nil {
return

16
proto/private/pbconfigentry/config_entry.go

@ -85,6 +85,14 @@ func ConfigEntryToStructs(s *ConfigEntry) structs.ConfigEntry {
pbcommon.RaftIndexToStructs(s.RaftIndex, &target.RaftIndex)
pbcommon.EnterpriseMetaToStructs(s.EnterpriseMeta, &target.EnterpriseMeta)
return &target
case Kind_KindFileSystemCertificate:
var target structs.FileSystemCertificateConfigEntry
target.Name = s.Name
FileSystemCertificateToStructs(s.GetFileSystemCertificate(), &target)
pbcommon.RaftIndexToStructs(s.RaftIndex, &target.RaftIndex)
pbcommon.EnterpriseMetaToStructs(s.EnterpriseMeta, &target.EnterpriseMeta)
return &target
case Kind_KindInlineCertificate:
var target structs.InlineCertificateConfigEntry
target.Name = s.Name
@ -213,6 +221,14 @@ func ConfigEntryFromStructs(s structs.ConfigEntry) *ConfigEntry {
configEntry.Entry = &ConfigEntry_HTTPRoute{
HTTPRoute: &route,
}
case *structs.FileSystemCertificateConfigEntry:
var cert FileSystemCertificate
FileSystemCertificateFromStructs(v, &cert)
configEntry.Kind = Kind_KindFileSystemCertificate
configEntry.Entry = &ConfigEntry_FileSystemCertificate{
FileSystemCertificate: &cert,
}
case *structs.InlineCertificateConfigEntry:
var cert InlineCertificate
InlineCertificateFromStructs(v, &cert)

10
proto/private/pbconfigentry/config_entry.pb.binary.go

@ -657,6 +657,16 @@ func (msg *BoundAPIGatewayListener) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *FileSystemCertificate) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *FileSystemCertificate) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *InlineCertificate) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)

3914
proto/private/pbconfigentry/config_entry.pb.go

File diff suppressed because it is too large Load Diff

15
proto/private/pbconfigentry/config_entry.proto

@ -54,6 +54,7 @@ enum Kind {
KindSamenessGroup = 11;
KindJWTProvider = 12;
KindExportedServices = 13;
KindFileSystemCertificate = 14;
}
message ConfigEntry {
@ -77,6 +78,7 @@ message ConfigEntry {
SamenessGroup SamenessGroup = 15;
JWTProvider JWTProvider = 16;
ExportedServices ExportedServices = 17;
FileSystemCertificate FileSystemCertificate = 18;
}
}
@ -867,6 +869,19 @@ message BoundAPIGatewayListener {
repeated ResourceReference Routes = 3;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.FileSystemCertificateConfigEntry
// output=config_entry.gen.go
// name=Structs
// ignore-fields=Kind,Name,RaftIndex,EnterpriseMeta
message FileSystemCertificate {
map<string, string> Meta = 1;
string Certificate = 2;
string PrivateKey = 3;
uint64 Hash = 4;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.InlineCertificateConfigEntry

48
proto/private/pbsubscribe/subscribe.pb.go

@ -80,6 +80,8 @@ const (
Topic_JWTProvider Topic = 16
// ExportedServices topic contains events for changes to exported-services.
Topic_ExportedServices Topic = 17
// FileSystemCertificate topic contains events for changes to file-system-certificates.
Topic_FileSystemCertificate Topic = 18
)
// Enum value maps for Topic.
@ -103,6 +105,7 @@ var (
15: "SamenessGroup",
16: "JWTProvider",
17: "ExportedServices",
18: "FileSystemCertificate",
}
Topic_value = map[string]int32{
"Unknown": 0,
@ -123,6 +126,7 @@ var (
"SamenessGroup": 15,
"JWTProvider": 16,
"ExportedServices": 17,
"FileSystemCertificate": 18,
}
)
@ -1004,7 +1008,7 @@ var file_private_pbsubscribe_subscribe_proto_rawDesc = []byte{
0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0e, 0x45,
0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a,
0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x2a, 0xdb, 0x02, 0x0a, 0x05, 0x54, 0x6f,
0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x2a, 0xf6, 0x02, 0x0a, 0x05, 0x54, 0x6f,
0x70, 0x69, 0x63, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00,
0x12, 0x11, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74,
0x68, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65,
@ -1026,26 +1030,28 @@ var file_private_pbsubscribe_subscribe_proto_rawDesc = []byte{
0x0d, 0x53, 0x61, 0x6d, 0x65, 0x6e, 0x65, 0x73, 0x73, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x10, 0x0f,
0x12, 0x0f, 0x0a, 0x0b, 0x4a, 0x57, 0x54, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x10,
0x10, 0x12, 0x14, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x73, 0x10, 0x11, 0x2a, 0x29, 0x0a, 0x09, 0x43, 0x61, 0x74, 0x61, 0x6c,
0x6f, 0x67, 0x4f, 0x70, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
0x10, 0x01, 0x32, 0x61, 0x0a, 0x17, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67,
0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a,
0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x75, 0x62,
0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72,
0x69, 0x62, 0x65, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x08, 0xe2, 0x86, 0x04, 0x04, 0x08,
0x02, 0x10, 0x09, 0x30, 0x01, 0x42, 0x9a, 0x01, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x75,
0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x42, 0x0e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
0x62, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f,
0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x69,
0x76, 0x61, 0x74, 0x65, 0x2f, 0x70, 0x62, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,
0xa2, 0x02, 0x03, 0x53, 0x58, 0x58, 0xaa, 0x02, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
0x62, 0x65, 0xca, 0x02, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0xe2, 0x02,
0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
0x62, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x76, 0x69, 0x63, 0x65, 0x73, 0x10, 0x11, 0x12, 0x19, 0x0a, 0x15, 0x46, 0x69, 0x6c, 0x65, 0x53,
0x79, 0x73, 0x74, 0x65, 0x6d, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
0x10, 0x12, 0x2a, 0x29, 0x0a, 0x09, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x4f, 0x70, 0x12,
0x0c, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x10, 0x00, 0x12, 0x0e, 0x0a,
0x0a, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x10, 0x01, 0x32, 0x61, 0x0a,
0x17, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x62, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73,
0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62,
0x65, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x10, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x2e, 0x45,
0x76, 0x65, 0x6e, 0x74, 0x22, 0x08, 0xe2, 0x86, 0x04, 0x04, 0x08, 0x02, 0x10, 0x09, 0x30, 0x01,
0x42, 0x9a, 0x01, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
0x62, 0x65, 0x42, 0x0e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x50, 0x72, 0x6f,
0x74, 0x6f, 0x50, 0x01, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75,
0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x2f,
0x70, 0x62, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0xa2, 0x02, 0x03, 0x53, 0x58,
0x58, 0xaa, 0x02, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0xca, 0x02, 0x09,
0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0xe2, 0x02, 0x15, 0x53, 0x75, 0x62, 0x73,
0x63, 0x72, 0x69, 0x62, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0xea, 0x02, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

3
proto/private/pbsubscribe/subscribe.proto

@ -109,6 +109,9 @@ enum Topic {
// ExportedServices topic contains events for changes to exported-services.
ExportedServices = 17;
// FileSystemCertificate topic contains events for changes to file-system-certificates.
FileSystemCertificate = 18;
}
message NamedSubject {

12
website/content/api-docs/config.mdx

@ -40,10 +40,11 @@ The corresponding CLI command is [`consul config write`](/consul/commands/config
The ACL required depends on the config entry being written:
| Config Entry Kind | Required ACLs |
| ------------------- | -------------------------------- |
| ----------------------- | -------------------------------- |
| api-gateway | `mesh:write` or `operator:write` |
| bound-api-gateway | Not writable. |
| exported-services | `mesh:write` or `operator:write` |
| file-system-certificate | `mesh:write` or `operator:write` |
| http-route | `mesh:write` or `operator:write` |
| ingress-gateway | `mesh:write` or `operator:write` |
| inline-certificate | `mesh:write` or `operator:write` |
@ -115,10 +116,11 @@ The corresponding CLI command is [`consul config read`](/consul/commands/config/
The ACL required depends on the config entry kind being read:
| Config Entry Kind | Required ACLs |
| ------------------- | -------------------------------- |
| ----------------------- | -------------------------------- |
| api-gateway | `service:read` |
| bound-api-gateway | `service:read` |
| exported-services | `mesh:read` or `operator:read` |
| file-system-certificate | `mesh:read` or `operator:read` |
| http-route | `mesh:read` or `operator:read` |
| ingress-gateway | `service:read` |
| inline-certificate | `mesh:read` or `operator:read` |
@ -193,10 +195,11 @@ The table below shows this endpoint's support for
The ACL required depends on the config entry kind being read:
| Config Entry Kind | Required ACLs |
| ------------------- | -------------------------------- |
| ----------------------- | -------------------------------- |
| api-gateway | `service:read` |
| bound-api-gateway | `service:read` |
| exported-services | `mesh:read` or `operator:read` |
| file-system-certificate | `mesh:read` or `operator:read` |
| http-route | `mesh:read` or `operator:read` |
| ingress-gateway | `service:read` |
| inline-certificate | `mesh:read` or `operator:read` |
@ -277,10 +280,11 @@ The table below shows this endpoint's support for
The ACL required depends on the config entry kind being deleted:
| Config Entry Kind | Required ACLs |
| ------------------- | -------------------------------- |
| ----------------------- | -------------------------------- |
| api-gateway | `mesh:write` or `operator:write` |
| bound-api-gateway | Not writable. |
| exported-services | `mesh:write` or `operator:write` |
| file-system-certificate | `mesh:write` or `operator:write` |
| http-route | `mesh:write` or `operator:write` |
| ingress-gateway | `mesh:write` or `operator:write` |
| inline-certificate | `mesh:write` or `operator:write` |

20
website/content/docs/connect/config-entries/api-gateway.mdx

@ -31,7 +31,7 @@ The following list outlines field hierarchy, language-specific data types, and r
- [`MaxVersion`](#listeners-tls-maxversion): string | no default
- [`CipherSuites`](#listeners-tls-ciphersuites): list of strings | Envoy default cipher suites
- [`Certificates`](#listeners-tls-certificates): list of objects | no default
- [`Kind`](#listeners-tls-certificates-kind): string | must be `"inline-certificate"`
- [`Kind`](#listeners-tls-certificates-kind): string | no default
- [`Name`](#listeners-tls-certificates-name): string | no default
- [`Namespace`](#listeners-tls-certificates-namespace): string | no default <EnterpriseAlert inline />
- [`Partition`](#listeners-tls-certificates-partition): string | no default <EnterpriseAlert inline />
@ -81,8 +81,8 @@ Listeners = [
]
Certificates = [
{
Kind = "inline-certificate"
Name = "<name of inline-certificate>"
Kind = "file-system-certificate"
Name = "<name of file-system-certificate>"
Namespace = "<enterprise: namespace of the certificate>"
Partition = "<enterprise: partition of the certificate>"
}
@ -136,8 +136,8 @@ Listeners = [
],
"Certificates": [
{
"Kind": "inline-certificate",
"Name": "<name of inline-certificate>",
"Kind": "file-system-certificate",
"Name": "<name of file-system-certificate>",
"Namespace": "<enterprise: namespace of the certificate>",
"Partition": "<enterprise: partition of the certificate>"
}
@ -349,7 +349,7 @@ Specifies a list of cipher suites that the listener supports when negotiating co
### `Listeners[].TLS.Certificates[]`
The list of references to inline certificates that the listener uses for TLS termination.
The list of references to file system or inline certificates that the listener uses for TLS termination.
#### Values
@ -362,17 +362,17 @@ The list of references to inline certificates that the listener uses for TLS ter
### `Listeners[].TLS.Certificates[].Kind`
The list of references to inline-certificates that the listener uses for TLS termination.
The list of references to certificates that the listener uses for TLS termination.
#### Values
- Default: None
- This field is required and must be set to `"inline-certificate"`.
- Data type: string
- This field is required.
- The data type is one of the following string values: `"file-system-certificate"` or `"inline-certificate"`.
### `Listeners[].TLS.Certificates[].Name`
The list of references to inline certificates that the listener uses for TLS termination.
Specifies the name of the file system or inline certificate that the listener uses for TLS termination.
#### Values

127
website/content/docs/connect/config-entries/file-system-certificate.mdx

@ -0,0 +1,127 @@
---
layout: docs
page_title: File System Certificate Configuration Reference
description: Learn how to configure a file system certificate bound to an API Gateway on VMs.
---
# File system certificate configuration reference
This topic provides reference information for the gateway file system certificate
configuration entry. For information about certificate configuration for Kubernetes environments, refer to [Gateway Resource Configuration](/consul/docs/connect/gateways/api-gateway/configuration/gateway).
## Configuration model
The following list outlines field hierarchy, language-specific data types, and
requirements in a `file-system-certificate` configuration entry. Click on a property name
to view additional details, including default values.
- [`Kind`](#kind): string | must be `"file-system-certificate"`
- [`Name`](#name): string | no default
- [`Namespace`](#namespace): string | no default <EnterpriseAlert inline />
- [`Partition`](#partition): string | no default <EnterpriseAlert inline />
- [`Meta`](#meta): map | no default
- [`Certificate`](#certificate): string | no default
- [`PrivateKey`](#privatekey): string | no default
## Complete configuration
When every field is defined, a `file-system-certificate` configuration entry has the following form:
<CodeTabs>
```HCL
Kind = "file-system-certificate"
Name = "<name of certificate>"
Meta = {
"<any key>" = "<any value>"
}
Certificate = "<filepath to public certificate>"
PrivateKey = "<filepath to private key>"
```
```JSON
{
"Kind": "file-system-certificate",
"Name": "<name of certificate>",
"Meta": {
"any key": "any value"
}
"Certificate": "<filepath to public certificate>",
"PrivateKey": "<filepath to private key>"
}
```
</CodeTabs>
## Specification
### `Kind`
Specifies the type of configuration entry to implement.
#### Values
- Default: none
- This field is required.
- Data type: string that must equal `"file-system-certificate"`
### `Name`
Specifies a name for the configuration entry. The name is metadata that you can
use to reference the configuration entry when performing Consul operations, such
as applying a configuration entry to a specific cluster.
#### Values
- Default: none
- This field is required.
- Data type: string
### `Namespace` <EnterpriseAlert inline />
Specifies the Enterprise [namespace](/consul/docs/enterprise/namespaces) to apply to the configuration entry.
#### Values
- Default: `"default"` in Enterprise
- Data type: string
### `Partition` <EnterpriseAlert inline />
Specifies the Enterprise [admin partition](/consul/docs/enterprise/admin-partitions) to apply to the configuration entry.
#### Values
- Default: `"default"` in Enterprise
- Data type: string
### `Meta`
Specifies an arbitrary set of key-value pairs to associate with the gateway.
#### Values
- Default: none
- Data type: map containing one or more keys and string values.
### `Certificate`
Specifies the filepath to a public certificate to use for TLS. This filepath must be accessible to the API gateway proxy at runtime.
#### Values
- Default: none
- This field is required.
- Data type: string value of the filepath to a public certificate
### `PrivateKey`
Specifies the filepath to a private key to use for TLS. This filepath must be accessible to the API gateway proxy at runtime.
#### Values
- Default: none
- This field is required.
- Data type: string value of the filepath to a private key

3
website/content/docs/connect/gateways/api-gateway/configuration/index.mdx

@ -18,7 +18,8 @@ Apply the following configuration items if your network runs on virtual machines
| [`api-gateway`](/consul/docs/connect/config-entries/api-gateway) | Defines the main infrastructure resource for declaring an API gateway and listeners on the gateway. | [Deploy API gateway listeners on virtual machines](/consul/docs/connect/gateways/api-gateway/deploy/listeners-vms) |
| [`http-route`](/consul/docs/connect/config-entries/http-route) | Enables HTTP traffic to reach services in the mesh from a listener on the gateway.| <nobr>[Define routes on virtual machines](/consul/docs/connect/gateways/api-gateway/define-routes/routes-vms)</nobr> |
| [`tcp-route`](/consul/docs/connect/config-entries/tcp-route) | Enables TCP traffic to reach services in the mesh from a listener on the gateway.| [Define routes on virtual machines](/consul/docs/connect/gateways/api-gateway/define-routes/routes-vms) |
| <nobr>[`inline-certificate`](/consul/docs/connect/config-entries/inline-certificate)</nobr> | Provides gateway artificats with a CA certificate so that requests between the user and the gateway endpoint are encrypted. | [Encrypt API gateway traffic on virtual machines](/consul/docs/connect/gateways/api-gateway/secure-traffic/encrypt-vms) |
| <nobr>[`file-system-certificate`](/consul/docs/connect/config-entries/file-system-certificate)</nobr> | Provides gateway with a CA certificate so that requests between the user and the gateway endpoint are encrypted. | [Encrypt API gateway traffic on virtual machines](/consul/docs/connect/gateways/api-gateway/secure-traffic/encrypt-vms) |
| <nobr>[`inline-certificate`](/consul/docs/connect/config-entries/inline-certificate)</nobr> | Provides gateway with a CA certificate so that requests between the user and the gateway endpoint are encrypted. | [Encrypt API gateway traffic on virtual machines](/consul/docs/connect/gateways/api-gateway/secure-traffic/encrypt-vms) |
| [`service-intentions`](/consul/docs/connect/config-entries/service-intentions) | Specifies traffic communication rules between services in the mesh. Intentions also enforce rules for service-to-service traffic routed through a Consul API gateway. | General configuration for securing a service mesh |
## Configurations for Kubernetes

4
website/data/docs-nav-data.json

@ -465,6 +465,10 @@
"title": "TCP route",
"path": "connect/config-entries/tcp-route"
},
{
"title": "File system certificate",
"path": "connect/config-entries/file-system-certificate"
},
{
"title": "Inline certificate",
"path": "connect/config-entries/inline-certificate"

Loading…
Cancel
Save