Skip to content

Commit

Permalink
Merge pull request #8099 from hashicorp/gateway-services-endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
freddygv authored Jun 12, 2020
2 parents 166a8b2 + d97cff0 commit 965c80e
Show file tree
Hide file tree
Showing 32 changed files with 1,418 additions and 957 deletions.
2 changes: 1 addition & 1 deletion agent/cache-types/gateway_services.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (g *GatewayServices) Fetch(opts cache.FetchOptions, req cache.Request) (cac

// Fetch
var reply structs.IndexedGatewayServices
if err := g.RPC.RPC("Internal.GatewayServices", reqReal, &reply); err != nil {
if err := g.RPC.RPC("Catalog.GatewayServices", reqReal, &reply); err != nil {
return result, err
}

Expand Down
6 changes: 3 additions & 3 deletions agent/cache-types/gateway_services_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestGatewayServices(t *testing.T) {
// Expect the proper RPC call. This also sets the expected value
// since that is return-by-pointer in the arguments.
var resp *structs.IndexedGatewayServices
rpc.On("RPC", "Internal.GatewayServices", mock.Anything, mock.Anything).Return(nil).
rpc.On("RPC", "Catalog.GatewayServices", mock.Anything, mock.Anything).Return(nil).
Run(func(args mock.Arguments) {
req := args.Get(1).(*structs.ServiceSpecificRequest)
require.Equal(t, uint64(24), req.QueryOptions.MinQueryIndex)
Expand All @@ -27,8 +27,8 @@ func TestGatewayServices(t *testing.T) {

services := structs.GatewayServices{
{
Service: structs.NewServiceID("api", nil),
Gateway: structs.NewServiceID("gateway", nil),
Service: structs.NewServiceName("api", nil),
Gateway: structs.NewServiceName("gateway", nil),
GatewayKind: structs.ServiceKindIngressGateway,
Port: 1234,
CAFile: "api/ca.crt",
Expand Down
42 changes: 42 additions & 0 deletions agent/catalog_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,45 @@ RETRY_ONCE:
[]metrics.Label{{Name: "node", Value: s.nodeName()}})
return &out.NodeServices, nil
}

func (s *HTTPServer) CatalogGatewayServices(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
metrics.IncrCounterWithLabels([]string{"client", "api", "catalog_gateway_services"}, 1,
[]metrics.Label{{Name: "node", Value: s.nodeName()}})

var args structs.ServiceSpecificRequest

if err := s.parseEntMetaNoWildcard(req, &args.EnterpriseMeta); err != nil {
return nil, err
}
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
return nil, nil
}

// Pull out the gateway's service name
args.ServiceName = strings.TrimPrefix(req.URL.Path, "/v1/catalog/gateway-services/")
if args.ServiceName == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing gateway name")
return nil, nil
}

// Make the RPC request
var out structs.IndexedGatewayServices
defer setMeta(resp, &out.QueryMeta)
RETRY_ONCE:
if err := s.agent.RPC("Catalog.GatewayServices", &args, &out); err != nil {
metrics.IncrCounterWithLabels([]string{"client", "rpc", "error", "catalog_gateway_services"}, 1,
[]metrics.Label{{Name: "node", Value: s.nodeName()}})
return nil, err
}
if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact {
args.AllowStale = false
args.MaxStaleDuration = 0
goto RETRY_ONCE
}
out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel()

metrics.IncrCounterWithLabels([]string{"client", "api", "success", "catalog_gateway_services"}, 1,
[]metrics.Label{{Name: "node", Value: s.nodeName()}})
return out.Services, nil
}
223 changes: 223 additions & 0 deletions agent/catalog_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package agent

import (
"fmt"
"github.com/hashicorp/consul/api"
"net/http"
"net/http/httptest"
"net/url"
Expand Down Expand Up @@ -1320,3 +1321,225 @@ func TestCatalogNodeServices_WanTranslation(t *testing.T) {
require.Equal(t, ns2.Address, "127.0.0.1")
require.Equal(t, ns2.Port, 8080)
}

func TestCatalog_GatewayServices_Terminating(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, "")
defer a.Shutdown()

testrpc.WaitForTestAgent(t, a.RPC, "dc1")

// Register a terminating gateway
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Kind: structs.ServiceKindTerminatingGateway,
Service: "terminating",
Port: 443,
},
}

var out struct{}
assert.NoError(t, a.RPC("Catalog.Register", &args, &out))

// Register two services the gateway will route to
args = structs.TestRegisterRequest(t)
args.Service.Service = "redis"
args.Check = &structs.HealthCheck{
Name: "redis",
Status: api.HealthPassing,
ServiceID: args.Service.Service,
}
assert.NoError(t, a.RPC("Catalog.Register", &args, &out))

args = structs.TestRegisterRequest(t)
args.Service.Service = "api"
args.Check = &structs.HealthCheck{
Name: "api",
Status: api.HealthPassing,
ServiceID: args.Service.Service,
}
assert.NoError(t, a.RPC("Catalog.Register", &args, &out))

// Associate the gateway and api/redis services
entryArgs := &structs.ConfigEntryRequest{
Op: structs.ConfigEntryUpsert,
Datacenter: "dc1",
Entry: &structs.TerminatingGatewayConfigEntry{
Kind: "terminating-gateway",
Name: "terminating",
Services: []structs.LinkedService{
{
Name: "api",
CAFile: "api/ca.crt",
CertFile: "api/client.crt",
KeyFile: "api/client.key",
SNI: "my-domain",
},
{
Name: "*",
CAFile: "ca.crt",
CertFile: "client.crt",
KeyFile: "client.key",
SNI: "my-alt-domain",
},
},
},
}
var entryResp bool
assert.NoError(t, a.RPC("ConfigEntry.Apply", &entryArgs, &entryResp))

retry.Run(t, func(r *retry.R) {
req, _ := http.NewRequest("GET", "/v1/catalog/gateway-services/terminating", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.CatalogGatewayServices(resp, req)
assert.NoError(r, err)

header := resp.Header().Get("X-Consul-Index")
if header == "" || header == "0" {
r.Fatalf("Bad: %v", header)
}

gatewayServices := obj.(structs.GatewayServices)

expect := structs.GatewayServices{
{
Service: structs.NewServiceName("api", nil),
Gateway: structs.NewServiceName("terminating", nil),
GatewayKind: structs.ServiceKindTerminatingGateway,
CAFile: "api/ca.crt",
CertFile: "api/client.crt",
KeyFile: "api/client.key",
SNI: "my-domain",
},
{
Service: structs.NewServiceName("redis", nil),
Gateway: structs.NewServiceName("terminating", nil),
GatewayKind: structs.ServiceKindTerminatingGateway,
CAFile: "ca.crt",
CertFile: "client.crt",
KeyFile: "client.key",
SNI: "my-alt-domain",
FromWildcard: true,
},
}

// Ignore raft index for equality
for _, s := range gatewayServices {
s.RaftIndex = structs.RaftIndex{}
}
assert.Equal(r, expect, gatewayServices)
})
}

func TestCatalog_GatewayServices_Ingress(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, "")
defer a.Shutdown()

testrpc.WaitForTestAgent(t, a.RPC, "dc1")

// Register an ingress gateway
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Kind: structs.ServiceKindTerminatingGateway,
Service: "ingress",
Port: 444,
},
}

var out struct{}
require.NoError(t, a.RPC("Catalog.Register", &args, &out))

// Register two services the gateway will route to
args = structs.TestRegisterRequest(t)
args.Service.Service = "redis"
args.Check = &structs.HealthCheck{
Name: "redis",
Status: api.HealthPassing,
ServiceID: args.Service.Service,
}
require.NoError(t, a.RPC("Catalog.Register", &args, &out))

args = structs.TestRegisterRequest(t)
args.Service.Service = "api"
args.Check = &structs.HealthCheck{
Name: "api",
Status: api.HealthPassing,
ServiceID: args.Service.Service,
}
require.NoError(t, a.RPC("Catalog.Register", &args, &out))

// Associate the gateway and db service
entryArgs := &structs.ConfigEntryRequest{
Op: structs.ConfigEntryUpsert,
Datacenter: "dc1",
Entry: &structs.IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress",
Listeners: []structs.IngressListener{
{
Port: 8888,
Services: []structs.IngressService{
{
Name: "api",
},
},
},
{
Port: 9999,
Services: []structs.IngressService{
{
Name: "redis",
},
},
},
},
},
}

var entryResp bool
require.NoError(t, a.RPC("ConfigEntry.Apply", &entryArgs, &entryResp))

retry.Run(t, func(r *retry.R) {
req, _ := http.NewRequest("GET", "/v1/catalog/gateway-services/ingress", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.CatalogGatewayServices(resp, req)
require.NoError(r, err)

header := resp.Header().Get("X-Consul-Index")
if header == "" || header == "0" {
r.Fatalf("Bad: %v", header)
}

gatewayServices := obj.(structs.GatewayServices)

expect := structs.GatewayServices{
{
Service: structs.NewServiceName("api", nil),
Gateway: structs.NewServiceName("ingress", nil),
GatewayKind: structs.ServiceKindIngressGateway,
Protocol: "tcp",
Port: 8888,
},
{
Service: structs.NewServiceName("redis", nil),
Gateway: structs.NewServiceName("ingress", nil),
GatewayKind: structs.ServiceKindIngressGateway,
Protocol: "tcp",
Port: 9999,
},
}

// Ignore raft index for equality
for _, s := range gatewayServices {
s.RaftIndex = structs.RaftIndex{}
}
require.Equal(r, expect, gatewayServices)
})
}
6 changes: 3 additions & 3 deletions agent/consul/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -1199,12 +1199,12 @@ func (f *aclFilter) allowGateway(gs *structs.GatewayService) bool {

// Need read on service and gateway. Gateway may have different EnterpriseMeta so we fill authzContext twice
gs.Gateway.FillAuthzContext(&authzContext)
if !f.allowService(gs.Gateway.ID, &authzContext) {
if !f.allowService(gs.Gateway.Name, &authzContext) {
return false
}

gs.Service.FillAuthzContext(&authzContext)
if !f.allowService(gs.Service.ID, &authzContext) {
if !f.allowService(gs.Service.Name, &authzContext) {
return false
}
return true
Expand Down Expand Up @@ -1771,7 +1771,7 @@ func (f *aclFilter) filterGatewayServices(mappings *structs.GatewayServices) {
var authzContext acl.AuthorizerContext
s.Service.FillAuthzContext(&authzContext)

if f.authorizer.ServiceRead(s.Service.ID, &authzContext) != acl.Allow {
if f.authorizer.ServiceRead(s.Service.Name, &authzContext) != acl.Allow {
f.logger.Debug("dropping service from result due to ACLs", "service", s.Service.String())
continue
}
Expand Down
Loading

0 comments on commit 965c80e

Please sign in to comment.