Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose HTTP-based paths through Connect proxy #6446

Merged
merged 84 commits into from
Sep 26, 2019
Merged
Show file tree
Hide file tree
Changes from 78 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
fecd182
Add service.proxy.config types
freddygv Aug 22, 2019
9db1c10
Add cache-type for service HTTP checks
freddygv Aug 28, 2019
72ead60
Update tests for service-checks cachetype and fix hash bug
freddygv Aug 29, 2019
011ac9e
Set up service-check notification and handling
freddygv Aug 29, 2019
dc1a343
Move expose config out of opaque proxy.config
freddygv Aug 29, 2019
f9c05b7
Inject proxy address into HTTP checks
freddygv Aug 30, 2019
839de82
Inject proxy address into GRPC checks
freddygv Aug 30, 2019
4fa3217
Set and reset check targets on proxy (de)registration
freddygv Aug 31, 2019
45c0984
Rename http1.1 in Expose.Protocol to http
freddygv Sep 2, 2019
09d8994
Only try to read CAFile if provided
freddygv Sep 2, 2019
b29ed48
Fix deadlock in resetExposedChecks
freddygv Sep 2, 2019
1f9d202
Generate listener port for exposing checks
freddygv Sep 4, 2019
4b632ff
Update expose config to reflect listener per path
freddygv Sep 4, 2019
02baf13
Create Envoy listeners and clusters for exposed paths
freddygv Sep 4, 2019
c999f0d
Update configuration and add cfg tests
freddygv Sep 4, 2019
949c081
Create clusters and listeners for expose Consul checks
freddygv Sep 4, 2019
6e9892e
Fix grpc test
freddygv Sep 4, 2019
5b4f105
Fix broken tests
freddygv Sep 4, 2019
90c8747
Move expose config validation to NodeService func
freddygv Sep 4, 2019
9966c70
Avoid panic in expose.checks check
freddygv Sep 4, 2019
127d944
Add service.proxy.config types
freddygv Aug 22, 2019
7fd306d
Add cache-type for service HTTP checks
freddygv Aug 28, 2019
69c59c5
Update tests for service-checks cachetype and fix hash bug
freddygv Aug 29, 2019
3e88f6b
Set up service-check notification and handling
freddygv Aug 29, 2019
478a01e
Move expose config out of opaque proxy.config
freddygv Aug 29, 2019
c48b0cc
Inject proxy address into HTTP checks
freddygv Aug 30, 2019
d59be99
Inject proxy address into GRPC checks
freddygv Aug 30, 2019
06d120c
Set and reset check targets on proxy (de)registration
freddygv Aug 31, 2019
30544e2
Rename http1.1 in Expose.Protocol to http
freddygv Sep 2, 2019
eddb16e
Only try to read CAFile if provided
freddygv Sep 2, 2019
a4dbffc
Fix deadlock in resetExposedChecks
freddygv Sep 2, 2019
67f59ef
Generate listener port for exposing checks
freddygv Sep 4, 2019
baa3a12
Update expose config to reflect listener per path
freddygv Sep 4, 2019
859bb5b
Create Envoy listeners and clusters for exposed paths
freddygv Sep 4, 2019
17cf996
Update configuration and add cfg tests
freddygv Sep 4, 2019
931d167
Create clusters and listeners for expose Consul checks
freddygv Sep 4, 2019
48f7ddf
Fix grpc test
freddygv Sep 4, 2019
ada090d
Fix broken tests
freddygv Sep 4, 2019
ba19c38
Move expose config validation to NodeService func
freddygv Sep 4, 2019
404b323
Avoid panic in expose.checks check
freddygv Sep 4, 2019
9651441
Fix broken agent/api tests
freddygv Sep 5, 2019
05c909a
Resolve conflicts
freddygv Sep 5, 2019
f2b8fe8
Rename Path to ExposePath
freddygv Sep 5, 2019
c4c1780
Add tests for check proxy addr setting/resetting
freddygv Sep 5, 2019
5be00c7
Add more tests and config entry support
freddygv Sep 5, 2019
35c80c0
Add listener and cluster creation tests
freddygv Sep 5, 2019
c0e6a44
Update agent/agent.go
freddygv Sep 5, 2019
af5b5ca
Update agent/agent.go
freddygv Sep 5, 2019
520c7c1
Update agent/agent.go
freddygv Sep 5, 2019
4c7d7e1
PR comments
freddygv Sep 5, 2019
2f62040
Merge branch 'expose-urls' of https://github.com/hashicorp/consul int…
freddygv Sep 5, 2019
7357e7e
Resolve conflicts
freddygv Sep 5, 2019
bc0709e
More PR review comments
freddygv Sep 5, 2019
0ed1b8f
More PR review comments, like parsing url with url.Parse()
freddygv Sep 5, 2019
bbfbbdd
Return error on duplicate exposed listener ports
freddygv Sep 5, 2019
6c87445
Use set tag when hashing slice
freddygv Sep 5, 2019
f4df424
Improve cache type test
freddygv Sep 5, 2019
3a6a526
Expand error msgs on NodeService validation
freddygv Sep 12, 2019
5a02b07
Add badreq test service checks cache-type
freddygv Sep 12, 2019
5a53d67
Move localBlockingQuery to Agent
freddygv Sep 17, 2019
1eb3452
Fix tests to account for tls validation
freddygv Sep 17, 2019
671cdaf
Fix panic in addCheck
freddygv Sep 17, 2019
f2982b0
Restrict exposed check sources to localhost and agent addr
freddygv Sep 18, 2019
901253c
Fix some tests
freddygv Sep 18, 2019
b514c6f
Address config entries PR comments
freddygv Sep 20, 2019
c8366b2
Fix filtering test
freddygv Sep 20, 2019
91b9855
Add documentation
freddygv Sep 20, 2019
a1bf96f
Address docs review comments
freddygv Sep 23, 2019
aa60c1b
Fix broken link
freddygv Sep 23, 2019
c2add74
Fix TestManager_BasicLifecycle
freddygv Sep 23, 2019
9393b56
Add expose flag to service-reg full example
freddygv Sep 24, 2019
c94bb59
Merge branch 'master' into expose-urls
freddygv Sep 24, 2019
e1a08c4
Docs updates
freddygv Sep 24, 2019
8c0e1eb
Merge branch 'expose-urls' of https://github.com/hashicorp/consul int…
freddygv Sep 24, 2019
a97f4e6
Update serviceConfigWatch ref
freddygv Sep 24, 2019
25712d0
Yank TLS flags
freddygv Sep 24, 2019
091d771
go vet fix
freddygv Sep 24, 2019
b10c8bf
Fix docs example
freddygv Sep 24, 2019
717b686
Update website/source/docs/connect/registration/service-registration.…
freddygv Sep 25, 2019
e30cb81
Update website/source/docs/connect/registration/service-registration.…
freddygv Sep 25, 2019
ff5ca75
Update website/source/docs/connect/registration/service-registration.…
freddygv Sep 25, 2019
8d68b38
Final docs updates
freddygv Sep 25, 2019
1c00dc3
Fix http2 integration test
freddygv Sep 25, 2019
18638a4
Update golden files
freddygv Sep 25, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
348 changes: 337 additions & 11 deletions agent/agent.go

