agent: implement HTTP endpoint
This commit is contained in:
parent
a48ff54318
commit
b961bab08c
|
@ -48,6 +48,7 @@ func init() {
|
||||||
registerEndpoint("/v1/connect/ca/roots", []string{"GET"}, (*HTTPServer).ConnectCARoots)
|
registerEndpoint("/v1/connect/ca/roots", []string{"GET"}, (*HTTPServer).ConnectCARoots)
|
||||||
registerEndpoint("/v1/connect/intentions", []string{"GET", "POST"}, (*HTTPServer).IntentionEndpoint)
|
registerEndpoint("/v1/connect/intentions", []string{"GET", "POST"}, (*HTTPServer).IntentionEndpoint)
|
||||||
registerEndpoint("/v1/connect/intentions/match", []string{"GET"}, (*HTTPServer).IntentionMatch)
|
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/connect/intentions/", []string{"GET", "PUT", "DELETE"}, (*HTTPServer).IntentionSpecific)
|
||||||
registerEndpoint("/v1/coordinate/datacenters", []string{"GET"}, (*HTTPServer).CoordinateDatacenters)
|
registerEndpoint("/v1/coordinate/datacenters", []string{"GET"}, (*HTTPServer).CoordinateDatacenters)
|
||||||
registerEndpoint("/v1/coordinate/nodes", []string{"GET"}, (*HTTPServer).CoordinateNodes)
|
registerEndpoint("/v1/coordinate/nodes", []string{"GET"}, (*HTTPServer).CoordinateNodes)
|
||||||
|
|
|
@ -122,6 +122,59 @@ func (s *HTTPServer) IntentionMatch(resp http.ResponseWriter, req *http.Request)
|
||||||
return response, nil
|
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
|
// IntentionSpecific handles the endpoint for /v1/connection/intentions/:id
|
||||||
func (s *HTTPServer) IntentionSpecific(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) IntentionSpecific(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
id := strings.TrimPrefix(req.URL.Path, "/v1/connect/intentions/")
|
id := strings.TrimPrefix(req.URL.Path, "/v1/connect/intentions/")
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIntentionsList_empty(t *testing.T) {
|
func TestIntentionsList_empty(t *testing.T) {
|
||||||
|
@ -180,6 +181,96 @@ func TestIntentionsMatch_noName(t *testing.T) {
|
||||||
assert.Nil(obj)
|
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) {
|
func TestIntentionsCreate_good(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue