Native API Gateway Config Entries (#15897)

* Stub Config Entries for Consul Native API Gateway (#15644)
* Add empty InlineCertificate struct and protobuf
* apigateway stubs
* Stub HTTPRoute in api pkg
* Stub HTTPRoute in structs pkg
* Simplify api.APIGatewayConfigEntry to be consistent w/ other entries
* Update makeConfigEntry switch, add docstring for HTTPRouteConfigEntry
* Add TCPRoute to MakeConfigEntry, return unique Kind
* Stub BoundAPIGatewayConfigEntry in agent
* Add RaftIndex to APIGatewayConfigEntry stub
* Add new config entry kinds to validation allow-list
* Add RaftIndex to other added config entry stubs
* Update usage metrics assertions to include new cfg entries
* Add Meta and acl.EnterpriseMeta to all new ConfigEntry types
* Remove unnecessary Services field from added config entry types
* Implement GetMeta(), GetEnterpriseMeta() for added config entry types
* Add meta field to proto, name consistently w/ existing config entries
* Format config_entry.proto
* Add initial implementation of CanRead + CanWrite for new config entry types
* Add unit tests for decoding of new config entry types
* Add unit tests for parsing of new config entry types
* Add unit tests for API Gateway config entry ACLs
* Return typed PermissionDeniedError on BoundAPIGateway CanWrite
* Add unit tests for added config entry ACLs
* Add BoundAPIGateway type to AllConfigEntryKinds
* Return proper kind from BoundAPIGateway
* Add docstrings for new config entry types
* Add missing config entry kinds to proto def
* Update usagemetrics_oss_test.go
* Use utility func for returning PermissionDeniedError
* EventPublisher subscriptions for Consul Native API Gateway (#15757)
* Create new event topics in subscribe proto
* Add tests for PBSubscribe func
* Make configs singular, add all configs to PBToStreamSubscribeRequest
* Add snapshot methods
* Add config_entry_events tests
* Add config entry kind to topic for new configs
* Add unit tests for snapshot methods
* Start adding integration test
* Test using the new controller code
* Update agent/consul/state/config_entry_events.go
* Check value of error
* Add controller stubs for API Gateway (#15837)
* update initial stub implementation
* move files, clean up mutex references
* Remove embed, use idiomatic names for constructors
* Remove stray file introduced in merge
* Add APIGateway validation (#15847)
* Add APIGateway validation
* Add additional validations
* Add cert ref validation
* Add protobuf definitions
* Fix up field types
* Add API structs
* Move struct fields around a bit
* APIGateway InlineCertificate validation (#15856)
* Add APIGateway validation
* Add additional validations
* Add protobuf definitions
* Tabs to spaces
* Add API structs
* Move struct fields around a bit
* Add validation for InlineCertificate
* Fix ACL test
* APIGateway BoundAPIGateway validation (#15858)
* Add APIGateway validation
* Add additional validations
* Add cert ref validation
* Add protobuf definitions
* Fix up field types
* Add API structs
* Move struct fields around a bit
* Add validation for BoundAPIGateway
* APIGateway TCPRoute validation (#15855)
* Add APIGateway validation
* Add additional validations
* Add cert ref validation
* Add protobuf definitions
* Fix up field types
* Add API structs
* Add TCPRoute normalization and validation
* Add forgotten Status
* Add some more field docs in api package
* Fix test
* Format imports
* Rename snapshot test variable names
* Add plumbing for Native API GW Subscriptions (#16003)

Co-authored-by: Sarah Alsmiller <sarah.alsmiller@hashicorp.com>
Co-authored-by: Nathan Coleman <nathan.coleman@hashicorp.com>
Co-authored-by: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com>
Co-authored-by: Andrew Stucki <andrew.stucki@hashicorp.com>
This commit is contained in:
Thomas Eckert 2023-01-18 17:14:34 -05:00 committed by GitHub
parent 4e154144a6
commit 13da1a5285
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 5489 additions and 230 deletions

View File

@ -70,7 +70,7 @@ type PermissionDeniedError struct {
Accessor string
// Resource (e.g. Service)
Resource Resource
// Access leve (e.g. Read)
// Access level (e.g. Read)
AccessLevel AccessLevel
// e.g. "sidecar-proxy-1"
ResourceID ResourceDescriptor

View File

@ -64,7 +64,7 @@ LOOP:
}
}
// since we only modified each entry once, we should have exactly 200 reconcliation calls
// since we only modified each entry once, we should have exactly 200 reconciliation calls
require.Len(t, received, 200)
for i := 0; i < 200; i++ {
require.Contains(t, received, fmt.Sprintf("foo-%d", i))
@ -271,3 +271,147 @@ func TestBasicController_RunPanicAssertions(t *testing.T) {
controller.WithQueueFactory(RunWorkQueue)
})
}
func TestConfigEntrySubscriptions(t *testing.T) {
t.Parallel()
cases := map[string]struct {
configEntry func(string) structs.ConfigEntry
topic stream.Topic
kind string
}{
"Subscribe to Service Resolver Config Changes": {
configEntry: func(name string) structs.ConfigEntry {
return &structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: name,
}
},
topic: state.EventTopicServiceResolver,
kind: structs.ServiceResolver,
},
"Subscribe to Ingress Gateway Changes": {
configEntry: func(name string) structs.ConfigEntry {
return &structs.IngressGatewayConfigEntry{
Kind: structs.IngressGateway,
Name: name,
}
},
topic: state.EventTopicIngressGateway,
kind: structs.IngressGateway,
},
"Subscribe to Service Intentions Changes": {
configEntry: func(name string) structs.ConfigEntry {
return &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: name,
}
},
topic: state.EventTopicServiceIntentions,
kind: structs.ServiceIntentions,
},
"Subscribe to API Gateway Changes": {
configEntry: func(name string) structs.ConfigEntry {
return &structs.APIGatewayConfigEntry{
Kind: structs.APIGateway,
Name: name,
}
},
topic: state.EventTopicAPIGateway,
kind: structs.APIGateway,
},
"Subscribe to Inline Certificate Changes": {
configEntry: func(name string) structs.ConfigEntry {
return &structs.InlineCertificateConfigEntry{
Kind: structs.InlineCertificate,
Name: name,
}
},
topic: state.EventTopicInlineCertificate,
kind: structs.InlineCertificate,
},
"Subscribe to HTTP Route Changes": {
configEntry: func(name string) structs.ConfigEntry {
return &structs.HTTPRouteConfigEntry{
Kind: structs.HTTPRoute,
Name: name,
}
},
topic: state.EventTopicHTTPRoute,
kind: structs.HTTPRoute,
},
"Subscribe to TCP Route Changes": {
configEntry: func(name string) structs.ConfigEntry {
return &structs.TCPRouteConfigEntry{
Kind: structs.TCPRoute,
Name: name,
}
},
topic: state.EventTopicTCPRoute,
kind: structs.TCPRoute,
},
"Subscribe to Bound API Gateway Changes": {
configEntry: func(name string) structs.ConfigEntry {
return &structs.BoundAPIGatewayConfigEntry{
Kind: structs.BoundAPIGateway,
Name: name,
}
},
topic: state.EventTopicBoundAPIGateway,
kind: structs.BoundAPIGateway,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
reconciler := newTestReconciler(false)
publisher := stream.NewEventPublisher(1 * time.Millisecond)
go publisher.Run(ctx)
// get the store through the FSM since the publisher handlers get registered through it
store := fsm.NewFromDeps(fsm.Deps{
Logger: hclog.New(nil),
NewStateStore: func() *state.Store {
return state.NewStateStoreWithEventPublisher(nil, publisher)
},
Publisher: publisher,
}).State()
for i := 0; i < 200; i++ {
entryIndex := uint64(i + 1)
name := fmt.Sprintf("foo-%d", i)
require.NoError(t, store.EnsureConfigEntry(entryIndex, tc.configEntry(name)))
}
go New(publisher, reconciler).Subscribe(&stream.SubscribeRequest{
Topic: tc.topic,
Subject: stream.SubjectWildcard,
}).WithWorkers(10).Run(ctx)
received := []string{}
LOOP:
for {
select {
case request := <-reconciler.received:
require.Equal(t, tc.kind, request.Kind)
received = append(received, request.Name)
if len(received) == 200 {
break LOOP
}
case <-ctx.Done():
break LOOP
}
}
// since we only modified each entry once, we should have exactly 200 reconciliation calls
require.Len(t, received, 200)
for i := 0; i < 200; i++ {
require.Contains(t, received, fmt.Sprintf("foo-%d", i))
}
})
}
}

View File

@ -342,4 +342,39 @@ func (c *FSM) registerStreamSnapshotHandlers() {
if err != nil {
panic(fmt.Errorf("fatal error encountered registering streaming snapshot handlers: %w", err))
}
err = c.deps.Publisher.RegisterHandler(state.EventTopicAPIGateway, func(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return c.State().APIGatewaySnapshot(req, buf)
}, true)
if err != nil {
panic(fmt.Errorf("fatal error encountered registering streaming snapshot handlers: %w", err))
}
err = c.deps.Publisher.RegisterHandler(state.EventTopicInlineCertificate, func(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return c.State().InlineCertificateSnapshot(req, buf)
}, true)
if err != nil {
panic(fmt.Errorf("fatal error encountered registering streaming snapshot handlers: %w", err))
}
err = c.deps.Publisher.RegisterHandler(state.EventTopicHTTPRoute, func(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return c.State().HTTPRouteSnapshot(req, buf)
}, true)
if err != nil {
panic(fmt.Errorf("fatal error encountered registering streaming snapshot handlers: %w", err))
}
err = c.deps.Publisher.RegisterHandler(state.EventTopicTCPRoute, func(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return c.State().TCPRouteSnapshot(req, buf)
}, true)
if err != nil {
panic(fmt.Errorf("fatal error encountered registering streaming snapshot handlers: %w", err))
}
err = c.deps.Publisher.RegisterHandler(state.EventTopicBoundAPIGateway, func(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return c.State().BoundAPIGatewaySnapshot(req, buf)
}, true)
if err != nil {
panic(fmt.Errorf("fatal error encountered registering streaming snapshot handlers: %w", err))
}
}

View File

@ -0,0 +1,34 @@
package gateways
import (
"context"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/consul/agent/consul/controller"
"github.com/hashicorp/consul/agent/consul/fsm"
"github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/agent/consul/stream"
)
type apiGatewayReconciler struct {
fsm *fsm.FSM
logger hclog.Logger
}
func (r apiGatewayReconciler) Reconcile(ctx context.Context, req controller.Request) error {
return nil
}
func NewAPIGatewayController(fsm *fsm.FSM, publisher state.EventPublisher, logger hclog.Logger) controller.Controller {
reconciler := apiGatewayReconciler{
fsm: fsm,
logger: logger,
}
return controller.New(publisher, reconciler).Subscribe(
&stream.SubscribeRequest{
Topic: state.EventTopicAPIGateway,
Subject: stream.SubjectWildcard,
},
)
}

View File

@ -0,0 +1,56 @@
package gateways
import (
"context"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/consul/agent/consul/controller"
"github.com/hashicorp/consul/agent/consul/fsm"
"github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/agent/consul/stream"
)
type tcpRouteReconciler struct {
fsm *fsm.FSM
logger hclog.Logger
}
func (r tcpRouteReconciler) Reconcile(ctx context.Context, req controller.Request) error {
return nil
}
func NewTCPRouteController(fsm *fsm.FSM, publisher state.EventPublisher, logger hclog.Logger) controller.Controller {
reconciler := tcpRouteReconciler{
fsm: fsm,
logger: logger,
}
return controller.New(publisher, reconciler).Subscribe(
&stream.SubscribeRequest{
Topic: state.EventTopicTCPRoute,
Subject: stream.SubjectWildcard,
},
)
}
type httpRouteReconciler struct {
fsm *fsm.FSM
logger hclog.Logger
}
func (r httpRouteReconciler) Reconcile(ctx context.Context, req controller.Request) error {
return nil
}
func NewHTTPRouteController(fsm *fsm.FSM, publisher state.EventPublisher, logger hclog.Logger) controller.Controller {
reconciler := httpRouteReconciler{
fsm: fsm,
logger: logger,
}
return controller.New(publisher, reconciler).Subscribe(
&stream.SubscribeRequest{
Topic: state.EventTopicHTTPRoute,
Subject: stream.SubjectWildcard,
},
)
}

View File

@ -496,6 +496,11 @@ func validateProposedConfigEntryInGraph(
case structs.ServiceIntentions:
case structs.MeshConfig:
case structs.ExportedServices:
case structs.APIGateway: // TODO Consider checkGatewayClash
case structs.BoundAPIGateway:
case structs.InlineCertificate:
case structs.HTTPRoute:
case structs.TCPRoute:
default:
return fmt.Errorf("unhandled kind %q during validation of %q", kindName.Kind, kindName.Name)
}

View File

@ -17,6 +17,11 @@ var configEntryKindToTopic = map[string]stream.Topic{
structs.IngressGateway: EventTopicIngressGateway,
structs.ServiceIntentions: EventTopicServiceIntentions,
structs.ServiceDefaults: EventTopicServiceDefaults,
structs.APIGateway: EventTopicAPIGateway,
structs.TCPRoute: EventTopicTCPRoute,
structs.HTTPRoute: EventTopicHTTPRoute,
structs.InlineCertificate: EventTopicInlineCertificate,
structs.BoundAPIGateway: EventTopicBoundAPIGateway,
}
// EventSubjectConfigEntry is a stream.Subject used to route and receive events
@ -117,6 +122,36 @@ func (s *Store) ServiceDefaultsSnapshot(req stream.SubscribeRequest, buf stream.
return s.configEntrySnapshot(structs.ServiceDefaults, req, buf)
}
// APIGatewaySnapshot is a stream.SnapshotFunc that returns a snapshot of
// api-gateway config entries.
func (s *Store) APIGatewaySnapshot(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return s.configEntrySnapshot(structs.APIGateway, req, buf)
}
// TCPRouteSnapshot is a stream.SnapshotFunc that returns a snapshot of
// tcp-route config entries.
func (s *Store) TCPRouteSnapshot(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return s.configEntrySnapshot(structs.TCPRoute, req, buf)
}
// HTTPRouteSnapshot is a stream.SnapshotFunc that retuns a snapshot of
// http-route config entries.
func (s *Store) HTTPRouteSnapshot(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return s.configEntrySnapshot(structs.HTTPRoute, 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) {
return s.configEntrySnapshot(structs.InlineCertificate, req, buf)
}
// BoundAPIGatewaySnapshot is a stream.SnapshotFunc that returns a snapshot of
// bound-api-gateway config entries.
func (s *Store) BoundAPIGatewaySnapshot(req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
return s.configEntrySnapshot(structs.BoundAPIGateway, req, buf)
}
func (s *Store) configEntrySnapshot(kind string, req stream.SubscribeRequest, buf stream.SnapshotAppender) (uint64, error) {
var (
idx uint64

View File

@ -547,3 +547,348 @@ func TestServiceDefaultsSnapshot(t *testing.T) {
})
}
}
func TestAPIGatewaySnapshot(t *testing.T) {
const index uint64 = 123
gw1 := &structs.APIGatewayConfigEntry{
Kind: structs.APIGateway,
Name: "agw1",
}
gw2 := &structs.APIGatewayConfigEntry{
Kind: structs.APIGateway,
Name: "agw2",
}
store := testStateStore(t)
require.NoError(t, store.EnsureConfigEntry(index, gw1))
require.NoError(t, store.EnsureConfigEntry(index, gw2))
testCases := map[string]struct {
subject stream.Subject
events []stream.Event
}{
"named entry": {
subject: EventSubjectConfigEntry{Name: gw1.Name},
events: []stream.Event{
{
Topic: EventTopicAPIGateway,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: gw1,
},
},
},
},
"wildcard": {
subject: stream.SubjectWildcard,
events: []stream.Event{
{
Topic: EventTopicAPIGateway,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: gw1,
},
},
{
Topic: EventTopicAPIGateway,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: gw2,
},
},
},
},
}
for desc, tc := range testCases {
t.Run(desc, func(t *testing.T) {
buf := &snapshotAppender{}
idx, err := store.APIGatewaySnapshot(stream.SubscribeRequest{Subject: tc.subject}, buf)
require.NoError(t, err)
require.Equal(t, index, idx)
require.Len(t, buf.events, 1)
require.ElementsMatch(t, tc.events, buf.events[0])
})
}
}
func TestTCPRouteSnapshot(t *testing.T) {
const index uint64 = 123
rt1 := &structs.TCPRouteConfigEntry{
Kind: structs.TCPRoute,
Name: "tcprt1",
}
rt2 := &structs.TCPRouteConfigEntry{
Kind: structs.TCPRoute,
Name: "tcprt2",
}
store := testStateStore(t)
require.NoError(t, store.EnsureConfigEntry(index, rt1))
require.NoError(t, store.EnsureConfigEntry(index, rt2))
testCases := map[string]struct {
subject stream.Subject
events []stream.Event
}{
"named entry": {
subject: EventSubjectConfigEntry{Name: rt1.Name},
events: []stream.Event{
{
Topic: EventTopicTCPRoute,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: rt1,
},
},
},
},
"wildcard": {
subject: stream.SubjectWildcard,
events: []stream.Event{
{
Topic: EventTopicTCPRoute,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: rt1,
},
},
{
Topic: EventTopicTCPRoute,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: rt2,
},
},
},
},
}
for desc, tc := range testCases {
t.Run(desc, func(t *testing.T) {
buf := &snapshotAppender{}
idx, err := store.TCPRouteSnapshot(stream.SubscribeRequest{Subject: tc.subject}, buf)
require.NoError(t, err)
require.Equal(t, index, idx)
require.Len(t, buf.events, 1)
require.ElementsMatch(t, tc.events, buf.events[0])
})
}
}
func TestHTTPRouteSnapshot(t *testing.T) {
const index uint64 = 123
rt1 := &structs.HTTPRouteConfigEntry{
Kind: structs.HTTPRoute,
Name: "httprt1",
}
gw2 := &structs.HTTPRouteConfigEntry{
Kind: structs.HTTPRoute,
Name: "httprt2",
}
store := testStateStore(t)
require.NoError(t, store.EnsureConfigEntry(index, rt1))
require.NoError(t, store.EnsureConfigEntry(index, gw2))
testCases := map[string]struct {
subject stream.Subject
events []stream.Event
}{
"named entry": {
subject: EventSubjectConfigEntry{Name: rt1.Name},
events: []stream.Event{
{
Topic: EventTopicHTTPRoute,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: rt1,
},
},
},
},
"wildcard": {
subject: stream.SubjectWildcard,
events: []stream.Event{
{
Topic: EventTopicHTTPRoute,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: rt1,
},
},
{
Topic: EventTopicHTTPRoute,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: gw2,
},
},
},
},
}
for desc, tc := range testCases {
t.Run(desc, func(t *testing.T) {
buf := &snapshotAppender{}
idx, err := store.HTTPRouteSnapshot(stream.SubscribeRequest{Subject: tc.subject}, buf)
require.NoError(t, err)
require.Equal(t, index, idx)
require.Len(t, buf.events, 1)
require.ElementsMatch(t, tc.events, buf.events[0])
})
}
}
func TestInlineCertificateSnapshot(t *testing.T) {
const index uint64 = 123
crt1 := &structs.InlineCertificateConfigEntry{
Kind: structs.InlineCertificate,
Name: "inlinecert1",
}
crt2 := &structs.InlineCertificateConfigEntry{
Kind: structs.InlineCertificate,
Name: "inlinecert2",
}
store := testStateStore(t)
require.NoError(t, store.EnsureConfigEntry(index, crt1))
require.NoError(t, store.EnsureConfigEntry(index, crt2))
testCases := map[string]struct {
subject stream.Subject
events []stream.Event
}{
"named entry": {
subject: EventSubjectConfigEntry{Name: crt1.Name},
events: []stream.Event{
{
Topic: EventTopicInlineCertificate,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: crt1,
},
},
},
},
"wildcard": {
subject: stream.SubjectWildcard,
events: []stream.Event{
{
Topic: EventTopicInlineCertificate,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: crt1,
},
},
{
Topic: EventTopicInlineCertificate,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: crt2,
},
},
},
},
}
for desc, tc := range testCases {
t.Run(desc, func(t *testing.T) {
buf := &snapshotAppender{}
idx, err := store.InlineCertificateSnapshot(stream.SubscribeRequest{Subject: tc.subject}, buf)
require.NoError(t, err)
require.Equal(t, index, idx)
require.Len(t, buf.events, 1)
require.ElementsMatch(t, tc.events, buf.events[0])
})
}
}
func TestBoundAPIGatewaySnapshot(t *testing.T) {
const index uint64 = 123
gw1 := &structs.BoundAPIGatewayConfigEntry{
Kind: structs.BoundAPIGateway,
Name: "boundapigw1",
}
gw2 := &structs.BoundAPIGatewayConfigEntry{
Kind: structs.BoundAPIGateway,
Name: "boundapigw2",
}
store := testStateStore(t)
require.NoError(t, store.EnsureConfigEntry(index, gw1))
require.NoError(t, store.EnsureConfigEntry(index, gw2))
testCases := map[string]struct {
subject stream.Subject
events []stream.Event
}{
"named entry": {
subject: EventSubjectConfigEntry{Name: gw1.Name},
events: []stream.Event{
{
Topic: EventTopicBoundAPIGateway,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: gw1,
},
},
},
},
"wildcard": {
subject: stream.SubjectWildcard,
events: []stream.Event{
{
Topic: EventTopicBoundAPIGateway,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: gw1,
},
},
{
Topic: EventTopicBoundAPIGateway,
Index: index,
Payload: EventPayloadConfigEntry{
Op: pbsubscribe.ConfigEntryUpdate_Upsert,
Value: gw2,
},
},
},
},
}
for desc, tc := range testCases {
t.Run(desc, func(t *testing.T) {
buf := &snapshotAppender{}
idx, err := store.BoundAPIGatewaySnapshot(stream.SubscribeRequest{Subject: tc.subject}, buf)
require.NoError(t, err)
require.Equal(t, index, idx)
require.Len(t, buf.events, 1)
require.ElementsMatch(t, tc.events, buf.events[0])
})
}
}

