mirror of https://github.com/status-im/consul.git
agent: verify proxy token for ProxyConfig endpoint + tests
This commit is contained in:
parent
5d969e3cbb
commit
a099c27b07
|
@ -2115,6 +2115,39 @@ func (a *Agent) RemoveProxy(proxyID string, persist bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// verifyProxyToken takes a proxy service ID and a token and verifies
|
||||
// that the token is allowed to access proxy-related information (leaf
|
||||
// cert, config, etc.).
|
||||
//
|
||||
// The given token may be a local-only proxy token or it may be an ACL
|
||||
// token. We will attempt to verify the local proxy token first.
|
||||
func (a *Agent) verifyProxyToken(proxyId, token string) error {
|
||||
proxy := a.State.Proxy(proxyId)
|
||||
if proxy == nil {
|
||||
return fmt.Errorf("unknown proxy service ID: %q", proxyId)
|
||||
}
|
||||
|
||||
// Easy case is if the token just matches our local proxy token.
|
||||
// If this happens we can return without any requests.
|
||||
if token == proxy.ProxyToken {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Doesn't match, we have to do a full token resolution. The required
|
||||
// permission for any proxy-related endpont is service:write, since
|
||||
// to register a proxy you require that permission and sensitive data
|
||||
// is usually present in the configuration.
|
||||
rule, err := a.resolveToken(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rule != nil && !rule.ServiceWrite(proxy.Proxy.TargetServiceID, nil) {
|
||||
return acl.ErrPermissionDenied
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) cancelCheckMonitors(checkID types.CheckID) {
|
||||
// Stop any monitors
|
||||
delete(a.checkReapAfter, checkID)
|
||||
|
|
|
@ -965,6 +965,10 @@ func (s *HTTPServer) AgentConnectProxyConfig(resp http.ResponseWriter, req *http
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// Parse the token
|
||||
var token string
|
||||
s.parseToken(req, &token)
|
||||
|
||||
// Parse hash specially since it's only this endpoint that uses it currently.
|
||||
// Eventually this should happen in parseWait and end up in QueryOptions but I
|
||||
// didn't want to make very general changes right away.
|
||||
|
@ -980,6 +984,11 @@ func (s *HTTPServer) AgentConnectProxyConfig(resp http.ResponseWriter, req *http
|
|||
return "", nil, nil
|
||||
}
|
||||
|
||||
// Validate the ACL token
|
||||
if err := s.agent.verifyProxyToken(id, token); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
// Lookup the target service as a convenience
|
||||
target := s.agent.State.Service(proxy.Proxy.TargetServiceID)
|
||||
if target == nil {
|
||||
|
|
|
@ -2517,6 +2517,217 @@ func TestAgentConnectProxyConfig_Blocking(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAgentConnectProxyConfig_aclDefaultDeny(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require := require.New(t)
|
||||
a := NewTestAgent(t.Name(), TestACLConfig()+`
|
||||
connect {
|
||||
enabled = true
|
||||
}
|
||||
`)
|
||||
defer a.Shutdown()
|
||||
|
||||
// Register a service with a managed proxy
|
||||
{
|
||||
reg := &structs.ServiceDefinition{
|
||||
ID: "test-id",
|
||||
Name: "test",
|
||||
Address: "127.0.0.1",
|
||||
Port: 8000,
|
||||
Check: structs.CheckType{
|
||||
TTL: 15 * time.Second,
|
||||
},
|
||||
Connect: &structs.ServiceDefinitionConnect{
|
||||
Proxy: &structs.ServiceDefinitionConnectProxy{},
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/v1/agent/service/register?token=root", jsonReader(reg))
|
||||
resp := httptest.NewRecorder()
|
||||
_, err := a.srv.AgentRegisterService(resp, req)
|
||||
require.NoError(err)
|
||||
require.Equal(200, resp.Code, "body: %s", resp.Body.String())
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", "/v1/agent/connect/proxy/test-id-proxy", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
_, err := a.srv.AgentConnectProxyConfig(resp, req)
|
||||
require.True(acl.IsErrPermissionDenied(err))
|
||||
|
||||
}
|
||||
|
||||
func TestAgentConnectProxyConfig_aclProxyToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require := require.New(t)
|
||||
a := NewTestAgent(t.Name(), TestACLConfig()+`
|
||||
connect {
|
||||
enabled = true
|
||||
}
|
||||
`)
|
||||
defer a.Shutdown()
|
||||
|
||||
// Register a service with a managed proxy
|
||||
{
|
||||
reg := &structs.ServiceDefinition{
|
||||
ID: "test-id",
|
||||
Name: "test",
|
||||
Address: "127.0.0.1",
|
||||
Port: 8000,
|
||||
Check: structs.CheckType{
|
||||
TTL: 15 * time.Second,
|
||||
},
|
||||
Connect: &structs.ServiceDefinitionConnect{
|
||||
Proxy: &structs.ServiceDefinitionConnectProxy{},
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/v1/agent/service/register?token=root", jsonReader(reg))
|
||||
resp := httptest.NewRecorder()
|
||||
_, err := a.srv.AgentRegisterService(resp, req)
|
||||
require.NoError(err)
|
||||
require.Equal(200, resp.Code, "body: %s", resp.Body.String())
|
||||
}
|
||||
|
||||
// Get the proxy token from the agent directly, since there is no API
|
||||
// to expose this.
|
||||
proxy := a.State.Proxy("test-id-proxy")
|
||||
require.NotNil(proxy)
|
||||
token := proxy.ProxyToken
|
||||
require.NotEmpty(token)
|
||||
|
||||
req, _ := http.NewRequest(
|
||||
"GET", "/v1/agent/connect/proxy/test-id-proxy?token="+token, nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.AgentConnectProxyConfig(resp, req)
|
||||
require.NoError(err)
|
||||
proxyCfg := obj.(*api.ConnectProxyConfig)
|
||||
require.Equal("test-id-proxy", proxyCfg.ProxyServiceID)
|
||||
require.Equal("test-id", proxyCfg.TargetServiceID)
|
||||
require.Equal("test", proxyCfg.TargetServiceName)
|
||||
}
|
||||
|
||||
func TestAgentConnectProxyConfig_aclServiceWrite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require := require.New(t)
|
||||
a := NewTestAgent(t.Name(), TestACLConfig()+`
|
||||
connect {
|
||||
enabled = true
|
||||
}
|
||||
`)
|
||||
defer a.Shutdown()
|
||||
|
||||
// Register a service with a managed proxy
|
||||
{
|
||||
reg := &structs.ServiceDefinition{
|
||||
ID: "test-id",
|
||||
Name: "test",
|
||||
Address: "127.0.0.1",
|
||||
Port: 8000,
|
||||
Check: structs.CheckType{
|
||||
TTL: 15 * time.Second,
|
||||
},
|
||||
Connect: &structs.ServiceDefinitionConnect{
|
||||
Proxy: &structs.ServiceDefinitionConnectProxy{},
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/v1/agent/service/register?token=root", jsonReader(reg))
|
||||
resp := httptest.NewRecorder()
|
||||
_, err := a.srv.AgentRegisterService(resp, req)
|
||||
require.NoError(err)
|
||||
require.Equal(200, resp.Code, "body: %s", resp.Body.String())
|
||||
}
|
||||
|
||||
// Create an ACL with service:write for our service
|
||||
var token string
|
||||
{
|
||||
args := map[string]interface{}{
|
||||
"Name": "User Token",
|
||||
"Type": "client",
|
||||
"Rules": `service "test" { policy = "write" }`,
|
||||
}
|
||||
req, _ := http.NewRequest("PUT", "/v1/acl/create?token=root", jsonReader(args))
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.ACLCreate(resp, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
aclResp := obj.(aclCreateResponse)
|
||||
token = aclResp.ID
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest(
|
||||
"GET", "/v1/agent/connect/proxy/test-id-proxy?token="+token, nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.AgentConnectProxyConfig(resp, req)
|
||||
require.NoError(err)
|
||||
proxyCfg := obj.(*api.ConnectProxyConfig)
|
||||
require.Equal("test-id-proxy", proxyCfg.ProxyServiceID)
|
||||
require.Equal("test-id", proxyCfg.TargetServiceID)
|
||||
require.Equal("test", proxyCfg.TargetServiceName)
|
||||
}
|
||||
|
||||
func TestAgentConnectProxyConfig_aclServiceReadDeny(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require := require.New(t)
|
||||
a := NewTestAgent(t.Name(), TestACLConfig()+`
|
||||
connect {
|
||||
enabled = true
|
||||
}
|
||||
`)
|
||||
defer a.Shutdown()
|
||||
|
||||
// Register a service with a managed proxy
|
||||
{
|
||||
reg := &structs.ServiceDefinition{
|
||||
ID: "test-id",
|
||||
Name: "test",
|
||||
Address: "127.0.0.1",
|
||||
Port: 8000,
|
||||
Check: structs.CheckType{
|
||||
TTL: 15 * time.Second,
|
||||
},
|
||||
Connect: &structs.ServiceDefinitionConnect{
|
||||
Proxy: &structs.ServiceDefinitionConnectProxy{},
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/v1/agent/service/register?token=root", jsonReader(reg))
|
||||
resp := httptest.NewRecorder()
|
||||
_, err := a.srv.AgentRegisterService(resp, req)
|
||||
require.NoError(err)
|
||||
require.Equal(200, resp.Code, "body: %s", resp.Body.String())
|
||||
}
|
||||
|
||||
// Create an ACL with service:read for our service
|
||||
var token string
|
||||
{
|
||||
args := map[string]interface{}{
|
||||
"Name": "User Token",
|
||||
"Type": "client",
|
||||
"Rules": `service "test" { policy = "read" }`,
|
||||
}
|
||||
req, _ := http.NewRequest("PUT", "/v1/acl/create?token=root", jsonReader(args))
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.ACLCreate(resp, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
aclResp := obj.(aclCreateResponse)
|
||||
token = aclResp.ID
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest(
|
||||
"GET", "/v1/agent/connect/proxy/test-id-proxy?token="+token, nil)
|
||||
resp := httptest.NewRecorder()
|
||||
_, err := a.srv.AgentConnectProxyConfig(resp, req)
|
||||
require.True(acl.IsErrPermissionDenied(err))
|
||||
}
|
||||
|
||||
func TestAgentConnectProxyConfig_ConfigHandling(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
Loading…
Reference in New Issue