Large diffs are not rendered by default.

82 changes: 13 additions & 69 deletions agent/agent_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ package agent
import (
"encoding/json"
"fmt"
"github.com/hashicorp/go-memdb"
"github.com/mitchellh/hashstructure"
"log"
"net/http"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/hashicorp/go-memdb"
"github.com/mitchellh/hashstructure"

"github.com/hashicorp/consul/acl"
cachetype "github.com/hashicorp/consul/agent/cache-types"
Expand All @@ -24,7 +22,7 @@ import (
"github.com/hashicorp/consul/lib/file"
"github.com/hashicorp/consul/logger"
"github.com/hashicorp/consul/types"
bexpr "github.com/hashicorp/go-bexpr"
"github.com/hashicorp/go-bexpr"
"github.com/hashicorp/logutils"
"github.com/hashicorp/serf/coordinate"
"github.com/hashicorp/serf/serf"
Expand Down Expand Up @@ -262,7 +260,7 @@ func (s *HTTPServer) AgentService(resp http.ResponseWriter, req *http.Request) (
// in QueryOptions but I didn't want to make very general changes right away.
hash := req.URL.Query().Get("hash")

return s.agentLocalBlockingQuery(resp, hash, &queryOpts,
resultHash, service, err := s.agent.LocalBlockingQuery(false, hash, queryOpts.MaxQueryTime,
func(ws memdb.WatchSet) (string, interface{}, error) {

svcState := s.agent.State.ServiceState(id)
Expand Down Expand Up @@ -299,7 +297,12 @@ func (s *HTTPServer) AgentService(resp http.ResponseWriter, req *http.Request) (
reply.ContentHash = fmt.Sprintf("%x", rawHash)

return reply.ContentHash, reply, nil
})
},
)
if resultHash != "" {
resp.Header().Set("X-Consul-ContentHash", resultHash)
}
return service, err
}

func (s *HTTPServer) AgentChecks(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
Expand Down Expand Up @@ -769,6 +772,9 @@ func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Re
"local_service_address": "LocalServiceAddress",
// SidecarService
"sidecar_service": "SidecarService",
// Expose Config
"local_path_port": "LocalPathPort",
"listener_port": "ListenerPort",

// DON'T Recurse into these opaque config maps or we might mangle user's
// keys. Note empty canonical is a special sentinel to prevent recursion.
Expand Down Expand Up @@ -1297,68 +1303,6 @@ func (s *HTTPServer) AgentConnectCALeafCert(resp http.ResponseWriter, req *http.
return reply, nil
}

type agentLocalBlockingFunc func(ws memdb.WatchSet) (string, interface{}, error)

// agentLocalBlockingQuery performs a blocking query in a generic way against
// local agent state that has no RPC or raft to back it. It uses `hash` parameter
// instead of an `index`. The resp is needed to write the `X-Consul-ContentHash`
// header back on return no Status nor body content is ever written to it.
func (s *HTTPServer) agentLocalBlockingQuery(resp http.ResponseWriter, hash string,
queryOpts *structs.QueryOptions, fn agentLocalBlockingFunc) (interface{}, error) {

// If we are not blocking we can skip tracking and allocating - nil WatchSet
// is still valid to call Add on and will just be a no op.
var ws memdb.WatchSet
var timeout *time.Timer

if hash != "" {
// TODO(banks) at least define these defaults somewhere in a const. Would be
// nice not to duplicate the ones in consul/rpc.go too...
wait := queryOpts.MaxQueryTime
if wait == 0 {
wait = 5 * time.Minute
}
if wait > 10*time.Minute {
wait = 10 * time.Minute
}
// Apply a small amount of jitter to the request.
wait += lib.RandomStagger(wait / 16)
timeout = time.NewTimer(wait)
}

for {
// Must reset this every loop in case the Watch set is already closed but
// hash remains same. In that case we'll need to re-block on ws.Watch()
// again.
ws = memdb.NewWatchSet()
curHash, curResp, err := fn(ws)
if err != nil {
return curResp, err
}
// Return immediately if there is no timeout, the hash is different or the
// Watch returns true (indicating timeout fired). Note that Watch on a nil
// WatchSet immediately returns false which would incorrectly cause this to
// loop and repeat again, however we rely on the invariant that ws == nil
// IFF timeout == nil in which case the Watch call is never invoked.
if timeout == nil || hash != curHash || ws.Watch(timeout.C) {
resp.Header().Set("X-Consul-ContentHash", curHash)
return curResp, err
}
// Watch returned false indicating a change was detected, loop and repeat
// the callback to load the new value. If agent sync is paused it means
// local state is currently being bulk-edited e.g. config reload. In this
// case it's likely that local state just got unloaded and may or may not be
// reloaded yet. Wait a short amount of time for Sync to resume to ride out
// typical config reloads.
if syncPauseCh := s.agent.syncPausedCh(); syncPauseCh != nil {
select {
case <-syncPauseCh:
case <-timeout.C:
}
}
}
}

// AgentConnectAuthorize
//
// POST /v1/agent/connect/authorize
Expand Down
92 changes: 72 additions & 20 deletions agent/agent_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ func TestAgent_Service(t *testing.T) {
Service: "web-sidecar-proxy",
Port: 8000,
Proxy: expectProxy.ToAPI(),
ContentHash: "f5826efc5ffc207a",
ContentHash: "4c7d5f8d3748be6d",
Weights: api.AgentWeights{
Passing: 1,
Warning: 1,
Expand All @@ -337,7 +337,7 @@ func TestAgent_Service(t *testing.T) {
// Copy and modify
updatedResponse := *expectedResponse
updatedResponse.Port = 9999
updatedResponse.ContentHash = "c8cb04cb77ef33d8"
updatedResponse.ContentHash = "713435ba1f5badcf"

// Simple response for non-proxy service registered in TestAgent config
expectWebResponse := &api.AgentService{
Expand Down Expand Up @@ -1980,39 +1980,51 @@ func TestAgent_RegisterCheck_ACLDeny(t *testing.T) {
require.NotNil(t, nodeToken)

t.Run("no token - node check", func(t *testing.T) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register", jsonReader(nodeCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.True(t, acl.IsErrPermissionDenied(err))
retry.Run(t, func(r *retry.R) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register", jsonReader(nodeCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.True(r, acl.IsErrPermissionDenied(err))
})
})

t.Run("svc token - node check", func(t *testing.T) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token="+svcToken.SecretID, jsonReader(nodeCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.True(t, acl.IsErrPermissionDenied(err))
retry.Run(t, func(r *retry.R) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token="+svcToken.SecretID, jsonReader(nodeCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.True(r, acl.IsErrPermissionDenied(err))
})
})

t.Run("node token - node check", func(t *testing.T) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token="+nodeToken.SecretID, jsonReader(nodeCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.NoError(t, err)
retry.Run(t, func(r *retry.R) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token="+nodeToken.SecretID, jsonReader(nodeCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.NoError(r, err)
})
})

t.Run("no token - svc check", func(t *testing.T) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register", jsonReader(svcCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.True(t, acl.IsErrPermissionDenied(err))
retry.Run(t, func(r *retry.R) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register", jsonReader(svcCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.True(r, acl.IsErrPermissionDenied(err))
})
})