View File

@ -9,6 +9,8 @@ import (
"github.com/hashicorp/consul/proto/pbsubscribe"
)
// PBToStreamSubscribeRequest takes a protobuf subscribe request and enterprise
// metadata to properly generate the matching stream subscribe request.
func PBToStreamSubscribeRequest(req *pbsubscribe.SubscribeRequest, entMeta acl.EnterpriseMeta) (*stream.SubscribeRequest, error) {
var subject stream.Subject
@ -17,7 +19,7 @@ func PBToStreamSubscribeRequest(req *pbsubscribe.SubscribeRequest, entMeta acl.E
} else {
named := req.GetNamedSubject()
// Support the (deprcated) top-level Key, Partition, Namespace, and PeerName fields.
// Support the (deprecated) top-level Key, Partition, Namespace, and PeerName fields.
if named == nil {
named = &pbsubscribe.NamedSubject{
Key: req.Key, // nolint:staticcheck // SA1019 intentional use of deprecated field
@ -38,7 +40,10 @@ func PBToStreamSubscribeRequest(req *pbsubscribe.SubscribeRequest, entMeta acl.E
EnterpriseMeta: entMeta,
PeerName: named.PeerName,
}
case EventTopicMeshConfig, EventTopicServiceResolver, EventTopicIngressGateway, EventTopicServiceIntentions, EventTopicServiceDefaults:
case EventTopicMeshConfig, EventTopicServiceResolver, EventTopicIngressGateway,
EventTopicServiceIntentions, EventTopicServiceDefaults, EventTopicAPIGateway,
EventTopicTCPRoute, EventTopicHTTPRoute, EventTopicInlineCertificate,
EventTopicBoundAPIGateway:
subject = EventSubjectConfigEntry{
Name: named.Key,
EnterpriseMeta: &entMeta,

View File

@ -0,0 +1,157 @@
package state
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/consul/stream"
"github.com/hashicorp/consul/proto/pbsubscribe"
)
const aclToken = "67b04fbc-e35f-494a-ad43-739f1c8b839c"
func TestPBToStreamSubscribeRequest(t *testing.T) {
cases := map[string]struct {
req *pbsubscribe.SubscribeRequest
entMeta acl.EnterpriseMeta
expectedSubscribeRequest *stream.SubscribeRequest
err error
}{
"Wildcard subject": {
req: &pbsubscribe.SubscribeRequest{
Topic: EventTopicServiceList,
Subject: &pbsubscribe.SubscribeRequest_WildcardSubject{WildcardSubject: true},
Token: aclToken,
Index: 1,
},
entMeta: acl.EnterpriseMeta{},
expectedSubscribeRequest: &stream.SubscribeRequest{
Topic: EventTopicServiceList,
Subject: stream.SubjectWildcard,
Token: aclToken,
Index: 1,
},
err: nil,
},
"Deprecated top level fields": {
req: &pbsubscribe.SubscribeRequest{
Topic: EventTopicServiceDefaults,
Key: "key",
Partition: "partition",
Namespace: "consul",
PeerName: "peer",
},
entMeta: acl.EnterpriseMeta{},
expectedSubscribeRequest: &stream.SubscribeRequest{
Topic: EventTopicServiceDefaults,
Subject: EventSubjectConfigEntry{
Name: "key",
EnterpriseMeta: &acl.EnterpriseMeta{},
},
},
err: nil,
},
"Service health": {
req: &pbsubscribe.SubscribeRequest{
Topic: EventTopicServiceHealth,
Subject: &pbsubscribe.SubscribeRequest_NamedSubject{
NamedSubject: &pbsubscribe.NamedSubject{
Key: "key",
Namespace: "consul",
Partition: "partition",
PeerName: "peer",
},
},
Token: aclToken,
Index: 2,
},
entMeta: acl.EnterpriseMeta{},
expectedSubscribeRequest: &stream.SubscribeRequest{
Topic: EventTopicServiceHealth,
Subject: EventSubjectService{
Key: "key",
EnterpriseMeta: acl.EnterpriseMeta{},
PeerName: "peer",
},
Token: aclToken,
Index: 2,
},
err: nil,
},
"Config": {
req: &pbsubscribe.SubscribeRequest{
Topic: EventTopicAPIGateway,
Subject: &pbsubscribe.SubscribeRequest_NamedSubject{
NamedSubject: &pbsubscribe.NamedSubject{
Key: "key",
Namespace: "consul",
Partition: "partition",
PeerName: "peer",
},
},
Token: aclToken,
Index: 2,
},
entMeta: acl.EnterpriseMeta{},
expectedSubscribeRequest: &stream.SubscribeRequest{
Topic: EventTopicAPIGateway,
Subject: EventSubjectConfigEntry{
Name: "key",
EnterpriseMeta: &acl.EnterpriseMeta{},
},
Token: aclToken,
Index: 2,
},
err: nil,
},
"Service list without wildcard returns error": {
req: &pbsubscribe.SubscribeRequest{
Topic: EventTopicServiceList,
Subject: &pbsubscribe.SubscribeRequest_NamedSubject{
NamedSubject: &pbsubscribe.NamedSubject{
Key: "key",
Namespace: "consul",
Partition: "partition",
PeerName: "peer",
},
},
},
entMeta: acl.EnterpriseMeta{},
expectedSubscribeRequest: nil,
err: fmt.Errorf("topic %s can only be consumed using WildcardSubject", EventTopicServiceList),
},
"Unrecognized topic returns error": {
req: &pbsubscribe.SubscribeRequest{
Topic: 99999,
Subject: &pbsubscribe.SubscribeRequest_NamedSubject{
NamedSubject: &pbsubscribe.NamedSubject{
Key: "key",
Namespace: "consul",
Partition: "partition",
PeerName: "peer",
},
},
},
entMeta: acl.EnterpriseMeta{},
expectedSubscribeRequest: nil,
err: fmt.Errorf("cannot construct subject for topic 99999"),
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
actual, err := PBToStreamSubscribeRequest(tc.req, tc.entMeta)
if tc.err != nil {
require.EqualError(t, err, tc.err.Error())
} else {
require.NoError(t, err)
}
require.Equal(t, tc.expectedSubscribeRequest, actual)
})
}
}

View File

@ -186,6 +186,11 @@ var (
EventTopicServiceIntentions = pbsubscribe.Topic_ServiceIntentions
EventTopicServiceDefaults = pbsubscribe.Topic_ServiceDefaults
EventTopicServiceList = pbsubscribe.Topic_ServiceList
EventTopicAPIGateway = pbsubscribe.Topic_APIGateway
EventTopicTCPRoute = pbsubscribe.Topic_TCPRoute
EventTopicHTTPRoute = pbsubscribe.Topic_HTTPRoute
EventTopicInlineCertificate = pbsubscribe.Topic_InlineCertificate
EventTopicBoundAPIGateway = pbsubscribe.Topic_BoundAPIGateway
)
func processDBChanges(tx ReadTxn, changes Changes) ([]stream.Event, error) {

View File

@ -350,6 +350,86 @@ var baseCases = map[string]testCase{
{Name: "kind", Value: "exported-services"},
},
},
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=api-gateway": { // Legacy
Name: "consul.usage.test.consul.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "api-gateway"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=api-gateway": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "api-gateway"},
},
},
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=bound-api-gateway": { // Legacy
Name: "consul.usage.test.consul.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "bound-api-gateway"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=bound-api-gateway": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "bound-api-gateway"},
},
},
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=inline-certificate": { // Legacy
Name: "consul.usage.test.consul.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "inline-certificate"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=inline-certificate": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "inline-certificate"},
},
},
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=http-route": { // Legacy
Name: "consul.usage.test.consul.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "http-route"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=http-route": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "http-route"},
},
},
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=tcp-route": { // Legacy
Name: "consul.usage.test.consul.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "tcp-route"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=tcp-route": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "tcp-route"},
},
},
},
getMembersFunc: func() []serf.Member { return []serf.Member{} },
},
@ -690,6 +770,86 @@ var baseCases = map[string]testCase{
{Name: "kind", Value: "exported-services"},
},
},
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=api-gateway": { // Legacy
Name: "consul.usage.test.consul.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "api-gateway"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=api-gateway": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "api-gateway"},
},
},
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=bound-api-gateway": { // Legacy
Name: "consul.usage.test.consul.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "bound-api-gateway"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=bound-api-gateway": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "bound-api-gateway"},
},
},
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=inline-certificate": { // Legacy
Name: "consul.usage.test.consul.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "inline-certificate"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=inline-certificate": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "inline-certificate"},
},
},
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=http-route": { // Legacy
Name: "consul.usage.test.consul.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "http-route"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=http-route": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "http-route"},
},
},
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=tcp-route": { // Legacy
Name: "consul.usage.test.consul.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "tcp-route"},
},
},
"consul.usage.test.state.config_entries;datacenter=dc1;kind=tcp-route": {
Name: "consul.usage.test.state.config_entries",
Value: 0,
Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "tcp-route"},
},
},
},
},
}

