agent: leaf endpoint accepts name, not service ID
This change is important so that requests can made representing a service that may not be registered with the same local agent.
This commit is contained in:
parent
1476745bdc
commit
9249662c6c
|
@ -2177,21 +2177,22 @@ func (a *Agent) verifyProxyToken(token, targetService, targetProxy string) (stri
|
||||||
// that the service name of the matching proxy matches our target
|
// that the service name of the matching proxy matches our target
|
||||||
// service.
|
// service.
|
||||||
if proxy != nil {
|
if proxy != nil {
|
||||||
if proxy.Proxy.TargetServiceID != targetService {
|
// Get the target service since we only have the name. The nil
|
||||||
|
// check below should never be true since a proxy token always
|
||||||
|
// represents the existence of a local service.
|
||||||
|
target := a.State.Service(proxy.Proxy.TargetServiceID)
|
||||||
|
if target == nil {
|
||||||
|
return "", fmt.Errorf("proxy target service not found: %q",
|
||||||
|
proxy.Proxy.TargetServiceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Service != targetService {
|
||||||
return "", acl.ErrPermissionDenied
|
return "", acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the actual ACL token used to register the proxy/service and
|
// Resolve the actual ACL token used to register the proxy/service and
|
||||||
// return that for use in RPC calls.
|
// return that for use in RPC calls.
|
||||||
return a.State.ServiceToken(targetService), nil
|
return a.State.ServiceToken(proxy.Proxy.TargetServiceID), nil
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the service specified. This should always exist because
|
|
||||||
// we only call this function for proxies and leaf certs and both can
|
|
||||||
// only be called for local services.
|
|
||||||
service := a.State.Service(targetService)
|
|
||||||
if service == nil {
|
|
||||||
return "", fmt.Errorf("unknown service ID: %s", targetService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doesn't match, we have to do a full token resolution. The required
|
// Doesn't match, we have to do a full token resolution. The required
|
||||||
|
@ -2202,7 +2203,7 @@ func (a *Agent) verifyProxyToken(token, targetService, targetProxy string) (stri
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if rule != nil && !rule.ServiceWrite(service.Service, nil) {
|
if rule != nil && !rule.ServiceWrite(targetService, nil) {
|
||||||
return "", acl.ErrPermissionDenied
|
return "", acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -908,16 +908,10 @@ func (s *HTTPServer) AgentConnectCARoots(resp http.ResponseWriter, req *http.Req
|
||||||
// instance. This supports blocking queries to update the returned bundle.
|
// instance. This supports blocking queries to update the returned bundle.
|
||||||
func (s *HTTPServer) AgentConnectCALeafCert(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) AgentConnectCALeafCert(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
// Get the service ID. Note that this is the ID of a service instance.
|
// Get the service ID. Note that this is the ID of a service instance.
|
||||||
id := strings.TrimPrefix(req.URL.Path, "/v1/agent/connect/ca/leaf/")
|
serviceName := strings.TrimPrefix(req.URL.Path, "/v1/agent/connect/ca/leaf/")
|
||||||
|
|
||||||
// Retrieve the service specified
|
|
||||||
service := s.agent.State.Service(id)
|
|
||||||
if service == nil {
|
|
||||||
return nil, fmt.Errorf("unknown service ID: %s", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
args := cachetype.ConnectCALeafRequest{
|
args := cachetype.ConnectCALeafRequest{
|
||||||
Service: service.Service, // Need name not ID
|
Service: serviceName, // Need name not ID
|
||||||
}
|
}
|
||||||
var qOpts structs.QueryOptions
|
var qOpts structs.QueryOptions
|
||||||
// Store DC in the ConnectCALeafRequest but query opts separately
|
// Store DC in the ConnectCALeafRequest but query opts separately
|
||||||
|
@ -928,7 +922,7 @@ func (s *HTTPServer) AgentConnectCALeafCert(resp http.ResponseWriter, req *http.
|
||||||
|
|
||||||
// Verify the proxy token. This will check both the local proxy token
|
// Verify the proxy token. This will check both the local proxy token
|
||||||
// as well as the ACL if the token isn't local.
|
// as well as the ACL if the token isn't local.
|
||||||
effectiveToken, err := s.agent.verifyProxyToken(qOpts.Token, id, "")
|
effectiveToken, err := s.agent.verifyProxyToken(qOpts.Token, serviceName, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -983,12 +977,6 @@ func (s *HTTPServer) AgentConnectProxyConfig(resp http.ResponseWriter, req *http
|
||||||
return "", nil, nil
|
return "", nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the ACL token
|
|
||||||
_, err := s.agent.verifyProxyToken(token, proxy.Proxy.TargetServiceID, id)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup the target service as a convenience
|
// Lookup the target service as a convenience
|
||||||
target := s.agent.State.Service(proxy.Proxy.TargetServiceID)
|
target := s.agent.State.Service(proxy.Proxy.TargetServiceID)
|
||||||
if target == nil {
|
if target == nil {
|
||||||
|
@ -999,6 +987,12 @@ func (s *HTTPServer) AgentConnectProxyConfig(resp http.ResponseWriter, req *http
|
||||||
return "", nil, nil
|
return "", nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the ACL token
|
||||||
|
_, err := s.agent.verifyProxyToken(token, target.Service, id)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Watch the proxy for changes
|
// Watch the proxy for changes
|
||||||
ws.Add(proxy.WatchCh)
|
ws.Add(proxy.WatchCh)
|
||||||
|
|
||||||
|
|
|
@ -2238,7 +2238,7 @@ func TestAgentConnectCALeafCert_aclDefaultDeny(t *testing.T) {
|
||||||
require.Equal(200, resp.Code, "body: %s", resp.Body.String())
|
require.Equal(200, resp.Code, "body: %s", resp.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test-id", nil)
|
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test", nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
_, err := a.srv.AgentConnectCALeafCert(resp, req)
|
_, err := a.srv.AgentConnectCALeafCert(resp, req)
|
||||||
require.Error(err)
|
require.Error(err)
|
||||||
|
@ -2284,7 +2284,7 @@ func TestAgentConnectCALeafCert_aclProxyToken(t *testing.T) {
|
||||||
token := proxy.ProxyToken
|
token := proxy.ProxyToken
|
||||||
require.NotEmpty(token)
|
require.NotEmpty(token)
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test-id?token="+token, nil)
|
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test?token="+token, nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
obj, err := a.srv.AgentConnectCALeafCert(resp, req)
|
obj, err := a.srv.AgentConnectCALeafCert(resp, req)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
@ -2355,7 +2355,7 @@ func TestAgentConnectCALeafCert_aclProxyTokenOther(t *testing.T) {
|
||||||
token := proxy.ProxyToken
|
token := proxy.ProxyToken
|
||||||
require.NotEmpty(token)
|
require.NotEmpty(token)
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test-id?token="+token, nil)
|
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test?token="+token, nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
_, err := a.srv.AgentConnectCALeafCert(resp, req)
|
_, err := a.srv.AgentConnectCALeafCert(resp, req)
|
||||||
require.Error(err)
|
require.Error(err)
|
||||||
|
@ -2413,7 +2413,7 @@ func TestAgentConnectCALeafCert_aclServiceWrite(t *testing.T) {
|
||||||
token = aclResp.ID
|
token = aclResp.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test-id?token="+token, nil)
|
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test?token="+token, nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
obj, err := a.srv.AgentConnectCALeafCert(resp, req)
|
obj, err := a.srv.AgentConnectCALeafCert(resp, req)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
@ -2474,7 +2474,7 @@ func TestAgentConnectCALeafCert_aclServiceReadDeny(t *testing.T) {
|
||||||
token = aclResp.ID
|
token = aclResp.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test-id?token="+token, nil)
|
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test?token="+token, nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
_, err := a.srv.AgentConnectCALeafCert(resp, req)
|
_, err := a.srv.AgentConnectCALeafCert(resp, req)
|
||||||
require.Error(err)
|
require.Error(err)
|
||||||
|
@ -2517,7 +2517,113 @@ func TestAgentConnectCALeafCert_good(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List
|
// List
|
||||||
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/foo", nil)
|
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := a.srv.AgentConnectCALeafCert(resp, req)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// Get the issued cert
|
||||||
|
issued, ok := obj.(*structs.IssuedCert)
|
||||||
|
assert.True(ok)
|
||||||
|
|
||||||
|
// Verify that the cert is signed by the CA
|
||||||
|
requireLeafValidUnderCA(t, issued, ca1)
|
||||||
|
|
||||||
|
// Verify blocking index
|
||||||
|
assert.True(issued.ModifyIndex > 0)
|
||||||
|
assert.Equal(fmt.Sprintf("%d", issued.ModifyIndex),
|
||||||
|
resp.Header().Get("X-Consul-Index"))
|
||||||
|
|
||||||
|
// That should've been a cache miss, so no hit change
|
||||||
|
require.Equal(cacheHits, a.cache.Hits())
|
||||||
|
|
||||||
|
// Test caching
|
||||||
|
{
|
||||||
|
// Fetch it again
|
||||||
|
obj2, err := a.srv.AgentConnectCALeafCert(httptest.NewRecorder(), req)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(obj, obj2)
|
||||||
|
|
||||||
|
// Should cache hit this time and not make request
|
||||||
|
require.Equal(cacheHits+1, a.cache.Hits())
|
||||||
|
cacheHits++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that caching is updated in the background
|
||||||
|
{
|
||||||
|
// Set a new CA
|
||||||
|
ca := connect.TestCAConfigSet(t, a, nil)
|
||||||
|
|
||||||
|
retry.Run(t, func(r *retry.R) {
|
||||||
|
// Try and sign again (note no index/wait arg since cache should update in
|
||||||
|
// background even if we aren't actively blocking)
|
||||||
|
obj, err := a.srv.AgentConnectCALeafCert(httptest.NewRecorder(), req)
|
||||||
|
r.Check(err)
|
||||||
|
|
||||||
|
issued2 := obj.(*structs.IssuedCert)
|
||||||
|
if issued.CertPEM == issued2.CertPEM {
|
||||||
|
r.Fatalf("leaf has not updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Got a new leaf. Sanity check it's a whole new key as well as differnt
|
||||||
|
// cert.
|
||||||
|
if issued.PrivateKeyPEM == issued2.PrivateKeyPEM {
|
||||||
|
r.Fatalf("new leaf has same private key as before")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the cert is signed by the new CA
|
||||||
|
requireLeafValidUnderCA(t, issued2, ca)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Should be a cache hit! The data should've updated in the cache
|
||||||
|
// in the background so this should've been fetched directly from
|
||||||
|
// the cache.
|
||||||
|
if v := a.cache.Hits(); v < cacheHits+1 {
|
||||||
|
t.Fatalf("expected at least one more cache hit, still at %d", v)
|
||||||
|
}
|
||||||
|
cacheHits = a.cache.Hits()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test we can request a leaf cert for a service we have permission for
|
||||||
|
// but is not local to this agent.
|
||||||
|
func TestAgentConnectCALeafCert_goodNotLocal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
a := NewTestAgent(t.Name(), "")
|
||||||
|
defer a.Shutdown()
|
||||||
|
|
||||||
|
// CA already setup by default by NewTestAgent but force a new one so we can
|
||||||
|
// verify it was signed easily.
|
||||||
|
ca1 := connect.TestCAConfigSet(t, a, nil)
|
||||||
|
|
||||||
|
// Grab the initial cache hit count
|
||||||
|
cacheHits := a.cache.Hits()
|
||||||
|
|
||||||
|
{
|
||||||
|
// Register a non-local service (central catalog)
|
||||||
|
args := &structs.RegisterRequest{
|
||||||
|
Node: "foo",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Service: "test",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Port: 8080,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
req, _ := http.NewRequest("PUT", "/v1/catalog/register", jsonReader(args))
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
_, err := a.srv.CatalogRegister(resp, req)
|
||||||
|
require.NoError(err)
|
||||||
|
if !assert.Equal(200, resp.Code) {
|
||||||
|
t.Log("Body: ", resp.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List
|
||||||
|
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/leaf/test", nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
obj, err := a.srv.AgentConnectCALeafCert(resp, req)
|
obj, err := a.srv.AgentConnectCALeafCert(resp, req)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
Loading…
Reference in New Issue