From 6c01e402e01e4b69f3562517358419e081bc4a9a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Apr 2018 18:26:58 -0500 Subject: [PATCH] agent: augment /v1/connect/authorize to cache intentions --- agent/agent.go | 9 +++++ agent/agent_endpoint.go | 10 ++++- agent/agent_endpoint_test.go | 75 ++++++++++++++++++++++++++++++++---- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index b6e923ee3f..610aeb64f7 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -2649,4 +2649,13 @@ func (a *Agent) registerCache() { RefreshTimer: 0, RefreshTimeout: 10 * time.Minute, }) + + a.cache.RegisterType(cachetype.IntentionMatchName, &cachetype.IntentionMatch{ + RPC: a.delegate, + }, &cache.RegisterOptions{ + // Maintain a blocking query, retry dropped connections quickly + Refresh: true, + RefreshTimer: 0, + RefreshTimeout: 10 * time.Minute, + }) } diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index c64eb7a923..798c370b2e 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -1124,10 +1124,16 @@ func (s *HTTPServer) AgentConnectAuthorize(resp http.ResponseWriter, req *http.R }, } args.Token = token - var reply structs.IndexedIntentionMatches - if err := s.agent.RPC("Intention.Match", args, &reply); err != nil { + + raw, err := s.agent.cache.Get(cachetype.IntentionMatchName, args) + if err != nil { return nil, err } + + reply, ok := raw.(*structs.IndexedIntentionMatches) + if !ok { + return nil, fmt.Errorf("internal error: response type not correct") + } if len(reply.Matches) != 1 { return nil, fmt.Errorf("Internal error loading matches") } diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index 2e583ec4f0..93cffa6173 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -2495,13 +2495,14 @@ func TestAgentConnectAuthorize_idNotService(t *testing.T) { func TestAgentConnectAuthorize_allow(t *testing.T) { t.Parallel() - assert := assert.New(t) + require := require.New(t) a := NewTestAgent(t.Name(), "") defer a.Shutdown() target := "db" // Create some intentions + var ixnId string { req := structs.IntentionRequest{ Datacenter: "dc1", @@ -2514,10 +2515,12 @@ func TestAgentConnectAuthorize_allow(t *testing.T) { req.Intention.DestinationName = target req.Intention.Action = structs.IntentionActionAllow - var reply string - assert.Nil(a.RPC("Intention.Apply", &req, &reply)) + require.Nil(a.RPC("Intention.Apply", &req, &ixnId)) } + // Grab the initial cache hit count + cacheHits := a.cache.Hits() + args := &structs.ConnectAuthorizeRequest{ Target: target, ClientCertURI: connect.TestSpiffeIDService(t, "web").URI().String(), @@ -2525,12 +2528,70 @@ func TestAgentConnectAuthorize_allow(t *testing.T) { req, _ := http.NewRequest("POST", "/v1/agent/connect/authorize", jsonReader(args)) resp := httptest.NewRecorder() respRaw, err := a.srv.AgentConnectAuthorize(resp, req) - assert.Nil(err) - assert.Equal(200, resp.Code) + require.Nil(err) + require.Equal(200, resp.Code) obj := respRaw.(*connectAuthorizeResp) - assert.True(obj.Authorized) - assert.Contains(obj.Reason, "Matched") + require.True(obj.Authorized) + require.Contains(obj.Reason, "Matched") + + // That should've been a cache miss, so not hit change + require.Equal(cacheHits, a.cache.Hits()) + + // Make the request again + { + req, _ := http.NewRequest("POST", "/v1/agent/connect/authorize", jsonReader(args)) + resp := httptest.NewRecorder() + respRaw, err := a.srv.AgentConnectAuthorize(resp, req) + require.Nil(err) + require.Equal(200, resp.Code) + + obj := respRaw.(*connectAuthorizeResp) + require.True(obj.Authorized) + require.Contains(obj.Reason, "Matched") + } + + // That should've been a cache hit + require.Equal(cacheHits+1, a.cache.Hits()) + cacheHits++ + + // Change the intention + { + req := structs.IntentionRequest{ + Datacenter: "dc1", + Op: structs.IntentionOpUpdate, + Intention: structs.TestIntention(t), + } + req.Intention.ID = ixnId + req.Intention.SourceNS = structs.IntentionDefaultNamespace + req.Intention.SourceName = "web" + req.Intention.DestinationNS = structs.IntentionDefaultNamespace + req.Intention.DestinationName = target + req.Intention.Action = structs.IntentionActionDeny + + require.Nil(a.RPC("Intention.Apply", &req, &ixnId)) + } + + // Short sleep lets the cache background refresh happen + time.Sleep(100 * time.Millisecond) + + // Make the request again + { + req, _ := http.NewRequest("POST", "/v1/agent/connect/authorize", jsonReader(args)) + resp := httptest.NewRecorder() + respRaw, err := a.srv.AgentConnectAuthorize(resp, req) + require.Nil(err) + require.Equal(200, resp.Code) + + obj := respRaw.(*connectAuthorizeResp) + require.False(obj.Authorized) + require.Contains(obj.Reason, "Matched") + } + + // That should've been a cache hit, too, since it updated in the + // background. + require.Equal(cacheHits+1, a.cache.Hits()) + cacheHits++ } // Test when there is an intention denying the connection