View File

@ -62,6 +62,16 @@ func newConfigEntryRequest(req *structs.ConfigEntryQuery, deps ServerDataSourceD
topic = pbsubscribe.Topic_IngressGateway
case structs.ServiceDefaults:
topic = pbsubscribe.Topic_ServiceDefaults
case structs.APIGateway:
topic = pbsubscribe.Topic_APIGateway
case structs.HTTPRoute:
topic = pbsubscribe.Topic_HTTPRoute
case structs.TCPRoute:
topic = pbsubscribe.Topic_TCPRoute
case structs.InlineCertificate:
topic = pbsubscribe.Topic_InlineCertificate
case structs.BoundAPIGateway:
topic = pbsubscribe.Topic_BoundAPIGateway
default:
return nil, fmt.Errorf("cannot map config entry kind: %s to a topic", req.Kind)
}

View File

@ -34,6 +34,11 @@ const (
ServiceIntentions string = "service-intentions"
MeshConfig string = "mesh"
ExportedServices string = "exported-services"
APIGateway string = "api-gateway"
BoundAPIGateway string = "bound-api-gateway"
InlineCertificate string = "inline-certificate"
HTTPRoute string = "http-route"
TCPRoute string = "tcp-route"
ProxyConfigGlobal string = "global"
MeshConfigMesh string = "mesh"
@ -54,6 +59,11 @@ var AllConfigEntryKinds = []string{
ServiceIntentions,
MeshConfig,
ExportedServices,
APIGateway,
BoundAPIGateway,
HTTPRoute,
TCPRoute,
InlineCertificate,
}
const (
@ -698,6 +708,16 @@ func MakeConfigEntry(kind, name string) (ConfigEntry, error) {
return &MeshConfigEntry{}, nil
case ExportedServices:
return &ExportedServicesConfigEntry{Name: name}, nil
case APIGateway:
return &APIGatewayConfigEntry{Name: name}, nil
case BoundAPIGateway:
return &BoundAPIGatewayConfigEntry{Name: name}, nil
case InlineCertificate:
return &InlineCertificateConfigEntry{Name: name}, nil
case HTTPRoute:
return &HTTPRouteConfigEntry{Name: name}, nil
case TCPRoute:
return &TCPRouteConfigEntry{Name: name}, nil
default:
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
}

View File

@ -409,6 +409,636 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
},
},
},
{
name: "api-gateway",
entry: &APIGatewayConfigEntry{Name: "test"},
expectACLs: []testACL{
{
name: "no-authz",
authorizer: newAuthz(t, ``),
canRead: false,
canWrite: false,
},
{
name: "service deny and operator deny",
authorizer: newServiceAndOperatorACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator deny",
authorizer: newServiceAndOperatorACL(t, "read", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator deny",
authorizer: newServiceAndOperatorACL(t, "write", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service deny and mesh deny",
authorizer: newServiceAndMeshACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh deny",
authorizer: newServiceAndMeshACL(t, "read", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh deny",
authorizer: newServiceAndMeshACL(t, "write", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service deny and operator read",
authorizer: newServiceAndOperatorACL(t, "deny", "read"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator read",
authorizer: newServiceAndOperatorACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator read",
authorizer: newServiceAndOperatorACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and operator write",
authorizer: newServiceAndOperatorACL(t, "deny", "write"),
canRead: false,
canWrite: true,
},
{
name: "service read and operator write",
authorizer: newServiceAndOperatorACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and operator write",
authorizer: newServiceAndOperatorACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
{
name: "service deny and mesh read",
authorizer: newServiceAndMeshACL(t, "deny", "read"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh read",
authorizer: newServiceAndMeshACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh read",
authorizer: newServiceAndMeshACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and mesh write",
authorizer: newServiceAndMeshACL(t, "deny", "write"),
canRead: false,
canWrite: true,
},
{
name: "service read and mesh write",
authorizer: newServiceAndMeshACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and mesh write",
authorizer: newServiceAndMeshACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
},
},
{
name: "inline-certificate",
entry: &InlineCertificateConfigEntry{Name: "test", Certificate: validCertificate, PrivateKey: validPrivateKey},
expectACLs: []testACL{
{
name: "no-authz",
authorizer: newAuthz(t, ``),
canRead: false,
canWrite: false,
},
{
name: "service deny and operator deny",
authorizer: newServiceAndOperatorACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator deny",
authorizer: newServiceAndOperatorACL(t, "read", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service write and operator deny",
authorizer: newServiceAndOperatorACL(t, "write", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service deny and mesh deny",
authorizer: newServiceAndMeshACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh deny",
authorizer: newServiceAndMeshACL(t, "read", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service write and mesh deny",
authorizer: newServiceAndMeshACL(t, "write", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service deny and operator read",
authorizer: newServiceAndOperatorACL(t, "deny", "read"),
canRead: true,
canWrite: false,
},
{
name: "service read and operator read",
authorizer: newServiceAndOperatorACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator read",
authorizer: newServiceAndOperatorACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and operator write",
authorizer: newServiceAndOperatorACL(t, "deny", "write"),
canRead: true,
canWrite: true,
},
{
name: "service read and operator write",
authorizer: newServiceAndOperatorACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and operator write",
authorizer: newServiceAndOperatorACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
{
name: "service deny and mesh read",
authorizer: newServiceAndMeshACL(t, "deny", "read"),
canRead: true,
canWrite: false,
},
{
name: "service read and mesh read",
authorizer: newServiceAndMeshACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh read",
authorizer: newServiceAndMeshACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and mesh write",
authorizer: newServiceAndMeshACL(t, "deny", "write"),
canRead: true,
canWrite: true,
},
{
name: "service read and mesh write",
authorizer: newServiceAndMeshACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and mesh write",
authorizer: newServiceAndMeshACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
},
},
{
name: "http-route",
entry: &HTTPRouteConfigEntry{Name: "test"},
expectACLs: []testACL{
{
name: "no-authz",
authorizer: newAuthz(t, ``),
canRead: false,
canWrite: false,
},
{
name: "service deny and operator deny",
authorizer: newServiceAndOperatorACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator deny",
authorizer: newServiceAndOperatorACL(t, "read", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service write and operator deny",
authorizer: newServiceAndOperatorACL(t, "write", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service deny and mesh deny",
authorizer: newServiceAndMeshACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh deny",
authorizer: newServiceAndMeshACL(t, "read", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service write and mesh deny",
authorizer: newServiceAndMeshACL(t, "write", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service deny and operator read",
authorizer: newServiceAndOperatorACL(t, "deny", "read"),
canRead: true,
canWrite: false,
},
{
name: "service read and operator read",
authorizer: newServiceAndOperatorACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator read",
authorizer: newServiceAndOperatorACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and operator write",
authorizer: newServiceAndOperatorACL(t, "deny", "write"),
canRead: true,
canWrite: true,
},
{
name: "service read and operator write",
authorizer: newServiceAndOperatorACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and operator write",
authorizer: newServiceAndOperatorACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
{
name: "service deny and mesh read",
authorizer: newServiceAndMeshACL(t, "deny", "read"),
canRead: true,
canWrite: false,
},
{
name: "service read and mesh read",
authorizer: newServiceAndMeshACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh read",
authorizer: newServiceAndMeshACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and mesh write",
authorizer: newServiceAndMeshACL(t, "deny", "write"),
canRead: true,
canWrite: true,
},
{
name: "service read and mesh write",
authorizer: newServiceAndMeshACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and mesh write",
authorizer: newServiceAndMeshACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
},
},
{
name: "tcp-route",
entry: &TCPRouteConfigEntry{Name: "test"},
expectACLs: []testACL{
{
name: "no-authz",
authorizer: newAuthz(t, ``),
canRead: false,
canWrite: false,
},
{
name: "service deny and operator deny",
authorizer: newServiceAndOperatorACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator deny",
authorizer: newServiceAndOperatorACL(t, "read", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service write and operator deny",
authorizer: newServiceAndOperatorACL(t, "write", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service deny and mesh deny",
authorizer: newServiceAndMeshACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh deny",
authorizer: newServiceAndMeshACL(t, "read", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service write and mesh deny",
authorizer: newServiceAndMeshACL(t, "write", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service deny and operator read",
authorizer: newServiceAndOperatorACL(t, "deny", "read"),
canRead: true,
canWrite: false,
},
{
name: "service read and operator read",
authorizer: newServiceAndOperatorACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator read",
authorizer: newServiceAndOperatorACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and operator write",
authorizer: newServiceAndOperatorACL(t, "deny", "write"),
canRead: true,
canWrite: true,
},
{
name: "service read and operator write",
authorizer: newServiceAndOperatorACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and operator write",
authorizer: newServiceAndOperatorACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
{
name: "service deny and mesh read",
authorizer: newServiceAndMeshACL(t, "deny", "read"),
canRead: true,
canWrite: false,
},
{
name: "service read and mesh read",
authorizer: newServiceAndMeshACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh read",
authorizer: newServiceAndMeshACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and mesh write",
authorizer: newServiceAndMeshACL(t, "deny", "write"),
canRead: true,
canWrite: true,
},
{
name: "service read and mesh write",
authorizer: newServiceAndMeshACL(t, "read", "write"),
canRead: true,
canWrite: true,
},
{
name: "service write and mesh write",
authorizer: newServiceAndMeshACL(t, "write", "write"),
canRead: true,
canWrite: true,
},
},
},
{
name: "bound-api-gateway",
entry: &BoundAPIGatewayConfigEntry{Name: "test"},
expectACLs: []testACL{
{
name: "no-authz",
authorizer: newAuthz(t, ``),
canRead: false,
canWrite: false,
},
{
name: "service deny and operator deny",
authorizer: newServiceAndOperatorACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator deny",
authorizer: newServiceAndOperatorACL(t, "read", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator deny",
authorizer: newServiceAndOperatorACL(t, "write", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service deny and mesh deny",
authorizer: newServiceAndMeshACL(t, "deny", "deny"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh deny",
authorizer: newServiceAndMeshACL(t, "read", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh deny",
authorizer: newServiceAndMeshACL(t, "write", "deny"),
canRead: true,
canWrite: false,
},
{
name: "service deny and operator read",
authorizer: newServiceAndOperatorACL(t, "deny", "read"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator read",
authorizer: newServiceAndOperatorACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator read",
authorizer: newServiceAndOperatorACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and operator write",
authorizer: newServiceAndOperatorACL(t, "deny", "write"),
canRead: false,
canWrite: false,
},
{
name: "service read and operator write",
authorizer: newServiceAndOperatorACL(t, "read", "write"),
canRead: true,
canWrite: false,
},
{
name: "service write and operator write",
authorizer: newServiceAndOperatorACL(t, "write", "write"),
canRead: true,
canWrite: false,
},
{
name: "service deny and mesh read",
authorizer: newServiceAndMeshACL(t, "deny", "read"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh read",
authorizer: newServiceAndMeshACL(t, "read", "read"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh read",
authorizer: newServiceAndMeshACL(t, "write", "read"),
canRead: true,
canWrite: false,
},
{
name: "service deny and mesh write",
authorizer: newServiceAndMeshACL(t, "deny", "write"),
canRead: false,
canWrite: false,
},
{
name: "service read and mesh write",
authorizer: newServiceAndMeshACL(t, "read", "write"),
canRead: true,
canWrite: false,
},
{
name: "service write and mesh write",
authorizer: newServiceAndMeshACL(t, "write", "write"),
canRead: true,
canWrite: false,
},
},
},
{
name: "terminating-gateway",
entry: &TerminatingGatewayConfigEntry{Name: "test"},

View File

@ -12,6 +12,10 @@ import (
"github.com/hashicorp/consul/types"
)
const (
wildcardPrefix = "*."
)
// IngressGatewayConfigEntry manages the configuration for an ingress service
// with the given name.
type IngressGatewayConfigEntry struct {
@ -371,7 +375,6 @@ func validateHost(tlsEnabled bool, host string) error {
return nil
}
wildcardPrefix := "*."
if _, ok := dns.IsDomainName(host); !ok {
return fmt.Errorf("Host %q must be a valid DNS hostname", host)
}
@ -687,3 +690,300 @@ func (g *GatewayService) Clone() *GatewayService {
ServiceKind: g.ServiceKind,
}
}
// APIGatewayConfigEntry manages the configuration for an API gateway service
// with the given name.
type APIGatewayConfigEntry struct {
// Kind of the config entry. This will be set to structs.APIGateway.
Kind string
// Name is used to match the config entry with its associated API gateway
// service. This should match the name provided in the service definition.
Name string
// Listeners is the set of listener configuration to which an API Gateway
// might bind.
Listeners []APIGatewayListener
// Status is the asynchronous status which an APIGateway propagates to the user.
Status Status
Meta map[string]string `json:",omitempty"`
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
}
func (e *APIGatewayConfigEntry) GetKind() string {
return APIGateway
}
func (e *APIGatewayConfigEntry) GetName() string {
if e == nil {
return ""
}
return e.Name
}
func (e *APIGatewayConfigEntry) GetMeta() map[string]string {
if e == nil {
return nil
}
return e.Meta
}
func (e *APIGatewayConfigEntry) Normalize() error {
for i, listener := range e.Listeners {
protocol := strings.ToLower(string(listener.Protocol))
listener.Protocol = APIGatewayListenerProtocol(protocol)
e.Listeners[i] = listener
}
return nil
}
func (e *APIGatewayConfigEntry) Validate() error {
if err := e.validateListenerNames(); err != nil {
return err
}
if err := e.validateMergedListeners(); err != nil {
return err
}
return e.validateListeners()
}
func (e *APIGatewayConfigEntry) validateListenerNames() error {
listeners := make(map[string]struct{})
for _, listener := range e.Listeners {
if _, found := listeners[listener.Name]; found {
return fmt.Errorf("found multiple listeners with the name %q", listener.Name)
}
listeners[listener.Name] = struct{}{}
}
return nil
}
func (e *APIGatewayConfigEntry) validateMergedListeners() error {
listeners := make(map[int]APIGatewayListener)
for _, listener := range e.Listeners {
merged, found := listeners[listener.Port]
if found && (merged.Hostname != listener.Hostname || merged.Protocol != listener.Protocol) {
return fmt.Errorf("listeners %q and %q cannot be merged", merged.Name, listener.Name)
}
listeners[listener.Port] = listener
}
return nil
}
func (e *APIGatewayConfigEntry) validateListeners() error {
validProtocols := map[APIGatewayListenerProtocol]bool{
ListenerProtocolHTTP: true,
ListenerProtocolTCP: true,
}
allowedCertificateKinds := map[string]bool{
InlineCertificate: true,
}
for _, listener := range e.Listeners {
if !validProtocols[listener.Protocol] {
return fmt.Errorf("unsupported listener protocol %q, must be one of 'tcp', or 'http'", listener.Protocol)
}
if listener.Protocol == ListenerProtocolTCP && listener.Hostname != "" {
// TODO: once we have SNI matching we should be able to implement this
return fmt.Errorf("hostname specification is not supported when using TCP")
}
if listener.Port <= 0 || listener.Port > 65535 {
return fmt.Errorf("listener port %d not in the range 1-65535", listener.Port)
}
if strings.ContainsRune(strings.TrimPrefix(listener.Hostname, wildcardPrefix), '*') {
return fmt.Errorf("host %q is not valid, a wildcard specifier is only allowed as the left-most label", listener.Hostname)
}
for _, certificate := range listener.TLS.Certificates {
if !allowedCertificateKinds[certificate.Kind] {
return fmt.Errorf("unsupported certificate kind: %q, must be 'inline-certificate'", certificate.Kind)
}
if certificate.Name == "" {
return fmt.Errorf("certificate reference must have a name")
}
}
if err := validateTLSConfig(listener.TLS.MinVersion, listener.TLS.MaxVersion, listener.TLS.CipherSuites); err != nil {
return err
}
}
return nil
}
func (e *APIGatewayConfigEntry) CanRead(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().ServiceReadAllowed(e.Name, &authzContext)
}
func (e *APIGatewayConfigEntry) CanWrite(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext)
}
func (e *APIGatewayConfigEntry) GetRaftIndex() *RaftIndex {
if e == nil {
return &RaftIndex{}
}
return &e.RaftIndex
}
func (e *APIGatewayConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
// APIGatewayListenerProtocol is the protocol that an APIGateway listener uses
type APIGatewayListenerProtocol string
const (
ListenerProtocolHTTP APIGatewayListenerProtocol = "http"
ListenerProtocolTCP APIGatewayListenerProtocol = "tcp"
)
// APIGatewayListener represents an individual listener for an APIGateway
type APIGatewayListener struct {
// Name is the optional name of the listener in a given gateway. This is
// optional, however, it must be unique. Therefore, if a gateway has more
// than a single listener, all but one must specify a Name.
Name string
// Hostname is the host name that a listener should be bound to, if
// unspecified, the listener accepts requests for all hostnames.
Hostname string
// Port is the port at which this listener should bind.
Port int
// Protocol is the protocol that a listener should use, it must
// either be http or tcp
Protocol APIGatewayListenerProtocol
// TLS is the TLS settings for the listener.
TLS APIGatewayTLSConfiguration
}
// APIGatewayTLSConfiguration specifies the configuration of a listeners
// TLS settings.
type APIGatewayTLSConfiguration struct {
// Certificates is a set of references to certificates
// that a gateway listener uses for TLS termination.
Certificates []ResourceReference
// MaxVersion is the maximum TLS version that the listener
// should support.
MaxVersion types.TLSVersion
// MinVersion is the minimum TLS version that the listener
// should support.
MinVersion types.TLSVersion
// CipherSuites is the cipher suites that the listener should support.
CipherSuites []types.TLSCipherSuite
}
// BoundAPIGatewayConfigEntry manages the configuration for a bound API
// gateway with the given name. This type is never written from the client.
// It is only written by the controller in order to represent an API gateway
// and the resources that are bound to it.
type BoundAPIGatewayConfigEntry struct {
// Kind of the config entry. This will be set to structs.BoundAPIGateway.
Kind string
// Name is used to match the config entry with its associated API gateway
// service. This should match the name provided in the corresponding API
// gateway service definition.
Name string
// Listeners are the valid listeners of an APIGateway with information about
// what certificates and routes have successfully bound to it.
Listeners []BoundAPIGatewayListener
Meta map[string]string `json:",omitempty"`
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
}
func (e *BoundAPIGatewayConfigEntry) GetKind() string {
return BoundAPIGateway
}
func (e *BoundAPIGatewayConfigEntry) GetName() string {
if e == nil {
return ""
}
return e.Name
}
func (e *BoundAPIGatewayConfigEntry) GetMeta() map[string]string {
if e == nil {
return nil
}
return e.Meta
}
func (e *BoundAPIGatewayConfigEntry) Normalize() error {
return nil
}
func (e *BoundAPIGatewayConfigEntry) Validate() error {
allowedCertificateKinds := map[string]bool{
InlineCertificate: true,
}
allowedRouteKinds := map[string]bool{
HTTPRoute: true,
TCPRoute: true,
}
// These should already be validated by upstream validation
// logic in the gateways/routes, but just in case we validate
// here as well.
for _, listener := range e.Listeners {
for _, certificate := range listener.Certificates {
if !allowedCertificateKinds[certificate.Kind] {
return fmt.Errorf("unsupported certificate kind: %q, must be 'inline-certificate'", certificate.Kind)
}
if certificate.Name == "" {
return fmt.Errorf("certificate reference must have a name")
}
}
for _, route := range listener.Routes {
if !allowedRouteKinds[route.Kind] {
return fmt.Errorf("unsupported route kind: %q, must be one of 'http-route', or 'tcp-route'", route.Kind)
}
if route.Name == "" {
return fmt.Errorf("route reference must have a name")
}
}
}
return nil
}
func (e *BoundAPIGatewayConfigEntry) CanRead(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().ServiceReadAllowed(e.Name, &authzContext)
}
func (e *BoundAPIGatewayConfigEntry) CanWrite(_ acl.Authorizer) error {
return acl.PermissionDenied("only writeable by controller")
}
func (e *BoundAPIGatewayConfigEntry) GetRaftIndex() *RaftIndex {
if e == nil {
return &RaftIndex{}
}
return &e.RaftIndex
}
func (e *BoundAPIGatewayConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
// BoundAPIGatewayListener is an API gateway listener with information
// about the routes and certificates that have successfully bound to it.
type BoundAPIGatewayListener struct {
Name string
Routes []ResourceReference
Certificates []ResourceReference
}

View File

@ -1123,3 +1123,221 @@ func TestGatewayService_Addresses(t *testing.T) {
})
}
}
func TestAPIGateway_Listeners(t *testing.T) {
cases := map[string]configEntryTestcase{
"listener name conflict": {
entry: &APIGatewayConfigEntry{
Kind: "api-gateway",
Name: "api-gw-one",
Listeners: []APIGatewayListener{
{
Port: 80,
Name: "foo",
},
{
Port: 80,
Name: "foo",
},
},
},
validateErr: "multiple listeners with the name",
},
"merged listener protocol conflict": {
entry: &APIGatewayConfigEntry{
Kind: "api-gateway",
Name: "api-gw-two",
Listeners: []APIGatewayListener{
{
Port: 80,
Protocol: ListenerProtocolHTTP,
},
{
Name: "foo",
Port: 80,
Protocol: ListenerProtocolTCP,
},
},
},
validateErr: "cannot be merged",
},
"merged listener hostname conflict": {
entry: &APIGatewayConfigEntry{
Kind: "api-gateway",
Name: "api-gw-three",
Listeners: []APIGatewayListener{
{
Port: 80,
Hostname: "host.one",
},
{
Name: "foo",
Port: 80,
Hostname: "host.two",
},
},
},
validateErr: "cannot be merged",
},
"invalid protocol": {
entry: &APIGatewayConfigEntry{
Kind: "api-gateway",
Name: "api-gw-four",
Listeners: []APIGatewayListener{
{
Port: 80,
Hostname: "host.one",
Protocol: APIGatewayListenerProtocol("UDP"),
},
},
},
validateErr: "unsupported listener protocol",
},
"hostname in unsupported protocol": {
entry: &APIGatewayConfigEntry{
Kind: "api-gateway",
Name: "api-gw-five",
Listeners: []APIGatewayListener{
{
Port: 80,
Hostname: "host.one",
Protocol: APIGatewayListenerProtocol("tcp"),
},
},
},
validateErr: "hostname specification is not supported",
},
"invalid port": {
entry: &APIGatewayConfigEntry{
Kind: "api-gateway",
Name: "api-gw-six",
Listeners: []APIGatewayListener{
{
Port: -1,
Protocol: APIGatewayListenerProtocol("tcp"),
},
},
},
validateErr: "not in the range 1-65535",
},
"invalid hostname": {
entry: &APIGatewayConfigEntry{
Kind: "api-gateway",
Name: "api-gw-seven",
Listeners: []APIGatewayListener{
{
Port: 80,
Hostname: "*.*.host.one",
Protocol: APIGatewayListenerProtocol("http"),
},
},
},
validateErr: "only allowed as the left-most label",
},
"unsupported certificate kind": {
entry: &APIGatewayConfigEntry{
Kind: "api-gateway",
Name: "api-gw-eight",
Listeners: []APIGatewayListener{
{
Port: 80,
Hostname: "host.one",
Protocol: APIGatewayListenerProtocol("http"),
TLS: APIGatewayTLSConfiguration{
Certificates: []ResourceReference{{
Kind: APIGateway,
}},
},
},
},
},
validateErr: "unsupported certificate kind",
},
"unnamed certificate": {
entry: &APIGatewayConfigEntry{
Kind: "api-gateway",
Name: "api-gw-nine",
Listeners: []APIGatewayListener{
{
Port: 80,
Hostname: "host.one",
Protocol: APIGatewayListenerProtocol("http"),
TLS: APIGatewayTLSConfiguration{
Certificates: []ResourceReference{{
Kind: InlineCertificate,
}},
},
},
},
},
validateErr: "certificate reference must have a name",
},
}
testConfigEntryNormalizeAndValidate(t, cases)
}
func TestBoundAPIGateway(t *testing.T) {
cases := map[string]configEntryTestcase{
"invalid certificate, no name": {
entry: &BoundAPIGatewayConfigEntry{
Kind: BoundAPIGateway,
Name: "bound-api-gw-one",
Listeners: []BoundAPIGatewayListener{
{
Name: "one",
Certificates: []ResourceReference{{
Kind: InlineCertificate,
}},
},
},
},
validateErr: "certificate reference must have a name",
},
"invalid certificate, no kind": {
entry: &BoundAPIGatewayConfigEntry{
Kind: BoundAPIGateway,
Name: "bound-api-gw-two",
Listeners: []BoundAPIGatewayListener{
{
Name: "one",
Certificates: []ResourceReference{{
Name: "foo",
}},
},
},
},
validateErr: "unsupported certificate kind",
},
"invalid route, no name": {
entry: &BoundAPIGatewayConfigEntry{
Kind: BoundAPIGateway,
Name: "bound-api-gw-three",
Listeners: []BoundAPIGatewayListener{
{
Name: "one",
Routes: []ResourceReference{{
Kind: TCPRoute,
}},
},
},
},
validateErr: "route reference must have a name",
},
"invalid route, no kind": {
entry: &BoundAPIGatewayConfigEntry{
Kind: BoundAPIGateway,
Name: "bound-api-gw-four",
Listeners: []BoundAPIGatewayListener{
{
Name: "one",
Routes: []ResourceReference{{
Name: "foo",
}},
},
},
},
validateErr: "unsupported route kind",
},
}
testConfigEntryNormalizeAndValidate(t, cases)
}

View File

@ -0,0 +1,101 @@
package structs
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"github.com/hashicorp/consul/acl"
)
// InlineCertificateConfigEntry manages the configuration for an inline certificate
// with the given name.
type InlineCertificateConfigEntry struct {
// Kind of config entry. This will be set to structs.InlineCertificate.
Kind string
// Name is used to match the config entry with its associated inline certificate.
Name string
// Certificate is the public certificate component of an x509 key pair encoded in raw PEM format.
Certificate string
// PrivateKey is the private key component of an x509 key pair encoded in raw PEM format.
PrivateKey string
Meta map[string]string `json:",omitempty"`
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
}
func (e *InlineCertificateConfigEntry) GetKind() string {
return InlineCertificate
}
func (e *InlineCertificateConfigEntry) GetName() string {
return e.Name
}
func (e *InlineCertificateConfigEntry) Normalize() error {
return nil
}
func (e *InlineCertificateConfigEntry) Validate() error {
privateKeyBlock, _ := pem.Decode([]byte(e.PrivateKey))
if privateKeyBlock == nil {
return errors.New("failed to parse private key PEM")
}
certificateBlock, _ := pem.Decode([]byte(e.Certificate))
if certificateBlock == nil {
return errors.New("failed to parse certificate PEM")
}
// make sure we have a valid x509 certificate
_, err := x509.ParseCertificate(certificateBlock.Bytes)
if err != nil {
return fmt.Errorf("failed to parse certificate: %w", err)
}
// validate that the cert was generated with the given private key
_, err = tls.X509KeyPair([]byte(e.Certificate), []byte(e.PrivateKey))
if err != nil {
return err
}
return nil
}
func (e *InlineCertificateConfigEntry) CanRead(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshReadAllowed(&authzContext)
}
func (e *InlineCertificateConfigEntry) CanWrite(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext)
}
func (e *InlineCertificateConfigEntry) GetMeta() map[string]string {
if e == nil {
return nil
}
return e.Meta
}
func (e *InlineCertificateConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
func (e *InlineCertificateConfigEntry) GetRaftIndex() *RaftIndex {
if e == nil {
return &RaftIndex{}
}
return &e.RaftIndex
}

View File

@ -0,0 +1,106 @@
package structs
import "testing"
const (
// generated via openssl req -x509 -sha256 -days 1825 -newkey rsa:2048 -keyout private.key -out certificate.crt
validPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAx95Opa6t4lGEpiTUogEBptqOdam2ch4BHQGhNhX/MrDwwuZQ
httBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2jQlhqTodElkbsd5vWY8R/bxJWQSo
NvVE12TlzECxGpJEiHt4W0r8pGffk+rvpljiUyCfnT1kGF3znOSjK1hRMTn6RKWC
yYaBvXQiB4SGilfLgJcEpOJKtISIxmZ+S409g9X5VU88/Bmmrz4cMyxce86Kc2ug
5/MOv0CjWDJwlrv8njneV2zvraQ61DDwQftrXOvuCbO5IBRHMOBHiHTZ4rtGuhMa
Ir21V4vb6n8c4YzXiFvhUYcyX7rltGZzVd+WmQIDAQABAoIBACYvceUzp2MK4gYA
GWPOP2uKbBdM0l+hHeNV0WAM+dHMfmMuL4pkT36ucqt0ySOLjw6rQyOZG5nmA6t9
sv0g4ae2eCMlyDIeNi1Yavu4Wt6YX4cTXbQKThm83C6W2X9THKbauBbxD621bsDK
7PhiGPN60yPue7YwFQAPqqD4YaK+s22HFIzk9gwM/rkvAUNwRv7SyHMiFe4Igc1C
Eev7iHWzvj5Heoz6XfF+XNF9DU+TieSUAdjd56VyUb8XL4+uBTOhHwLiXvAmfaMR
HvpcxeKnYZusS6NaOxcUHiJnsLNWrxmJj9WEGgQzuLxcLjTe4vVmELVZD8t3QUKj
PAxu8tUCgYEA7KIWVn9dfVpokReorFym+J8FzLwSktP9RZYEMonJo00i8aii3K9s
u/aSwRWQSCzmON1ZcxZzWhwQF9usz6kGCk//9+4hlVW90GtNK0RD+j7sp4aT2JI8
9eLEjTG+xSXa7XWe98QncjjL9lu/yrRncSTxHs13q/XP198nn2aYuQ8CgYEA2Dnt
sRBzv0fFEvzzFv7G/5f85mouN38TUYvxNRTjBLCXl9DeKjDkOVZ2b6qlfQnYXIru
H+W+v+AZEb6fySXc8FRab7lkgTMrwE+aeI4rkW7asVwtclv01QJ5wMnyT84AgDD/
Dgt/RThFaHgtU9TW5GOZveL+l9fVPn7vKFdTJdcCgYEArJ99zjHxwJ1whNAOk1av
09UmRPm6TvRo4heTDk8oEoIWCNatoHI0z1YMLuENNSnT9Q280FFDayvnrY/qnD7A
kktT/sjwJOG8q8trKzIMqQS4XWm2dxoPcIyyOBJfCbEY6XuRsUuePxwh5qF942EB
yS9a2s6nC4Ix0lgPrqAIr48CgYBgS/Q6riwOXSU8nqCYdiEkBYlhCJrKpnJxF9T1
ofa0yPzKZP/8ZEfP7VzTwHjxJehQ1qLUW9pG08P2biH1UEKEWdzo8vT6wVJT1F/k
HtTycR8+a+Hlk2SHVRHqNUYQGpuIe8mrdJ1as4Pd0d/F/P0zO9Rlh+mAsGPM8HUM
T0+9gwKBgHDoerX7NTskg0H0t8O+iSMevdxpEWp34ZYa9gHiftTQGyrRgERCa7Gj
nZPAxKb2JoWyfnu3v7G5gZ8fhDFsiOxLbZv6UZJBbUIh1MjJISpXrForDrC2QNLX
kHrHfwBFDB3KMudhQknsJzEJKCL/KmFH6o0MvsoaT9yzEl3K+ah/
-----END RSA PRIVATE KEY-----`
validCertificate = `-----BEGIN CERTIFICATE-----
MIICljCCAX4CCQCQMDsYO8FrPjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV
UzAeFw0yMjEyMjAxNzUwMjVaFw0yNzEyMTkxNzUwMjVaMA0xCzAJBgNVBAYTAlVT
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx95Opa6t4lGEpiTUogEB
ptqOdam2ch4BHQGhNhX/MrDwwuZQhttBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2
jQlhqTodElkbsd5vWY8R/bxJWQSoNvVE12TlzECxGpJEiHt4W0r8pGffk+rvplji
UyCfnT1kGF3znOSjK1hRMTn6RKWCyYaBvXQiB4SGilfLgJcEpOJKtISIxmZ+S409
g9X5VU88/Bmmrz4cMyxce86Kc2ug5/MOv0CjWDJwlrv8njneV2zvraQ61DDwQftr
XOvuCbO5IBRHMOBHiHTZ4rtGuhMaIr21V4vb6n8c4YzXiFvhUYcyX7rltGZzVd+W
mQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBfCqoUIdPf/HGSbOorPyZWbyizNtHJ
GL7x9cAeIYxpI5Y/WcO1o5v94lvrgm3FNfJoGKbV66+JxOge731FrfMpHplhar1Z
RahYIzNLRBTLrwadLAZkApUpZvB8qDK4knsTWFYujNsylCww2A6ajzIMFNU4GkUK
NtyHRuD+KYRmjXtyX1yHNqfGN3vOQmwavHq2R8wHYuBSc6LAHHV9vG+j0VsgMELO
qwxn8SmLkSKbf2+MsQVzLCXXN5u+D8Yv+4py+oKP4EQ5aFZuDEx+r/G/31rTthww
AAJAMaoXmoYVdgXV+CPuBb2M4XCpuzLu3bcA2PXm5ipSyIgntMKwXV7r
-----END CERTIFICATE-----`
mismatchedCertificate = `-----BEGIN CERTIFICATE-----
MIICljCCAX4CCQC49bq8e0QgLDANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV
UzAeFw0yMjEyMjAxNzUyMzJaFw0yNzEyMTkxNzUyMzJaMA0xCzAJBgNVBAYTAlVT
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk7Are9ulVDY0IqaG5Pt/
OVuS0kmDhgVUfQBM5JDGRfIsu1ebn68kn5JGCTQ+nC8nU9QXRJS7vG6As5GWm08W
FpkOyIbHLjOhWtYCYzQ+0R+sSSoMnczgl8l6wIUIkR3Vpoy6QUsSZbvo4/xDi3Uk
1CF+JMTM2oFDLD8PNrNzW/txRyTugK36W1G1ofUhvP6EHsTjmVcZwBcLOKToov6L
Ai758MLztl1/X/90DNdZwuHC9fGIgx52Ojz3+XIocXFttr+J8xZglMCtqL4n40bh
5b1DE+hC3NHQmA+7Chc99z28baj2cU1woNk/TO+ewqpyvj+WPWwGOQt3U63ZoPaw
yQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCMF3JlrDdcSv2KYrxEp1tWB/GglI8a
JiSvrf3hePaRz59099bg4DoHzTn0ptOcOPOO9epDPbCJrUqLuPlwvrQRvll6GaW1
y3TcbnE1AbwTAjbOTgpLhvuj6IVlyNNLoKbjZqs4A8N8i6UkQ7Y8qg77lwxD3QoH
pWLwGZKJifKPa7ObVWmKj727kbU59nA2Hx+Y4qa/MyiPWxJM9Y0JsFGxSBxp4kmQ
q4ikzSWaPv/TvtV+d4mO1H44aggdNMCYIQd/5BXQzG40l+ecHnBueJyG312ax/Zp
NsYUAKQT864cGlxrnWVgT4sW/tsl9Qen7g9iAdeBAPvLO7cQjAjtc7KZ
-----END CERTIFICATE-----`
)
func TestInlineCertificate(t *testing.T) {
cases := map[string]configEntryTestcase{
"invalid private key": {
entry: &InlineCertificateConfigEntry{
Kind: InlineCertificate,
Name: "cert-one",
PrivateKey: "foo",
},
validateErr: "failed to parse private key PEM",
},
"invalid certificate": {
entry: &InlineCertificateConfigEntry{
Kind: InlineCertificate,
Name: "cert-two",
PrivateKey: validPrivateKey,
Certificate: "foo",
},
validateErr: "failed to parse certificate PEM",
},
"mismatched certificate": {
entry: &InlineCertificateConfigEntry{
Kind: InlineCertificate,
Name: "cert-three",
PrivateKey: validPrivateKey,
Certificate: mismatchedCertificate,
},
validateErr: "private key does not match public key",
},
"matched certificate": {
entry: &InlineCertificateConfigEntry{
Kind: InlineCertificate,
Name: "cert-four",
PrivateKey: validPrivateKey,
Certificate: validCertificate,
},
},
}
testConfigEntryNormalizeAndValidate(t, cases)
}

View File

@ -0,0 +1,177 @@
package structs
import (
"fmt"
"github.com/hashicorp/consul/acl"
)
// HTTPRouteConfigEntry manages the configuration for a HTTP route
// with the given name.
type HTTPRouteConfigEntry struct {
// Kind of the config entry. This will be set to structs.HTTPRoute.
Kind string
// Name is used to match the config entry with its associated set
// of resources, which may include routers, splitters, filters, etc.
Name string
Meta map[string]string `json:",omitempty"`
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
}
func (e *HTTPRouteConfigEntry) GetKind() string {
return HTTPRoute
}
func (e *HTTPRouteConfigEntry) GetName() string {
if e == nil {
return ""
}
return e.Name
}
func (e *HTTPRouteConfigEntry) Normalize() error {
return nil
}
func (e *HTTPRouteConfigEntry) Validate() error {
return nil
}
func (e *HTTPRouteConfigEntry) CanRead(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshReadAllowed(&authzContext)
}
func (e *HTTPRouteConfigEntry) CanWrite(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext)
}
func (e *HTTPRouteConfigEntry) GetMeta() map[string]string {
if e == nil {
return nil
}
return e.Meta
}
func (e *HTTPRouteConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
func (e *HTTPRouteConfigEntry) GetRaftIndex() *RaftIndex {
if e == nil {
return &RaftIndex{}
}
return &e.RaftIndex
}
// TCPRouteConfigEntry manages the configuration for a TCP route
// with the given name.
type TCPRouteConfigEntry struct {
// Kind of the config entry. This will be set to structs.TCPRoute.
Kind string
// Name is used to match the config entry with its associated set
// of resources.
Name string
// Parents is a list of gateways that this route should be bound to
Parents []ResourceReference
// Services is a list of TCP-based services that this should route to.
// Currently, this must specify at maximum one service.
Services []TCPService
Meta map[string]string `json:",omitempty"`
// Status is the asynchronous status which a TCPRoute propagates to the user.
Status Status
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
}
func (e *TCPRouteConfigEntry) GetKind() string {
return TCPRoute
}
func (e *TCPRouteConfigEntry) GetName() string {
if e == nil {
return ""
}
return e.Name
}
func (e *TCPRouteConfigEntry) GetMeta() map[string]string {
if e == nil {
return nil
}
return e.Meta
}
func (e *TCPRouteConfigEntry) Normalize() error {
for i, parent := range e.Parents {
if parent.Kind == "" {
parent.Kind = APIGateway
e.Parents[i] = parent
}
}
return nil
}
func (e *TCPRouteConfigEntry) Validate() error {
validParentKinds := map[string]bool{
APIGateway: true,
}
if len(e.Services) > 1 {
return fmt.Errorf("tcp-route currently only supports one service")
}
for _, parent := range e.Parents {
if !validParentKinds[parent.Kind] {
return fmt.Errorf("unsupported parent kind: %q, must be 'api-gateway'", parent.Kind)
}
}
return nil
}
func (e *TCPRouteConfigEntry) CanRead(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshReadAllowed(&authzContext)
}
func (e *TCPRouteConfigEntry) CanWrite(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext)
}
func (e *TCPRouteConfigEntry) GetRaftIndex() *RaftIndex {
if e == nil {
return &RaftIndex{}
}
return &e.RaftIndex
}
func (e *TCPRouteConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
// TCPService is a service reference for a TCPRoute
type TCPService struct {
Name string
// Weight specifies the proportion of requests forwarded to the referenced service.
// This is computed as weight/(sum of all weights in the list of services).
Weight int
acl.EnterpriseMeta
}

View File

@ -0,0 +1,58 @@
package structs
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestTCPRoute(t *testing.T) {
cases := map[string]configEntryTestcase{
"multiple services": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-one",
Services: []TCPService{{
Name: "foo",
}, {
Name: "bar",
}},
},
validateErr: "tcp-route currently only supports one service",
},
"normalize parent kind": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Services: []TCPService{{
Name: "foo",
}},
},
normalizeOnly: true,
check: func(t *testing.T, entry ConfigEntry) {
expectedParent := ResourceReference{
Kind: APIGateway,
Name: "gateway",
}
route := entry.(*TCPRouteConfigEntry)
require.Len(t, route.Parents, 1)
require.Equal(t, expectedParent, route.Parents[0])
},
},
"invalid parent kind": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Kind: "route",
Name: "gateway",
}},
},
validateErr: "unsupported parent kind",
},
}
testConfigEntryNormalizeAndValidate(t, cases)
}

View File

@ -0,0 +1,51 @@
package structs
import (
"time"
"github.com/hashicorp/consul/acl"
)
// ResourceReference is a reference to a ConfigEntry
// with an optional reference to a subsection of that ConfigEntry
// that can be specified as SectionName
type ResourceReference struct {
// Kind is the kind of ConfigEntry that this resource refers to.
Kind string
// Name is the identifier for the ConfigEntry this resource refers to.
Name string
// SectionName is a generic subresource identifier that specifies
// a subset of the ConfigEntry to which this reference applies. Usage
// of this field should be up to the controller that leverages it. If
// unused, this should be blank.
SectionName string
acl.EnterpriseMeta
}
// Status is used for propagating back asynchronously calculated
// messages from control loops to a user
type Status struct {
// Conditions is the set of condition objects associated with
// a ConfigEntry status.
Conditions []Condition
}
// Condition is used for a single message and state associated
// with an object. For example, a ConfigEntry that references
// multiple other resources may have different statuses with
// respect to each of those resources.
type Condition struct {
// Status is a value from a bounded set of statuses that an object might have
Status string
// Reason is a value from a bounded set of reasons for a given status
Reason string
// Message is a message that gives more detailed information about
// why a Condition has a given status and reason
Message string
// Resource is an optional reference to a resource for which this
// condition applies
Resource *ResourceReference
// LastTransitionTime is the time at which this Condition was created
LastTransitionTime *time.Time
}

View File

@ -296,7 +296,7 @@ func TestDecodeConfigEntry_ServiceDefaults(t *testing.T) {
}
// TestDecodeConfigEntry is the 'structs' mirror image of
// command/config/write/config_write_test.go:TestParseConfigEntry
// command/helpers/helpers_test.go:TestParseConfigEntry
func TestDecodeConfigEntry(t *testing.T) {
for _, tc := range []struct {
@ -1899,6 +1899,114 @@ func TestDecodeConfigEntry(t *testing.T) {
},
},
},
{
name: "api-gateway",
snake: `
kind = "api-gateway"
name = "foo"
meta {
"foo" = "bar"
"gir" = "zim"
}
`,
camel: `
Kind = "api-gateway"
Name = "foo"
Meta {
"foo" = "bar"
"gir" = "zim"
}
`,
expect: &APIGatewayConfigEntry{
Kind: "api-gateway",
Name: "foo",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
},
{
name: "inline-certificate",
snake: `
kind = "inline-certificate"
name = "foo"
meta {
"foo" = "bar"
"gir" = "zim"
}
`,
camel: `
Kind = "inline-certificate"
Name = "foo"
Meta {
"foo" = "bar"
"gir" = "zim"
}
`,
expect: &InlineCertificateConfigEntry{
Kind: "inline-certificate",
Name: "foo",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
},
{
name: "http-route",
snake: `
kind = "http-route"
name = "foo"
meta {
"foo" = "bar"
"gir" = "zim"
}
`,
camel: `
Kind = "http-route"
Name = "foo"
Meta {
"foo" = "bar"
"gir" = "zim"
}
`,
expect: &HTTPRouteConfigEntry{
Kind: "http-route",
Name: "foo",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
},
{
name: "tcp-route",
snake: `
kind = "tcp-route"
name = "foo"
meta {
"foo" = "bar"
"gir" = "zim"
}
`,
camel: `
Kind = "tcp-route"
Name = "foo"
Meta {
"foo" = "bar"
"gir" = "zim"
}
`,
expect: &TCPRouteConfigEntry{
Kind: "tcp-route",
Name: "foo",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
},
{
name: "exported-services",
snake: `

View File

@ -26,6 +26,10 @@ const (
ProxyConfigGlobal string = "global"
MeshConfigMesh string = "mesh"
APIGateway string = "api-gateway"
TCPRoute string = "tcp-route"
InlineCertificate string = "inline-certificate"
HTTPRoute string = "http-route"
)
const (
@ -347,6 +351,14 @@ func makeConfigEntry(kind, name string) (ConfigEntry, error) {
return &MeshConfigEntry{}, nil
case ExportedServices:
return &ExportedServicesConfigEntry{Name: name}, nil
case APIGateway:
return &APIGatewayConfigEntry{Kind: kind, Name: name}, nil
case TCPRoute:
return &TCPRouteConfigEntry{Kind: kind, Name: name}, nil
case InlineCertificate:
return &InlineCertificateConfigEntry{Kind: kind, Name: name}, nil
case HTTPRoute:
return &HTTPRouteConfigEntry{Kind: kind, Name: name}, nil
default:
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
}

View File

@ -221,3 +221,82 @@ func (g *TerminatingGatewayConfigEntry) GetNamespace() string { return g.N
func (g *TerminatingGatewayConfigEntry) GetMeta() map[string]string { return g.Meta }
func (g *TerminatingGatewayConfigEntry) GetCreateIndex() uint64 { return g.CreateIndex }
func (g *TerminatingGatewayConfigEntry) GetModifyIndex() uint64 { return g.ModifyIndex }
// APIGatewayConfigEntry manages the configuration for an API gateway
// with the given name.
type APIGatewayConfigEntry struct {
// Kind of the config entry. This should be set to api.APIGateway.
Kind string
// Name is used to match the config entry with its associated api gateway
// service. This should match the name provided in the service definition.
Name string
Meta map[string]string `json:",omitempty"`
// Listeners is the set of listener configuration to which an API Gateway
// might bind.
Listeners []APIGatewayListener
// Status is the asynchronous status which an APIGateway propagates to the user.
Status ConfigEntryStatus
// 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 (g *APIGatewayConfigEntry) GetKind() string { return g.Kind }
func (g *APIGatewayConfigEntry) GetName() string { return g.Name }
func (g *APIGatewayConfigEntry) GetPartition() string { return g.Partition }
func (g *APIGatewayConfigEntry) GetNamespace() string { return g.Namespace }
func (g *APIGatewayConfigEntry) GetMeta() map[string]string { return g.Meta }
func (g *APIGatewayConfigEntry) GetCreateIndex() uint64 { return g.CreateIndex }
func (g *APIGatewayConfigEntry) GetModifyIndex() uint64 { return g.ModifyIndex }
// APIGatewayListener represents an individual listener for an APIGateway
type APIGatewayListener struct {
// Name is the optional name of the listener in a given gateway. This is
// optional, however, it must be unique. Therefore, if a gateway has more
// than a single listener, all but one must specify a Name.
Name string
// Hostname is the host name that a listener should be bound to, if
// unspecified, the listener accepts requests for all hostnames.
Hostname string
// Port is the port at which this listener should bind.
Port int
// Protocol is the protocol that a listener should use, it must
// either be "http" or "tcp"
Protocol string
// TLS is the TLS settings for the listener.
TLS APIGatewayTLSConfiguration
}
// APIGatewayTLSConfiguration specifies the configuration of a listeners
// TLS settings.
type APIGatewayTLSConfiguration struct {
// Certificates is a set of references to certificates
// that a gateway listener uses for TLS termination.
Certificates []ResourceReference
// MaxVersion is the maximum TLS version that the listener
// should support.
MaxVersion string `json:",omitempty" alias:"tls_max_version"`
// MinVersion is the minimum TLS version that the listener
// should support.
MinVersion string `json:",omitempty" alias:"tls_min_version"`
// Define a subset of cipher suites to restrict
// Only applicable to connections negotiated via TLS 1.2 or earlier
CipherSuites []string `json:",omitempty" alias:"cipher_suites"`
}

View File

@ -0,0 +1,75 @@
package api
// InlineCertificateConfigEntry -- TODO stub
type InlineCertificateConfigEntry struct {
// Kind of the config entry. This should be set to api.InlineCertificate.
Kind string
// Name is used to match the config entry with its associated tcp-route
// service. This should match the name provided in the service definition.
Name string
// Certificate is the public certificate component of an x509 key pair encoded in raw PEM format.
Certificate string
// PrivateKey is the private key component of an x509 key pair encoded in raw PEM format.
PrivateKey string
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 *InlineCertificateConfigEntry) GetKind() string {
return InlineCertificate
}
func (a *InlineCertificateConfigEntry) GetName() string {
if a != nil {
return ""
}
return a.Name
}
func (a *InlineCertificateConfigEntry) GetPartition() string {
if a != nil {
return ""
}
return a.Partition
}
func (a *InlineCertificateConfigEntry) GetNamespace() string {
if a != nil {
return ""
}
return a.GetNamespace()
}
func (a *InlineCertificateConfigEntry) GetMeta() map[string]string {
if a != nil {
return nil
}
return a.GetMeta()
}
func (a *InlineCertificateConfigEntry) GetCreateIndex() uint64 {
return a.CreateIndex
}
func (a *InlineCertificateConfigEntry) GetModifyIndex() uint64 {
return a.ModifyIndex
}

132
api/config_entry_routes.go Normal file
View File

@ -0,0 +1,132 @@
package api
// TCPRouteConfigEntry -- TODO stub
type TCPRouteConfigEntry struct {
// Kind of the config entry. This should be set to api.TCPRoute.
Kind string
// Name is used to match the config entry with its associated tcp-route
// service. This should match the name provided in the service definition.
Name string
// Parents is a list of gateways that this route should be bound to.
Parents []ResourceReference
// Services is a list of TCP-based services that this should route to.
// Currently, this must specify at maximum one service.
Services []TCPService
Meta map[string]string `json:",omitempty"`
// Status is the asynchronous status which a TCPRoute propagates to the user.
Status ConfigEntryStatus
// 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 *TCPRouteConfigEntry) GetKind() string {
return TCPRoute
}
func (a *TCPRouteConfigEntry) GetName() string {
if a != nil {
return ""
}
return a.Name
}
func (a *TCPRouteConfigEntry) GetPartition() string {
if a != nil {
return ""
}
return a.Partition
}
func (a *TCPRouteConfigEntry) GetNamespace() string {
if a != nil {
return ""
}
return a.GetNamespace()
}
func (a *TCPRouteConfigEntry) GetMeta() map[string]string {
if a != nil {
return nil
}
return a.GetMeta()
}
func (a *TCPRouteConfigEntry) GetCreateIndex() uint64 {
return a.CreateIndex
}
func (a *TCPRouteConfigEntry) GetModifyIndex() uint64 {
return a.ModifyIndex
}
// TCPService is a service reference for a TCPRoute
type TCPService struct {
Name string
// Weight specifies the proportion of requests forwarded to the referenced service.
// This is computed as weight/(sum of all weights in the list of services).
Weight int
// 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"`
}
// HTTPRouteConfigEntry manages the configuration for a HTTP route
// with the given name.
type HTTPRouteConfigEntry struct {
// Kind of the config entry. This should be set to api.HTTPRoute.
Kind string
// Name is used to match the config entry with its associated http-route.
Name string
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 (r *HTTPRouteConfigEntry) GetKind() string { return HTTPRoute }
func (r *HTTPRouteConfigEntry) GetName() string { return r.Name }
func (r *HTTPRouteConfigEntry) GetPartition() string { return r.Partition }
func (r *HTTPRouteConfigEntry) GetNamespace() string { return r.Namespace }
func (r *HTTPRouteConfigEntry) GetMeta() map[string]string { return r.Meta }
func (r *HTTPRouteConfigEntry) GetCreateIndex() uint64 { return r.CreateIndex }
func (r *HTTPRouteConfigEntry) GetModifyIndex() uint64 { return r.ModifyIndex }

View File

@ -0,0 +1,55 @@
package api
import (
"time"
)
// ResourceReference is a reference to a ConfigEntry
// with an optional reference to a subsection of that ConfigEntry
// that can be specified as SectionName
type ResourceReference struct {
// Kind is the kind of ConfigEntry that this resource refers to.
Kind string
// Name is the identifier for the ConfigEntry this resource refers to.
Name string
// SectionName is a generic subresource identifier that specifies
// a subset of the ConfigEntry to which this reference applies. Usage
// of this field should be up to the controller that leverages it. If
// unused, this should be blank.
SectionName string
// 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"`
}
// ConfigEntryStatus is used for propagating back asynchronously calculated
// messages from control loops to a user
type ConfigEntryStatus struct {
// Conditions is the set of condition objects associated with
// a ConfigEntry status.
Conditions []Condition
}
// Condition is used for a single message and state associated
// with an object. For example, a ConfigEntry that references
// multiple other resources may have different statuses with
// respect to each of those resources.
type Condition struct {
// Status is a value from a bounded set of statuses that an object might have
Status string
// Reason is a value from a bounded set of reasons for a given status
Reason string
// Message is a message that gives more detailed information about
// why a Condition has a given status and reason
Message string
// Resource is an optional reference to a resource for which this
// condition applies
Resource *ResourceReference
// LastTransitionTime is the time at which this Condition was created
LastTransitionTime *time.Time
}

View File

@ -3037,6 +3037,190 @@ func TestParseConfigEntry(t *testing.T) {
},
},
},
{
name: "api-gateway",
snake: `
kind = "api-gateway"
name = "main"
meta {
"foo" = "bar"
"gir" = "zim"
}
`,
camel: `
Kind = "api-gateway"
Name = "main"
Meta {
"foo" = "bar"
"gir" = "zim"
}
`,
snakeJSON: `
{
"kind": "api-gateway",
"name": "main",
"meta": {
"foo": "bar",
"gir": "zim"
}
}
`,
camelJSON: `
{
"Kind": "api-gateway",
"Name": "main",
"Meta": {
"foo": "bar",
"gir": "zim"
}
}`,
expect: &api.APIGatewayConfigEntry{
Kind: "api-gateway",
Name: "main",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
},
{
name: "inline-certificate",
snake: `
kind = "inline-certificate"
name = "main"
meta {
"foo" = "bar"
"gir" = "zim"
}
`,
camel: `
Kind = "inline-certificate"
Name = "main"
Meta {
"foo" = "bar"
"gir" = "zim"
}
`,
snakeJSON: `
{
"kind": "inline-certificate",
"name": "main",
"meta": {
"foo": "bar",
"gir": "zim"
}
}
`,
camelJSON: `
{
"Kind": "inline-certificate",
"Name": "main",
"Meta": {
"foo": "bar",
"gir": "zim"
}
}`,
expect: &api.InlineCertificateConfigEntry{
Kind: "inline-certificate",
Name: "main",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
},
{
name: "http-route",
snake: `
kind = "http-route"
name = "main"
meta {
"foo" = "bar"
"gir" = "zim"
}
`,
camel: `
Kind = "http-route"
Name = "main"
Meta {
"foo" = "bar"
"gir" = "zim"
}
`,
snakeJSON: `
{
"kind": "http-route",
"name": "main",
"meta": {
"foo": "bar",
"gir": "zim"
}
}
`,
camelJSON: `
{
"Kind": "http-route",
"Name": "main",
"Meta": {
"foo": "bar",
"gir": "zim"
}
}`,
expect: &api.HTTPRouteConfigEntry{
Kind: "http-route",
Name: "main",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
},
{
name: "tcp-route",
snake: `
kind = "tcp-route"
name = "main"
meta {
"foo" = "bar"
"gir" = "zim"
}
`,
camel: `
Kind = "tcp-route"
Name = "main"
Meta {
"foo" = "bar"
"gir" = "zim"
}
`,
snakeJSON: `
{
"kind": "tcp-route",
"name": "main",
"meta": {
"foo": "bar",
"gir": "zim"
}
}
`,
camelJSON: `
{
"Kind": "tcp-route",
"Name": "main",
"Meta": {
"foo": "bar",
"gir": "zim"
}
}`,
expect: &api.TCPRouteConfigEntry{
Kind: "tcp-route",
Name: "main",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
},
} {
tc := tc

View File

@ -4,6 +4,210 @@ package pbconfigentry
import "github.com/hashicorp/consul/agent/structs"
func APIGatewayToStructs(s *APIGateway, t *structs.APIGatewayConfigEntry) {
if s == nil {
return
}
{
t.Listeners = make([]structs.APIGatewayListener, len(s.Listeners))
for i := range s.Listeners {
if s.Listeners[i] != nil {
APIGatewayListenerToStructs(s.Listeners[i], &t.Listeners[i])
}
}
}
if s.Status != nil {
StatusToStructs(s.Status, &t.Status)
}
t.Meta = s.Meta
}
func APIGatewayFromStructs(t *structs.APIGatewayConfigEntry, s *APIGateway) {
if s == nil {
return
}
{
s.Listeners = make([]*APIGatewayListener, len(t.Listeners))
for i := range t.Listeners {
{
var x APIGatewayListener
APIGatewayListenerFromStructs(&t.Listeners[i], &x)
s.Listeners[i] = &x
}
}
}
{
var x Status
StatusFromStructs(&t.Status, &x)
s.Status = &x
}
s.Meta = t.Meta
}
func APIGatewayListenerToStructs(s *APIGatewayListener, t *structs.APIGatewayListener) {
if s == nil {
return
}
t.Name = s.Name
t.Hostname = s.Hostname
t.Port = int(s.Port)
t.Protocol = apiGatewayProtocolToStructs(s.Protocol)
if s.TLS != nil {
APIGatewayTLSConfigurationToStructs(s.TLS, &t.TLS)
}
}
func APIGatewayListenerFromStructs(t *structs.APIGatewayListener, s *APIGatewayListener) {
if s == nil {
return
}
s.Name = t.Name
s.Hostname = t.Hostname
s.Port = int32(t.Port)
s.Protocol = apiGatewayProtocolFromStructs(t.Protocol)
{
var x APIGatewayTLSConfiguration
APIGatewayTLSConfigurationFromStructs(&t.TLS, &x)
s.TLS = &x
}
}
func APIGatewayTLSConfigurationToStructs(s *APIGatewayTLSConfiguration, t *structs.APIGatewayTLSConfiguration) {
if s == nil {
return
}
{
t.Certificates = make([]structs.ResourceReference, len(s.Certificates))
for i := range s.Certificates {
if s.Certificates[i] != nil {
ResourceReferenceToStructs(s.Certificates[i], &t.Certificates[i])
}
}
}
t.MaxVersion = tlsVersionToStructs(s.MaxVersion)
t.MinVersion = tlsVersionToStructs(s.MinVersion)
t.CipherSuites = cipherSuitesToStructs(s.CipherSuites)
}
func APIGatewayTLSConfigurationFromStructs(t *structs.APIGatewayTLSConfiguration, s *APIGatewayTLSConfiguration) {
if s == nil {
return
}
{
s.Certificates = make([]*ResourceReference, len(t.Certificates))
for i := range t.Certificates {
{
var x ResourceReference
ResourceReferenceFromStructs(&t.Certificates[i], &x)
s.Certificates[i] = &x
}
}
}
s.MaxVersion = tlsVersionFromStructs(t.MaxVersion)
s.MinVersion = tlsVersionFromStructs(t.MinVersion)
s.CipherSuites = cipherSuitesFromStructs(t.CipherSuites)
}
func BoundAPIGatewayToStructs(s *BoundAPIGateway, t *structs.BoundAPIGatewayConfigEntry) {
if s == nil {
return
}
{
t.Listeners = make([]structs.BoundAPIGatewayListener, len(s.Listeners))
for i := range s.Listeners {
if s.Listeners[i] != nil {
BoundAPIGatewayListenerToStructs(s.Listeners[i], &t.Listeners[i])
}
}
}
t.Meta = s.Meta
}
func BoundAPIGatewayFromStructs(t *structs.BoundAPIGatewayConfigEntry, s *BoundAPIGateway) {
if s == nil {
return
}
{
s.Listeners = make([]*BoundAPIGatewayListener, len(t.Listeners))
for i := range t.Listeners {
{
var x BoundAPIGatewayListener
BoundAPIGatewayListenerFromStructs(&t.Listeners[i], &x)
s.Listeners[i] = &x
}
}
}
s.Meta = t.Meta
}
func BoundAPIGatewayListenerToStructs(s *BoundAPIGatewayListener, t *structs.BoundAPIGatewayListener) {
if s == nil {
return
}
t.Name = s.Name
{
t.Routes = make([]structs.ResourceReference, len(s.Routes))
for i := range s.Routes {
if s.Routes[i] != nil {
ResourceReferenceToStructs(s.Routes[i], &t.Routes[i])
}
}
}
{
t.Certificates = make([]structs.ResourceReference, len(s.Certificates))
for i := range s.Certificates {
if s.Certificates[i] != nil {
ResourceReferenceToStructs(s.Certificates[i], &t.Certificates[i])
}
}
}
}
func BoundAPIGatewayListenerFromStructs(t *structs.BoundAPIGatewayListener, s *BoundAPIGatewayListener) {
if s == nil {
return
}
s.Name = t.Name
{
s.Routes = make([]*ResourceReference, len(t.Routes))
for i := range t.Routes {
{
var x ResourceReference
ResourceReferenceFromStructs(&t.Routes[i], &x)
s.Routes[i] = &x
}
}
}
{
s.Certificates = make([]*ResourceReference, len(t.Certificates))
for i := range t.Certificates {
{
var x ResourceReference
ResourceReferenceFromStructs(&t.Certificates[i], &x)
s.Certificates[i] = &x
}
}
}
}
func ConditionToStructs(s *Condition, t *structs.Condition) {
if s == nil {
return
}
t.Status = s.Status
t.Reason = s.Reason
t.Message = s.Message
if s.Resource != nil {
var x structs.ResourceReference
ResourceReferenceToStructs(s.Resource, &x)
t.Resource = &x
}
t.LastTransitionTime = timeToStructs(s.LastTransitionTime)
}
func ConditionFromStructs(t *structs.Condition, s *Condition) {
if s == nil {
return
}
s.Status = t.Status
s.Reason = t.Reason
s.Message = t.Message
if t.Resource != nil {
var x ResourceReference
ResourceReferenceFromStructs(t.Resource, &x)
s.Resource = &x
}
s.LastTransitionTime = timeFromStructs(t.LastTransitionTime)
}
func CookieConfigToStructs(s *CookieConfig, t *structs.CookieConfig) {
if s == nil {
return
@ -178,6 +382,18 @@ func HTTPHeaderModifiersFromStructs(t *structs.HTTPHeaderModifiers, s *HTTPHeade
s.Set = t.Set
s.Remove = t.Remove
}
func HTTPRouteToStructs(s *HTTPRoute, t *structs.HTTPRouteConfigEntry) {
if s == nil {
return
}
t.Meta = s.Meta
}
func HTTPRouteFromStructs(t *structs.HTTPRouteConfigEntry, s *HTTPRoute) {
if s == nil {
return
}
s.Meta = t.Meta
}
func HashPolicyToStructs(s *HashPolicy, t *structs.HashPolicy) {
if s == nil {
return
@ -386,6 +602,22 @@ func IngressServiceConfigFromStructs(t *structs.IngressServiceConfig, s *Ingress
s.PassiveHealthCheck = &x
}
}
func InlineCertificateToStructs(s *InlineCertificate, t *structs.InlineCertificateConfigEntry) {
if s == nil {
return
}
t.Certificate = s.Certificate
t.PrivateKey = s.PrivateKey
t.Meta = s.Meta
}
func InlineCertificateFromStructs(t *structs.InlineCertificateConfigEntry, s *InlineCertificate) {
if s == nil {
return
}
s.Certificate = t.Certificate
s.PrivateKey = t.PrivateKey
s.Meta = t.Meta
}
func IntentionHTTPHeaderPermissionToStructs(s *IntentionHTTPHeaderPermission, t *structs.IntentionHTTPHeaderPermission) {
if s == nil {
return
@ -678,6 +910,24 @@ func PeeringMeshConfigFromStructs(t *structs.PeeringMeshConfig, s *PeeringMeshCo
}
s.PeerThroughMeshGateways = t.PeerThroughMeshGateways
}
func ResourceReferenceToStructs(s *ResourceReference, t *structs.ResourceReference) {
if s == nil {
return
}
t.Kind = s.Kind
t.Name = s.Name
t.SectionName = s.SectionName
t.EnterpriseMeta = enterpriseMetaToStructs(s.EnterpriseMeta)
}
func ResourceReferenceFromStructs(t *structs.ResourceReference, s *ResourceReference) {
if s == nil {
return
}
s.Kind = t.Kind
s.Name = t.Name
s.SectionName = t.SectionName
s.EnterpriseMeta = enterpriseMetaFromStructs(t.EnterpriseMeta)
}
func RingHashConfigToStructs(s *RingHashConfig, t *structs.RingHashConfig) {
if s == nil {
return
@ -1038,6 +1288,106 @@ func SourceIntentionFromStructs(t *structs.SourceIntention, s *SourceIntention)
s.EnterpriseMeta = enterpriseMetaFromStructs(t.EnterpriseMeta)
s.Peer = t.Peer
}
func StatusToStructs(s *Status, t *structs.Status) {
if s == nil {
return
}
{
t.Conditions = make([]structs.Condition, len(s.Conditions))
for i := range s.Conditions {
if s.Conditions[i] != nil {
ConditionToStructs(s.Conditions[i], &t.Conditions[i])
}
}
}
}
func StatusFromStructs(t *structs.Status, s *Status) {
if s == nil {
return
}
{
s.Conditions = make([]*Condition, len(t.Conditions))
for i := range t.Conditions {
{
var x Condition
ConditionFromStructs(&t.Conditions[i], &x)
s.Conditions[i] = &x
}
}
}
}
func TCPRouteToStructs(s *TCPRoute, t *structs.TCPRouteConfigEntry) {
if s == nil {
return
}
{
t.Parents = make([]structs.ResourceReference, len(s.Parents))
for i := range s.Parents {
if s.Parents[i] != nil {
ResourceReferenceToStructs(s.Parents[i], &t.Parents[i])
}
}
}
{
t.Services = make([]structs.TCPService, len(s.Services))
for i := range s.Services {
if s.Services[i] != nil {
TCPServiceToStructs(s.Services[i], &t.Services[i])
}
}
}
t.Meta = s.Meta
if s.Status != nil {
StatusToStructs(s.Status, &t.Status)
}
}
func TCPRouteFromStructs(t *structs.TCPRouteConfigEntry, s *TCPRoute) {
if s == nil {
return
}
{
s.Parents = make([]*ResourceReference, len(t.Parents))
for i := range t.Parents {
{
var x ResourceReference
ResourceReferenceFromStructs(&t.Parents[i], &x)
s.Parents[i] = &x
}
}
}
{
s.Services = make([]*TCPService, len(t.Services))
for i := range t.Services {
{
var x TCPService
TCPServiceFromStructs(&t.Services[i], &x)
s.Services[i] = &x
}
}
}
s.Meta = t.Meta
{
var x Status
StatusFromStructs(&t.Status, &x)
s.Status = &x
}
}
func TCPServiceToStructs(s *TCPService, t *structs.TCPService) {
if s == nil {
return
}
t.Name = s.Name
t.Weight = int(s.Weight)
t.EnterpriseMeta = enterpriseMetaToStructs(s.EnterpriseMeta)
}
func TCPServiceFromStructs(t *structs.TCPService, s *TCPService) {
if s == nil {
return
}
s.Name = t.Name
s.Weight = int32(t.Weight)
s.EnterpriseMeta = enterpriseMetaFromStructs(t.EnterpriseMeta)
}
func TransparentProxyConfigToStructs(s *TransparentProxyConfig, t *structs.TransparentProxyConfig) {
if s == nil {
return

View File

@ -314,3 +314,25 @@ func EnvoyExtensionsFromStructs(args []structs.EnvoyExtension) []*EnvoyExtension
return o
}
func apiGatewayProtocolFromStructs(a structs.APIGatewayListenerProtocol) APIGatewayListenerProtocol {
switch a {
case structs.ListenerProtocolHTTP:
return APIGatewayListenerProtocol_ListenerProtocolHTTP
case structs.ListenerProtocolTCP:
return APIGatewayListenerProtocol_ListenerProtocolTCP
default:
return APIGatewayListenerProtocol_ListenerProtocolHTTP
}
}
func apiGatewayProtocolToStructs(a APIGatewayListenerProtocol) structs.APIGatewayListenerProtocol {
switch a {
case APIGatewayListenerProtocol_ListenerProtocolHTTP:
return structs.ListenerProtocolHTTP
case APIGatewayListenerProtocol_ListenerProtocolTCP:
return structs.ListenerProtocolTCP
default:
return structs.ListenerProtocolHTTP
}
}

View File

@ -416,3 +416,123 @@ func (msg *DestinationConfig) MarshalBinary() ([]byte, error) {
func (msg *DestinationConfig) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *APIGateway) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *APIGateway) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *Status) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *Status) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *Condition) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *Condition) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *APIGatewayListener) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *APIGatewayListener) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *APIGatewayTLSConfiguration) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *APIGatewayTLSConfiguration) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *ResourceReference) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *ResourceReference) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *BoundAPIGateway) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *BoundAPIGateway) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *BoundAPIGatewayListener) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *BoundAPIGatewayListener) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *InlineCertificate) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *InlineCertificate) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *HTTPRoute) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *HTTPRoute) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *TCPRoute) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *TCPRoute) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *TCPService) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *TCPService) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,11 @@ enum Kind {
KindIngressGateway = 3;
KindServiceIntentions = 4;
KindServiceDefaults = 5;
KindInlineCertificate = 6;
KindAPIGateway = 7;
KindBoundAPIGateway = 8;
KindHTTPRoute = 9;
KindTCPRoute = 10;
}
message ConfigEntry {
@ -575,3 +580,156 @@ message DestinationConfig {
// mog: func-to=int func-from=int32
int32 Port = 2;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.APIGatewayConfigEntry
// output=config_entry.gen.go
// name=Structs
// ignore-fields=Kind,Name,RaftIndex,EnterpriseMeta
message APIGateway {
map<string, string> Meta = 1;
repeated APIGatewayListener Listeners = 2;
Status Status = 3;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.Status
// output=config_entry.gen.go
// name=Structs
message Status {
repeated Condition Conditions = 1;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.Condition
// output=config_entry.gen.go
// name=Structs
message Condition {
string Status = 1;
string Reason = 2;
string Message = 3;
ResourceReference Resource = 4;
// mog: func-to=timeToStructs func-from=timeFromStructs
google.protobuf.Timestamp LastTransitionTime = 5;
}
enum APIGatewayListenerProtocol {
ListenerProtocolHTTP = 0;
ListenerProtocolTCP = 1;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.APIGatewayListener
// output=config_entry.gen.go
// name=Structs
message APIGatewayListener {
string Name = 1;
string Hostname = 2;
// mog: func-to=int func-from=int32
int32 Port = 3;
// mog: func-to=apiGatewayProtocolToStructs func-from=apiGatewayProtocolFromStructs
APIGatewayListenerProtocol Protocol = 4;
APIGatewayTLSConfiguration TLS = 5;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.APIGatewayTLSConfiguration
// output=config_entry.gen.go
// name=Structs
message APIGatewayTLSConfiguration {
repeated ResourceReference Certificates = 1;
// mog: func-from=tlsVersionFromStructs func-to=tlsVersionToStructs
string MinVersion = 2;
// mog: func-from=tlsVersionFromStructs func-to=tlsVersionToStructs
string MaxVersion = 3;
// mog: func-from=cipherSuitesFromStructs func-to=cipherSuitesToStructs
repeated string CipherSuites = 4;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.ResourceReference
// output=config_entry.gen.go
// name=Structs
message ResourceReference {
string Kind = 1;
string Name = 2;
string SectionName = 3;
// mog: func-to=enterpriseMetaToStructs func-from=enterpriseMetaFromStructs
common.EnterpriseMeta EnterpriseMeta = 4;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.BoundAPIGatewayConfigEntry
// output=config_entry.gen.go
// name=Structs
// ignore-fields=Kind,Name,RaftIndex,EnterpriseMeta
message BoundAPIGateway {
map<string, string> Meta = 1;
repeated BoundAPIGatewayListener Listeners = 2;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.BoundAPIGatewayListener
// output=config_entry.gen.go
// name=Structs
message BoundAPIGatewayListener {
string Name = 1;
repeated ResourceReference Certificates = 2;
repeated ResourceReference Routes = 3;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.InlineCertificateConfigEntry
// output=config_entry.gen.go
// name=Structs
// ignore-fields=Kind,Name,RaftIndex,EnterpriseMeta
message InlineCertificate {
map<string, string> Meta = 1;
string Certificate = 2;
string PrivateKey = 3;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.HTTPRouteConfigEntry
// output=config_entry.gen.go
// name=Structs
// ignore-fields=Kind,Name,RaftIndex,EnterpriseMeta
message HTTPRoute {
map<string, string> Meta = 1;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.TCPRouteConfigEntry
// output=config_entry.gen.go
// name=Structs
// ignore-fields=Kind,Name,RaftIndex,EnterpriseMeta
message TCPRoute {
map<string, string> Meta = 1;
repeated ResourceReference Parents = 2;
repeated TCPService Services = 3;
Status Status = 4;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.TCPService
// output=config_entry.gen.go
// name=Structs
message TCPService {
string Name = 1;
// mog: func-to=int func-from=int32
int32 Weight = 2;
// mog: func-to=enterpriseMetaToStructs func-from=enterpriseMetaFromStructs
common.EnterpriseMeta EnterpriseMeta = 4;
}

View File

@ -59,20 +59,35 @@ const (
Topic_ServiceList Topic = 7
// ServiceDefaults topic contains events for changes to service-defaults.
Topic_ServiceDefaults Topic = 8
// APIGateway topic contains events for changes to api-gateways.
Topic_APIGateway Topic = 9
// TCPRoute topic contains events for changes to tcp-routes.
Topic_TCPRoute Topic = 10
// HTTPRoute topic contains events for changes to http-routes.
Topic_HTTPRoute Topic = 11
// InlineCertificate topic contains events for changes to inline-certificates.
Topic_InlineCertificate Topic = 12
// BoundAPIGateway topic contains events for changes to bound-api-gateways.
Topic_BoundAPIGateway Topic = 13
)
// Enum value maps for Topic.
var (
Topic_name = map[int32]string{
0: "Unknown",
1: "ServiceHealth",
2: "ServiceHealthConnect",
3: "MeshConfig",
4: "ServiceResolver",
5: "IngressGateway",
6: "ServiceIntentions",
7: "ServiceList",
8: "ServiceDefaults",
0: "Unknown",
1: "ServiceHealth",
2: "ServiceHealthConnect",
3: "MeshConfig",
4: "ServiceResolver",
5: "IngressGateway",
6: "ServiceIntentions",
7: "ServiceList",
8: "ServiceDefaults",
9: "APIGateway",
10: "TCPRoute",
11: "HTTPRoute",
12: "InlineCertificate",
13: "BoundAPIGateway",
}
Topic_value = map[string]int32{
"Unknown": 0,
@ -84,6 +99,11 @@ var (
"ServiceIntentions": 6,
"ServiceList": 7,
"ServiceDefaults": 8,
"APIGateway": 9,
"TCPRoute": 10,
"HTTPRoute": 11,
"InlineCertificate": 12,
"BoundAPIGateway": 13,
}
)
@ -965,8 +985,8 @@ var file_proto_pbsubscribe_subscribe_proto_rawDesc = []byte{
0x6d, 0x6f, 0x6e, 0x2e, 0x45, 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, 0xb7,
0x01, 0x0a, 0x05, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x2a, 0x90,
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, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
@ -977,26 +997,31 @@ var file_proto_pbsubscribe_subscribe_proto_rawDesc = []byte{
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x10, 0x06, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73,
0x74, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65,
0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x10, 0x08, 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, 0x5f, 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, 0x44,
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, 0x06, 0xe2, 0x86, 0x04, 0x02,
0x08, 0x02, 0x30, 0x01, 0x42, 0x92, 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, 0x2d, 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, 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,
0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x10, 0x08, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x50, 0x49, 0x47,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x10, 0x09, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x43, 0x50, 0x52,
0x6f, 0x75, 0x74, 0x65, 0x10, 0x0a, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x54, 0x54, 0x50, 0x52, 0x6f,
0x75, 0x74, 0x65, 0x10, 0x0b, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43,
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x10, 0x0c, 0x12, 0x13, 0x0a, 0x0f,
0x42, 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x50, 0x49, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x10,
0x0d, 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, 0x5f, 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, 0x44, 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, 0x06, 0xe2, 0x86, 0x04, 0x02, 0x08, 0x02, 0x30, 0x01, 0x42, 0x92, 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, 0x2d, 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, 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 (

View File

@ -78,6 +78,21 @@ enum Topic {
// ServiceDefaults topic contains events for changes to service-defaults.
ServiceDefaults = 8;
// APIGateway topic contains events for changes to api-gateways.
APIGateway = 9;
// TCPRoute topic contains events for changes to tcp-routes.
TCPRoute = 10;
// HTTPRoute topic contains events for changes to http-routes.
HTTPRoute = 11;
// InlineCertificate topic contains events for changes to inline-certificates.
InlineCertificate = 12;
// BoundAPIGateway topic contains events for changes to bound-api-gateways.
BoundAPIGateway = 13;
}
message NamedSubject {

View File

@ -1255,7 +1255,7 @@
},
{
"title": "Network Segments",
"routes":[
"routes": [
{
"title": "Network Segments Overview",
"path": "enterprise/network-segments/network-segments-overview"
@ -1323,8 +1323,8 @@
"path": "api-gateway/usage/usage"
},
{
"title": "Reroute HTTP Requests",
"path": "api-gateway/usage/reroute-http-requests"
"title": "Reroute HTTP Requests",
"path": "api-gateway/usage/reroute-http-requests"
},
{
"title": "Route Traffic to Peered Services",
@ -1334,7 +1334,6 @@
"title": "Error Messages",
"path": "api-gateway/usage/errors"
}
]
},
{