diff --git a/agent/config_endpoint_test.go b/agent/config_endpoint_test.go index 1fcf58b4ef..9d4d220a68 100644 --- a/agent/config_endpoint_test.go +++ b/agent/config_endpoint_test.go @@ -182,6 +182,63 @@ func TestConfig_Apply(t *testing.T) { } } +func TestConfig_Apply_TerminatingGateway(t *testing.T) { + t.Parallel() + + a := NewTestAgent(t, t.Name(), "") + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + // Create some config entries. + body := bytes.NewBuffer([]byte(` + { + "Kind": "terminating-gateway", + "Name": "west-gw-01", + "Services": [ + { + "Name": "web", + "CAFile": "/etc/web/ca.crt", + "CertFile": "/etc/web/client.crt", + "KeyFile": "/etc/web/tls.key" + }, + { + "Name": "api" + } + ] + }`)) + + req, _ := http.NewRequest("PUT", "/v1/config", body) + resp := httptest.NewRecorder() + _, err := a.srv.ConfigApply(resp, req) + require.NoError(t, err) + require.Equal(t, 200, resp.Code, "!200 Response Code: %s", resp.Body.String()) + + // Get the remaining entry. + { + args := structs.ConfigEntryQuery{ + Kind: structs.TerminatingGateway, + Name: "west-gw-01", + Datacenter: "dc1", + } + var out structs.ConfigEntryResponse + require.NoError(t, a.RPC("ConfigEntry.Get", &args, &out)) + require.NotNil(t, out.Entry) + got := out.Entry.(*structs.TerminatingGatewayConfigEntry) + expect := []structs.LinkedService{ + { + Name: "web", + CAFile: "/etc/web/ca.crt", + CertFile: "/etc/web/client.crt", + KeyFile: "/etc/web/tls.key", + }, + { + Name: "api", + }, + } + require.Equal(t, expect, got.Services) + } +} + func TestConfig_Apply_ProxyDefaultsMeshGateway(t *testing.T) { t.Parallel() diff --git a/agent/consul/state/config_entry.go b/agent/consul/state/config_entry.go index ddab4b08ed..fd5007c924 100644 --- a/agent/consul/state/config_entry.go +++ b/agent/consul/state/config_entry.go @@ -2,7 +2,6 @@ package state import ( "fmt" - "github.com/hashicorp/consul/agent/consul/discoverychain" "github.com/hashicorp/consul/agent/structs" memdb "github.com/hashicorp/go-memdb" @@ -326,6 +325,7 @@ func (s *Store) validateProposedConfigEntryInGraph( case structs.ServiceSplitter: case structs.ServiceResolver: case structs.IngressGateway: + case structs.TerminatingGateway: default: return fmt.Errorf("unhandled kind %q during validation of %q", kind, name) } diff --git a/agent/structs/config_entry.go b/agent/structs/config_entry.go index dea8f5d66c..8f783c99f3 100644 --- a/agent/structs/config_entry.go +++ b/agent/structs/config_entry.go @@ -15,12 +15,13 @@ import ( ) const ( - ServiceDefaults string = "service-defaults" - ProxyDefaults string = "proxy-defaults" - ServiceRouter string = "service-router" - ServiceSplitter string = "service-splitter" - ServiceResolver string = "service-resolver" - IngressGateway string = "ingress-gateway" + ServiceDefaults string = "service-defaults" + ProxyDefaults string = "proxy-defaults" + ServiceRouter string = "service-router" + ServiceSplitter string = "service-splitter" + ServiceResolver string = "service-resolver" + IngressGateway string = "ingress-gateway" + TerminatingGateway string = "terminating-gateway" ProxyConfigGlobal string = "global" @@ -386,6 +387,15 @@ func ConfigEntryDecodeRulesForKind(kind string) (skipWhenPatching []string, tran }, map[string]string{ "service_subset": "servicesubset", }, nil + case TerminatingGateway: + return []string{ + "services", + "Services", + }, map[string]string{ + "ca_file": "cafile", + "cert_file": "certfile", + "key_file": "keyfile", + }, nil default: return nil, nil, fmt.Errorf("kind %q should be explicitly handled here", kind) } @@ -478,6 +488,8 @@ func MakeConfigEntry(kind, name string) (ConfigEntry, error) { return &ServiceResolverConfigEntry{Name: name}, nil case IngressGateway: return &IngressGatewayConfigEntry{Name: name}, nil + case TerminatingGateway: + return &TerminatingGatewayConfigEntry{Name: name}, nil default: return nil, fmt.Errorf("invalid config entry kind: %s", kind) } @@ -489,7 +501,7 @@ func ValidateConfigEntryKind(kind string) bool { return true case ServiceRouter, ServiceSplitter, ServiceResolver: return true - case IngressGateway: + case IngressGateway, TerminatingGateway: return true default: return false diff --git a/agent/structs/config_entry_gateways.go b/agent/structs/config_entry_gateways.go index ae9e21f7b5..db41f2a7c3 100644 --- a/agent/structs/config_entry_gateways.go +++ b/agent/structs/config_entry_gateways.go @@ -159,3 +159,121 @@ func (e *IngressGatewayConfigEntry) GetEnterpriseMeta() *EnterpriseMeta { return &e.EnterpriseMeta } + +// TerminatingGatewayConfigEntry manages the configuration for a terminating service +// with the given name. +type TerminatingGatewayConfigEntry struct { + Kind string + Name string + Services []LinkedService + + EnterpriseMeta `hcl:",squash" mapstructure:",squash"` + RaftIndex +} + +// A LinkedService is a service represented by a terminating gateway +type LinkedService struct { + // Name is the name of the service, as defined in Consul's catalog + Name string `json:",omitempty"` + + // CAFile is the optional path to a CA certificate to use for TLS connections + // from the gateway to the linked service + CAFile string `json:",omitempty"` + + // CertFile is the optional path to a client certificate to use for TLS connections + // from the gateway to the linked service + CertFile string `json:",omitempty"` + + // KeyFile is the optional path to a private key to use for TLS connections + // from the gateway to the linked service + KeyFile string `json:",omitempty"` + + EnterpriseMeta `hcl:",squash" mapstructure:",squash"` +} + +func (e *TerminatingGatewayConfigEntry) GetKind() string { + return TerminatingGateway +} + +func (e *TerminatingGatewayConfigEntry) GetName() string { + if e == nil { + return "" + } + + return e.Name +} + +func (e *TerminatingGatewayConfigEntry) Normalize() error { + if e == nil { + return fmt.Errorf("config entry is nil") + } + + e.Kind = TerminatingGateway + + for i := range e.Services { + e.Services[i].EnterpriseMeta.Normalize() + } + e.EnterpriseMeta.Normalize() + + return nil +} + +func (e *TerminatingGatewayConfigEntry) Validate() error { + seen := make(map[ServiceID]bool) + + for _, svc := range e.Services { + if svc.Name == "" { + return fmt.Errorf("Service name cannot be blank.") + } + + ns := svc.NamespaceOrDefault() + if ns == WildcardSpecifier { + return fmt.Errorf("Wildcard namespace is not supported for terminating gateway services") + } + + // Check for duplicates within the entry + cid := NewServiceID(svc.Name, &svc.EnterpriseMeta) + if ok := seen[cid]; ok { + return fmt.Errorf("Service %q was specified more than once within a namespace", cid.String()) + } + seen[cid] = true + + // If any TLS config flag was specified, all must be + if (svc.CAFile != "" || svc.CertFile != "" || svc.KeyFile != "") && + !(svc.CAFile != "" && svc.CertFile != "" && svc.KeyFile != "") { + + return fmt.Errorf("Service %q must have a CertFile, CAFile, and KeyFile specified for TLS origination", svc.Name) + } + } + return nil +} + +func (e *TerminatingGatewayConfigEntry) CanRead(authz acl.Authorizer) bool { + var authzContext acl.AuthorizerContext + e.FillAuthzContext(&authzContext) + + return authz.OperatorRead(&authzContext) == acl.Allow +} + +func (e *TerminatingGatewayConfigEntry) CanWrite(authz acl.Authorizer) bool { + var authzContext acl.AuthorizerContext + e.FillAuthzContext(&authzContext) + + return authz.OperatorWrite(&authzContext) == acl.Allow +} + +func (e *TerminatingGatewayConfigEntry) GetRaftIndex() *RaftIndex { + if e == nil { + return &RaftIndex{} + } + + return &e.RaftIndex +} + +func (e *TerminatingGatewayConfigEntry) GetEnterpriseMeta() *EnterpriseMeta { + if e == nil { + return nil + } + + return &e.EnterpriseMeta +} diff --git a/agent/structs/config_entry_gateways_test.go b/agent/structs/config_entry_gateways_test.go index f9065e857c..9f90e51a23 100644 --- a/agent/structs/config_entry_gateways_test.go +++ b/agent/structs/config_entry_gateways_test.go @@ -185,3 +185,161 @@ func TestIngressConfigEntry_Validate(t *testing.T) { }) } } + +func TestTerminatingConfigEntry_Validate(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + entry TerminatingGatewayConfigEntry + expectErr string + }{ + { + name: "service conflict", + entry: TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Services: []LinkedService{ + { + Name: "foo", + }, + { + Name: "foo", + }, + }, + }, + expectErr: "specified more than once", + }, + { + name: "blank service name", + entry: TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Services: []LinkedService{ + { + Name: "", + }, + }, + }, + expectErr: "Service name cannot be blank.", + }, + { + name: "not all TLS options provided-1", + entry: TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Services: []LinkedService{ + { + Name: "web", + CAFile: "ca.crt", + }, + }, + }, + expectErr: "must have a CertFile, CAFile, and KeyFile", + }, + { + name: "not all TLS options provided-2", + entry: TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Services: []LinkedService{ + { + Name: "web", + CertFile: "client.crt", + }, + }, + }, + expectErr: "must have a CertFile, CAFile, and KeyFile", + }, + { + name: "not all TLS options provided-3", + entry: TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Services: []LinkedService{ + { + Name: "web", + KeyFile: "tls.key", + }, + }, + }, + expectErr: "must have a CertFile, CAFile, and KeyFile", + }, + { + name: "not all TLS options provided-4", + entry: TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Services: []LinkedService{ + { + Name: "web", + CAFile: "ca.crt", + KeyFile: "tls.key", + }, + }, + }, + expectErr: "must have a CertFile, CAFile, and KeyFile", + }, + { + name: "not all TLS options provided-5", + entry: TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Services: []LinkedService{ + { + Name: "web", + CAFile: "ca.crt", + CertFile: "client.crt", + }, + }, + }, + expectErr: "must have a CertFile, CAFile, and KeyFile", + }, + { + name: "not all TLS options provided-6", + entry: TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Services: []LinkedService{ + { + Name: "web", + KeyFile: "tls.key", + CertFile: "client.crt", + }, + }, + }, + expectErr: "must have a CertFile, CAFile, and KeyFile", + }, + { + name: "all TLS options provided", + entry: TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Services: []LinkedService{ + { + Name: "web", + CAFile: "ca.crt", + CertFile: "client.crt", + KeyFile: "tls.key", + }, + }, + }, + }, + } + + for _, test := range cases { + // We explicitly copy the variable for the range statement so that can run + // tests in parallel. + tc := test + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + err := tc.entry.Validate() + if tc.expectErr != "" { + require.Error(t, err) + requireContainsLower(t, err.Error(), tc.expectErr) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/agent/structs/config_entry_test.go b/agent/structs/config_entry_test.go index 4de8288e3d..076d9f3d9c 100644 --- a/agent/structs/config_entry_test.go +++ b/agent/structs/config_entry_test.go @@ -647,6 +647,63 @@ func TestDecodeConfigEntry(t *testing.T) { }, }, }, + { + name: "terminating-gateway: kitchen sink", + snake: ` + kind = "terminating-gateway" + name = "terminating-gw-west" + services = [ + { + name = "payments", + ca_file = "/etc/payments/ca.pem", + cert_file = "/etc/payments/cert.pem", + key_file = "/etc/payments/tls.key", + }, + { + name = "*", + ca_file = "/etc/all/ca.pem", + cert_file = "/etc/all/cert.pem", + key_file = "/etc/all/tls.key", + }, + ] + `, + camel: ` + Kind = "terminating-gateway" + Name = "terminating-gw-west" + Services = [ + { + Name = "payments", + CAFile = "/etc/payments/ca.pem", + CertFile = "/etc/payments/cert.pem", + KeyFile = "/etc/payments/tls.key", + }, + { + Name = "*", + CAFile = "/etc/all/ca.pem", + CertFile = "/etc/all/cert.pem", + KeyFile = "/etc/all/tls.key", + }, + ] + `, + expect: &TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Services: []LinkedService{ + { + Name: "payments", + CAFile: "/etc/payments/ca.pem", + CertFile: "/etc/payments/cert.pem", + KeyFile: "/etc/payments/tls.key", + }, + { + Name: "*", + CAFile: "/etc/all/ca.pem", + CertFile: "/etc/all/cert.pem", + KeyFile: "/etc/all/tls.key", + }, + }, + }, + }, } { tc := tc diff --git a/api/config_entry.go b/api/config_entry.go index 17e75000fa..6279a7c5a2 100644 --- a/api/config_entry.go +++ b/api/config_entry.go @@ -12,12 +12,13 @@ import ( ) const ( - ServiceDefaults string = "service-defaults" - ProxyDefaults string = "proxy-defaults" - ServiceRouter string = "service-router" - ServiceSplitter string = "service-splitter" - ServiceResolver string = "service-resolver" - IngressGateway string = "ingress-gateway" + ServiceDefaults string = "service-defaults" + ProxyDefaults string = "proxy-defaults" + ServiceRouter string = "service-router" + ServiceSplitter string = "service-splitter" + ServiceResolver string = "service-resolver" + IngressGateway string = "ingress-gateway" + TerminatingGateway string = "terminating-gateway" ProxyConfigGlobal string = "global" ) @@ -141,11 +142,6 @@ func (p *ProxyConfigEntry) GetModifyIndex() uint64 { return p.ModifyIndex } -type rawEntryListResponse struct { - kind string - Entries []map[string]interface{} -} - func makeConfigEntry(kind, name string) (ConfigEntry, error) { switch kind { case ServiceDefaults: @@ -160,6 +156,8 @@ func makeConfigEntry(kind, name string) (ConfigEntry, error) { return &ServiceResolverConfigEntry{Kind: kind, Name: name}, nil case IngressGateway: return &IngressGatewayConfigEntry{Kind: kind, Name: name}, nil + case TerminatingGateway: + return &TerminatingGatewayConfigEntry{Kind: kind, Name: name}, nil default: return nil, fmt.Errorf("invalid config entry kind: %s", kind) } diff --git a/api/config_entry_gateways.go b/api/config_entry_gateways.go index 1812032d2c..7314c67630 100644 --- a/api/config_entry_gateways.go +++ b/api/config_entry_gateways.go @@ -82,3 +82,67 @@ func (i *IngressGatewayConfigEntry) GetCreateIndex() uint64 { func (i *IngressGatewayConfigEntry) GetModifyIndex() uint64 { return i.ModifyIndex } + +// TerminatingGatewayConfigEntry manages the configuration for a terminating gateway +// with the given name. +type TerminatingGatewayConfigEntry struct { + // Kind of the config entry. This should be set to api.TerminatingGateway. + Kind string + + // Name is used to match the config entry with its associated terminating gateway + // service. This should match the name provided in the service definition. + Name string + + // Services is a list of service names represented by the terminating gateway. + Services []LinkedService `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 + + // Namespace is the namespace the config entry is associated with + // Namespacing is a Consul Enterprise feature. + Namespace string `json:",omitempty"` +} + +// A LinkedService is a service represented by a terminating gateway +type LinkedService struct { + // The namespace the service is registered in + Namespace string `json:",omitempty"` + + // Name is the name of the service, as defined in Consul's catalog + Name string `json:",omitempty"` + + // CAFile is the optional path to a CA certificate to use for TLS connections + // from the gateway to the linked service + CAFile string `json:",omitempty"` + + // CertFile is the optional path to a client certificate to use for TLS connections + // from the gateway to the linked service + CertFile string `json:",omitempty"` + + // KeyFile is the optional path to a private key to use for TLS connections + // from the gateway to the linked service + KeyFile string `json:",omitempty"` +} + +func (g *TerminatingGatewayConfigEntry) GetKind() string { + return g.Kind +} + +func (g *TerminatingGatewayConfigEntry) GetName() string { + return g.Name +} + +func (g *TerminatingGatewayConfigEntry) GetCreateIndex() uint64 { + return g.CreateIndex +} + +func (g *TerminatingGatewayConfigEntry) GetModifyIndex() uint64 { + return g.ModifyIndex +} diff --git a/api/config_entry_gateways_test.go b/api/config_entry_gateways_test.go index 71caaf92dc..9db4fa496f 100644 --- a/api/config_entry_gateways_test.go +++ b/api/config_entry_gateways_test.go @@ -125,3 +125,117 @@ func TestAPI_ConfigEntries_IngressGateway(t *testing.T) { entry, qm, err = config_entries.Get(IngressGateway, "foo", nil) require.Error(t, err) } + +func TestAPI_ConfigEntries_TerminatingGateway(t *testing.T) { + t.Parallel() + c, s := makeClient(t) + defer s.Stop() + + configEntries := c.ConfigEntries() + + terminating1 := &TerminatingGatewayConfigEntry{ + Kind: TerminatingGateway, + Name: "foo", + } + + terminating2 := &TerminatingGatewayConfigEntry{ + Kind: TerminatingGateway, + Name: "bar", + } + + // set it + _, wm, err := configEntries.Set(terminating1, nil) + require.NoError(t, err) + require.NotNil(t, wm) + require.NotEqual(t, 0, wm.RequestTime) + + // also set the second one + _, wm, err = configEntries.Set(terminating2, nil) + require.NoError(t, err) + require.NotNil(t, wm) + require.NotEqual(t, 0, wm.RequestTime) + + // get it + entry, qm, err := configEntries.Get(TerminatingGateway, "foo", nil) + require.NoError(t, err) + require.NotNil(t, qm) + require.NotEqual(t, 0, qm.RequestTime) + + // verify it + readTerminating, ok := entry.(*TerminatingGatewayConfigEntry) + require.True(t, ok) + require.Equal(t, terminating1.Kind, readTerminating.Kind) + require.Equal(t, terminating1.Name, readTerminating.Name) + + // update it + terminating1.Services = []LinkedService{ + { + Name: "web", + CAFile: "/etc/web/ca.crt", + CertFile: "/etc/web/client.crt", + KeyFile: "/etc/web/tls.key", + }, + } + + // CAS fail + written, _, err := configEntries.CAS(terminating1, 0, nil) + require.NoError(t, err) + require.False(t, written) + + // CAS success + written, wm, err = configEntries.CAS(terminating1, readTerminating.ModifyIndex, nil) + require.NoError(t, err) + require.NotNil(t, wm) + require.NotEqual(t, 0, wm.RequestTime) + require.True(t, written) + + // update no cas + terminating2.Services = []LinkedService{ + { + Name: "*", + CAFile: "/etc/certs/ca.crt", + CertFile: "/etc/certs/client.crt", + KeyFile: "/etc/certs/tls.key", + }, + } + _, wm, err = configEntries.Set(terminating2, nil) + require.NoError(t, err) + require.NotNil(t, wm) + require.NotEqual(t, 0, wm.RequestTime) + + // list them + entries, qm, err := configEntries.List(TerminatingGateway, nil) + require.NoError(t, err) + require.NotNil(t, qm) + require.NotEqual(t, 0, qm.RequestTime) + require.Len(t, entries, 2) + + for _, entry = range entries { + switch entry.GetName() { + case "foo": + // this also verifies that the update value was persisted and + // the updated values are seen + readTerminating, ok = entry.(*TerminatingGatewayConfigEntry) + require.True(t, ok) + require.Equal(t, terminating1.Kind, readTerminating.Kind) + require.Equal(t, terminating1.Name, readTerminating.Name) + require.Equal(t, terminating1.Services, readTerminating.Services) + case "bar": + readTerminating, ok = entry.(*TerminatingGatewayConfigEntry) + require.True(t, ok) + require.Equal(t, terminating2.Kind, readTerminating.Kind) + require.Equal(t, terminating2.Name, readTerminating.Name) + require.Equal(t, terminating2.Services, readTerminating.Services) + } + } + + // delete it + wm, err = configEntries.Delete(TerminatingGateway, "foo", nil) + require.NoError(t, err) + require.NotNil(t, wm) + require.NotEqual(t, 0, wm.RequestTime) + + // verify deletion + entry, qm, err = configEntries.Get(TerminatingGateway, "foo", nil) + require.Error(t, err) +} diff --git a/api/config_entry_test.go b/api/config_entry_test.go index 946ed3feb5..ac4998913b 100644 --- a/api/config_entry_test.go +++ b/api/config_entry_test.go @@ -674,6 +674,50 @@ func TestDecodeConfigEntry(t *testing.T) { }, }, }, + { + name: "terminating-gateway", + body: ` + { + "Kind": "terminating-gateway", + "Name": "terminating-west", + "Services": [ + { + "Namespace": "foo", + "Name": "web", + "CAFile": "/etc/ca.pem", + "CertFile": "/etc/cert.pem", + "KeyFile": "/etc/tls.key" + }, + { + "Name": "api" + }, + { + "Namespace": "bar", + "Name": "*" + } + ] + }`, + expect: &TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-west", + Services: []LinkedService{ + { + Namespace: "foo", + Name: "web", + CAFile: "/etc/ca.pem", + CertFile: "/etc/cert.pem", + KeyFile: "/etc/tls.key", + }, + { + Name: "api", + }, + { + Namespace: "bar", + Name: "*", + }, + }, + }, + }, } { tc := tc diff --git a/command/config/write/config_write_test.go b/command/config/write/config_write_test.go index 5ae4413610..5cb1527df0 100644 --- a/command/config/write/config_write_test.go +++ b/command/config/write/config_write_test.go @@ -245,6 +245,121 @@ func TestParseConfigEntry(t *testing.T) { }, }, }, + { + name: "terminating-gateway", + snake: ` + kind = "terminating-gateway" + name = "terminating-gw-west" + namespace = "default" + services = [ + { + name = "billing" + namespace = "biz" + ca_file = "/etc/ca.crt" + cert_file = "/etc/client.crt" + key_file = "/etc/tls.key" + }, + { + name = "*" + namespace = "ops" + } + ] + `, + camel: ` + Kind = "terminating-gateway" + Name = "terminating-gw-west" + Namespace = "default" + Services = [ + { + Name = "billing" + Namespace = "biz" + CAFile = "/etc/ca.crt" + CertFile = "/etc/client.crt" + KeyFile = "/etc/tls.key" + }, + { + Name = "*" + Namespace = "ops" + } + ] + `, + snakeJSON: ` + { + "kind": "terminating-gateway", + "name": "terminating-gw-west", + "namespace": "default", + "services": [ + { + "name": "billing", + "namespace": "biz", + "ca_file": "/etc/ca.crt", + "cert_file": "/etc/client.crt", + "key_file": "/etc/tls.key" + }, + { + "name": "*", + "namespace": "ops" + } + ] + } + `, + camelJSON: ` + { + "Kind": "terminating-gateway", + "Name": "terminating-gw-west", + "Namespace": "default", + "Services": [ + { + "Name": "billing", + "Namespace": "biz", + "CAFile": "/etc/ca.crt", + "CertFile": "/etc/client.crt", + "KeyFile": "/etc/tls.key" + }, + { + "Name": "*", + "Namespace": "ops" + } + ] + } + `, + expect: &api.TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Namespace: "default", + Services: []api.LinkedService{ + { + Name: "billing", + Namespace: "biz", + CAFile: "/etc/ca.crt", + CertFile: "/etc/client.crt", + KeyFile: "/etc/tls.key", + }, + { + Name: "*", + Namespace: "ops", + }, + }, + }, + expectJSON: &api.TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating-gw-west", + Namespace: "default", + Services: []api.LinkedService{ + { + Name: "billing", + Namespace: "biz", + CAFile: "/etc/ca.crt", + CertFile: "/etc/client.crt", + KeyFile: "/etc/tls.key", + }, + { + Name: "*", + Namespace: "ops", + }, + }, + }, + }, { name: "service-defaults", snake: ` @@ -1127,7 +1242,6 @@ func TestParseConfigEntry(t *testing.T) { snake: ` kind = "ingress-gateway" name = "ingress-web" - listeners = [ { port = 8080