agent: implement HTTP endpoint

This commit is contained in:
Mitchell Hashimoto 2018-05-10 22:52:57 -07:00
parent a48ff54318
commit b961bab08c
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
3 changed files with 145 additions and 0 deletions

View File

@ -48,6 +48,7 @@ func init() {
registerEndpoint("/v1/connect/ca/roots", []string{"GET"}, (*HTTPServer).ConnectCARoots)
registerEndpoint("/v1/connect/intentions", []string{"GET", "POST"}, (*HTTPServer).IntentionEndpoint)
registerEndpoint("/v1/connect/intentions/match", []string{"GET"}, (*HTTPServer).IntentionMatch)
registerEndpoint("/v1/connect/intentions/test", []string{"GET"}, (*HTTPServer).IntentionTest)
registerEndpoint("/v1/connect/intentions/", []string{"GET", "PUT", "DELETE"}, (*HTTPServer).IntentionSpecific)
registerEndpoint("/v1/coordinate/datacenters", []string{"GET"}, (*HTTPServer).CoordinateDatacenters)
registerEndpoint("/v1/coordinate/nodes", []string{"GET"}, (*HTTPServer).CoordinateNodes)

View File

@ -122,6 +122,59 @@ func (s *HTTPServer) IntentionMatch(resp http.ResponseWriter, req *http.Request)
return response, nil
}
// GET /v1/connect/intentions/test
func (s *HTTPServer) IntentionTest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// Prepare args
args := &structs.IntentionQueryRequest{Test: &structs.IntentionQueryTest{}}
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
return nil, nil
}
q := req.URL.Query()
// Set the source type if set
args.Test.SourceType = structs.IntentionSourceConsul
if sourceType, ok := q["source-type"]; ok && len(sourceType) > 0 {
args.Test.SourceType = structs.IntentionSourceType(sourceType[0])
}
// Extract the source/destination
source, ok := q["source"]
if !ok || len(source) != 1 {
return nil, fmt.Errorf("required query parameter 'source' not set")
}
destination, ok := q["destination"]
if !ok || len(destination) != 1 {
return nil, fmt.Errorf("required query parameter 'destination' not set")
}
// We parse them the same way as matches to extract namespace/name
args.Test.SourceName = source[0]
if args.Test.SourceType == structs.IntentionSourceConsul {
entry, err := parseIntentionMatchEntry(source[0])
if err != nil {
return nil, fmt.Errorf("source %q is invalid: %s", source[0], err)
}
args.Test.SourceNS = entry.Namespace
args.Test.SourceName = entry.Name
}
// The destination is always in the Consul format
entry, err := parseIntentionMatchEntry(destination[0])
if err != nil {
return nil, fmt.Errorf("destination %q is invalid: %s", destination[0], err)
}
args.Test.DestinationNS = entry.Namespace
args.Test.DestinationName = entry.Name
var reply structs.IntentionQueryTestResponse
if err := s.agent.RPC("Intention.Test", args, &reply); err != nil {
return nil, err
}
return &reply, nil
}
// IntentionSpecific handles the endpoint for /v1/connection/intentions/:id
func (s *HTTPServer) IntentionSpecific(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
id := strings.TrimPrefix(req.URL.Path, "/v1/connect/intentions/")

View File

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/consul/agent/structs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIntentionsList_empty(t *testing.T) {
@ -180,6 +181,96 @@ func TestIntentionsMatch_noName(t *testing.T) {
assert.Nil(obj)
}
func TestIntentionsTest_basic(t *testing.T) {
t.Parallel()
require := require.New(t)
a := NewTestAgent(t.Name(), "")
defer a.Shutdown()
// Create some intentions
{
insert := [][]string{
{"foo", "*", "foo", "*"},
{"foo", "*", "foo", "bar"},
{"bar", "*", "foo", "bar"},
}
for _, v := range insert {
ixn := structs.IntentionRequest{
Datacenter: "dc1",
Op: structs.IntentionOpCreate,
Intention: structs.TestIntention(t),
}
ixn.Intention.SourceNS = v[0]
ixn.Intention.SourceName = v[1]
ixn.Intention.DestinationNS = v[2]
ixn.Intention.DestinationName = v[3]
ixn.Intention.Action = structs.IntentionActionDeny
// Create
var reply string
require.Nil(a.RPC("Intention.Apply", &ixn, &reply))
}
}
// Request matching intention
{
req, _ := http.NewRequest("GET",
"/v1/connect/intentions/test?source=foo/bar&destination=foo/baz", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.IntentionTest(resp, req)
require.Nil(err)
value := obj.(*structs.IntentionQueryTestResponse)
require.False(value.Allowed)
}
// Request non-matching intention
{
req, _ := http.NewRequest("GET",
"/v1/connect/intentions/test?source=foo/bar&destination=bar/qux", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.IntentionTest(resp, req)
require.Nil(err)
value := obj.(*structs.IntentionQueryTestResponse)
require.True(value.Allowed)
}
}
func TestIntentionsTest_noSource(t *testing.T) {
t.Parallel()
require := require.New(t)
a := NewTestAgent(t.Name(), "")
defer a.Shutdown()
// Request
req, _ := http.NewRequest("GET",
"/v1/connect/intentions/test?destination=B", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.IntentionTest(resp, req)
require.NotNil(err)
require.Contains(err.Error(), "'source' not set")
require.Nil(obj)
}
func TestIntentionsTest_noDestination(t *testing.T) {
t.Parallel()
require := require.New(t)
a := NewTestAgent(t.Name(), "")
defer a.Shutdown()
// Request
req, _ := http.NewRequest("GET",
"/v1/connect/intentions/test?source=B", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.IntentionTest(resp, req)
require.NotNil(err)
require.Contains(err.Error(), "'destination' not set")
require.Nil(obj)
}
func TestIntentionsCreate_good(t *testing.T) {
t.Parallel()