t.Run("node token - svc check", func(t *testing.T) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token="+nodeToken.SecretID, jsonReader(svcCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.True(t, acl.IsErrPermissionDenied(err))
retry.Run(t, func(r *retry.R) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token="+nodeToken.SecretID, jsonReader(svcCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.True(r, acl.IsErrPermissionDenied(err))
})
})

t.Run("svc token - svc check", func(t *testing.T) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token="+svcToken.SecretID, jsonReader(svcCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.NoError(t, err)
retry.Run(t, func(r *retry.R) {
req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token="+svcToken.SecretID, jsonReader(svcCheck))
_, err := a.srv.AgentRegisterCheck(nil, req)
require.NoError(r, err)
})
})

}
Expand Down Expand Up @@ -5386,3 +5398,43 @@ func TestAgent_HostBadACL(t *testing.T) {
assert.Equal(http.StatusOK, resp.Code)
assert.Nil(respRaw)
}

// Thie tests that a proxy with an ExposeConfig is returned as expected.
func TestAgent_Services_ExposeConfig(t *testing.T) {
t.Parallel()

a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()

testrpc.WaitForTestAgent(t, a.RPC, "dc1")
srv1 := &structs.NodeService{
Kind: structs.ServiceKindConnectProxy,
ID: "proxy-id",
Service: "proxy-name",
Port: 8443,
Proxy: structs.ConnectProxyConfig{
Expose: structs.ExposeConfig{
Checks: true,
Paths: []structs.ExposePath{
{
ListenerPort: 8080,
LocalPathPort: 21500,
Protocol: "http2",
Path: "/metrics",
},
},
},
},
}
a.State.AddService(srv1, "")

req, _ := http.NewRequest("GET", "/v1/agent/services", nil)
obj, err := a.srv.AgentServices(nil, req)
require.NoError(t, err)
val := obj.(map[string]*api.AgentService)
require.Len(t, val, 1)
actual := val["proxy-id"]
require.NotNil(t, actual)
require.Equal(t, api.ServiceKindConnectProxy, actual.Kind)
require.Equal(t, srv1.Proxy.ToAPI(), actual.Proxy)
}
Loading