github.com/outbrain/consul@v1.4.5/agent/intentions_endpoint_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"testing"
     8  
     9  	"github.com/hashicorp/consul/agent/structs"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  func TestIntentionsList_empty(t *testing.T) {
    15  	t.Parallel()
    16  
    17  	assert := assert.New(t)
    18  	a := NewTestAgent(t, t.Name(), "")
    19  	defer a.Shutdown()
    20  
    21  	// Make sure an empty list is non-nil.
    22  	req, _ := http.NewRequest("GET", "/v1/connect/intentions", nil)
    23  	resp := httptest.NewRecorder()
    24  	obj, err := a.srv.IntentionList(resp, req)
    25  	assert.Nil(err)
    26  
    27  	value := obj.(structs.Intentions)
    28  	assert.NotNil(value)
    29  	assert.Len(value, 0)
    30  }
    31  
    32  func TestIntentionsList_values(t *testing.T) {
    33  	t.Parallel()
    34  
    35  	assert := assert.New(t)
    36  	a := NewTestAgent(t, t.Name(), "")
    37  	defer a.Shutdown()
    38  
    39  	// Create some intentions, note we create the lowest precedence first to test
    40  	// sorting.
    41  	for _, v := range []string{"*", "foo", "bar"} {
    42  		req := structs.IntentionRequest{
    43  			Datacenter: "dc1",
    44  			Op:         structs.IntentionOpCreate,
    45  			Intention:  structs.TestIntention(t),
    46  		}
    47  		req.Intention.SourceName = v
    48  
    49  		var reply string
    50  		assert.Nil(a.RPC("Intention.Apply", &req, &reply))
    51  	}
    52  
    53  	// Request
    54  	req, _ := http.NewRequest("GET", "/v1/connect/intentions", nil)
    55  	resp := httptest.NewRecorder()
    56  	obj, err := a.srv.IntentionList(resp, req)
    57  	assert.NoError(err)
    58  
    59  	value := obj.(structs.Intentions)
    60  	assert.Len(value, 3)
    61  
    62  	expected := []string{"bar", "foo", "*"}
    63  	actual := []string{
    64  		value[0].SourceName,
    65  		value[1].SourceName,
    66  		value[2].SourceName,
    67  	}
    68  	assert.Equal(expected, actual)
    69  }
    70  
    71  func TestIntentionsMatch_basic(t *testing.T) {
    72  	t.Parallel()
    73  
    74  	assert := assert.New(t)
    75  	a := NewTestAgent(t, t.Name(), "")
    76  	defer a.Shutdown()
    77  
    78  	// Create some intentions
    79  	{
    80  		insert := [][]string{
    81  			{"foo", "*", "foo", "*"},
    82  			{"foo", "*", "foo", "bar"},
    83  			{"foo", "*", "foo", "baz"}, // shouldn't match
    84  			{"foo", "*", "bar", "bar"}, // shouldn't match
    85  			{"foo", "*", "bar", "*"},   // shouldn't match
    86  			{"foo", "*", "*", "*"},
    87  			{"bar", "*", "foo", "bar"}, // duplicate destination different source
    88  		}
    89  
    90  		for _, v := range insert {
    91  			ixn := structs.IntentionRequest{
    92  				Datacenter: "dc1",
    93  				Op:         structs.IntentionOpCreate,
    94  				Intention:  structs.TestIntention(t),
    95  			}
    96  			ixn.Intention.SourceNS = v[0]
    97  			ixn.Intention.SourceName = v[1]
    98  			ixn.Intention.DestinationNS = v[2]
    99  			ixn.Intention.DestinationName = v[3]
   100  
   101  			// Create
   102  			var reply string
   103  			assert.Nil(a.RPC("Intention.Apply", &ixn, &reply))
   104  		}
   105  	}
   106  
   107  	// Request
   108  	req, _ := http.NewRequest("GET",
   109  		"/v1/connect/intentions/match?by=destination&name=foo/bar", nil)
   110  	resp := httptest.NewRecorder()
   111  	obj, err := a.srv.IntentionMatch(resp, req)
   112  	assert.Nil(err)
   113  
   114  	value := obj.(map[string]structs.Intentions)
   115  	assert.Len(value, 1)
   116  
   117  	var actual [][]string
   118  	expected := [][]string{
   119  		{"bar", "*", "foo", "bar"},
   120  		{"foo", "*", "foo", "bar"},
   121  		{"foo", "*", "foo", "*"},
   122  		{"foo", "*", "*", "*"},
   123  	}
   124  	for _, ixn := range value["foo/bar"] {
   125  		actual = append(actual, []string{
   126  			ixn.SourceNS,
   127  			ixn.SourceName,
   128  			ixn.DestinationNS,
   129  			ixn.DestinationName,
   130  		})
   131  	}
   132  
   133  	assert.Equal(expected, actual)
   134  }
   135  
   136  func TestIntentionsMatch_noBy(t *testing.T) {
   137  	t.Parallel()
   138  
   139  	assert := assert.New(t)
   140  	a := NewTestAgent(t, t.Name(), "")
   141  	defer a.Shutdown()
   142  
   143  	// Request
   144  	req, _ := http.NewRequest("GET",
   145  		"/v1/connect/intentions/match?name=foo/bar", nil)
   146  	resp := httptest.NewRecorder()
   147  	obj, err := a.srv.IntentionMatch(resp, req)
   148  	assert.NotNil(err)
   149  	assert.Contains(err.Error(), "by")
   150  	assert.Nil(obj)
   151  }
   152  
   153  func TestIntentionsMatch_byInvalid(t *testing.T) {
   154  	t.Parallel()
   155  
   156  	assert := assert.New(t)
   157  	a := NewTestAgent(t, t.Name(), "")
   158  	defer a.Shutdown()
   159  
   160  	// Request
   161  	req, _ := http.NewRequest("GET",
   162  		"/v1/connect/intentions/match?by=datacenter", nil)
   163  	resp := httptest.NewRecorder()
   164  	obj, err := a.srv.IntentionMatch(resp, req)
   165  	assert.NotNil(err)
   166  	assert.Contains(err.Error(), "'by' parameter")
   167  	assert.Nil(obj)
   168  }
   169  
   170  func TestIntentionsMatch_noName(t *testing.T) {
   171  	t.Parallel()
   172  
   173  	assert := assert.New(t)
   174  	a := NewTestAgent(t, t.Name(), "")
   175  	defer a.Shutdown()
   176  
   177  	// Request
   178  	req, _ := http.NewRequest("GET",
   179  		"/v1/connect/intentions/match?by=source", nil)
   180  	resp := httptest.NewRecorder()
   181  	obj, err := a.srv.IntentionMatch(resp, req)
   182  	assert.NotNil(err)
   183  	assert.Contains(err.Error(), "'name' not set")
   184  	assert.Nil(obj)
   185  }
   186  
   187  func TestIntentionsCheck_basic(t *testing.T) {
   188  	t.Parallel()
   189  
   190  	require := require.New(t)
   191  	a := NewTestAgent(t, t.Name(), "")
   192  	defer a.Shutdown()
   193  
   194  	// Create some intentions
   195  	{
   196  		insert := [][]string{
   197  			{"foo", "*", "foo", "*"},
   198  			{"foo", "*", "foo", "bar"},
   199  			{"bar", "*", "foo", "bar"},
   200  		}
   201  
   202  		for _, v := range insert {
   203  			ixn := structs.IntentionRequest{
   204  				Datacenter: "dc1",
   205  				Op:         structs.IntentionOpCreate,
   206  				Intention:  structs.TestIntention(t),
   207  			}
   208  			ixn.Intention.SourceNS = v[0]
   209  			ixn.Intention.SourceName = v[1]
   210  			ixn.Intention.DestinationNS = v[2]
   211  			ixn.Intention.DestinationName = v[3]
   212  			ixn.Intention.Action = structs.IntentionActionDeny
   213  
   214  			// Create
   215  			var reply string
   216  			require.Nil(a.RPC("Intention.Apply", &ixn, &reply))
   217  		}
   218  	}
   219  
   220  	// Request matching intention
   221  	{
   222  		req, _ := http.NewRequest("GET",
   223  			"/v1/connect/intentions/test?source=foo/bar&destination=foo/baz", nil)
   224  		resp := httptest.NewRecorder()
   225  		obj, err := a.srv.IntentionCheck(resp, req)
   226  		require.Nil(err)
   227  		value := obj.(*structs.IntentionQueryCheckResponse)
   228  		require.False(value.Allowed)
   229  	}
   230  
   231  	// Request non-matching intention
   232  	{
   233  		req, _ := http.NewRequest("GET",
   234  			"/v1/connect/intentions/test?source=foo/bar&destination=bar/qux", nil)
   235  		resp := httptest.NewRecorder()
   236  		obj, err := a.srv.IntentionCheck(resp, req)
   237  		require.Nil(err)
   238  		value := obj.(*structs.IntentionQueryCheckResponse)
   239  		require.True(value.Allowed)
   240  	}
   241  }
   242  
   243  func TestIntentionsCheck_noSource(t *testing.T) {
   244  	t.Parallel()
   245  
   246  	require := require.New(t)
   247  	a := NewTestAgent(t, t.Name(), "")
   248  	defer a.Shutdown()
   249  
   250  	// Request
   251  	req, _ := http.NewRequest("GET",
   252  		"/v1/connect/intentions/test?destination=B", nil)
   253  	resp := httptest.NewRecorder()
   254  	obj, err := a.srv.IntentionCheck(resp, req)
   255  	require.NotNil(err)
   256  	require.Contains(err.Error(), "'source' not set")
   257  	require.Nil(obj)
   258  }
   259  
   260  func TestIntentionsCheck_noDestination(t *testing.T) {
   261  	t.Parallel()
   262  
   263  	require := require.New(t)
   264  	a := NewTestAgent(t, t.Name(), "")
   265  	defer a.Shutdown()
   266  
   267  	// Request
   268  	req, _ := http.NewRequest("GET",
   269  		"/v1/connect/intentions/test?source=B", nil)
   270  	resp := httptest.NewRecorder()
   271  	obj, err := a.srv.IntentionCheck(resp, req)
   272  	require.NotNil(err)
   273  	require.Contains(err.Error(), "'destination' not set")
   274  	require.Nil(obj)
   275  }
   276  
   277  func TestIntentionsCreate_good(t *testing.T) {
   278  	t.Parallel()
   279  
   280  	assert := assert.New(t)
   281  	a := NewTestAgent(t, t.Name(), "")
   282  	defer a.Shutdown()
   283  
   284  	// Make sure an empty list is non-nil.
   285  	args := structs.TestIntention(t)
   286  	args.SourceName = "foo"
   287  	req, _ := http.NewRequest("POST", "/v1/connect/intentions", jsonReader(args))
   288  	resp := httptest.NewRecorder()
   289  	obj, err := a.srv.IntentionCreate(resp, req)
   290  	assert.Nil(err)
   291  
   292  	value := obj.(intentionCreateResponse)
   293  	assert.NotEqual("", value.ID)
   294  
   295  	// Read the value
   296  	{
   297  		req := &structs.IntentionQueryRequest{
   298  			Datacenter:  "dc1",
   299  			IntentionID: value.ID,
   300  		}
   301  		var resp structs.IndexedIntentions
   302  		assert.Nil(a.RPC("Intention.Get", req, &resp))
   303  		assert.Len(resp.Intentions, 1)
   304  		actual := resp.Intentions[0]
   305  		assert.Equal("foo", actual.SourceName)
   306  	}
   307  }
   308  
   309  func TestIntentionsCreate_noBody(t *testing.T) {
   310  	t.Parallel()
   311  
   312  	a := NewTestAgent(t, t.Name(), "")
   313  	defer a.Shutdown()
   314  
   315  	// Create with no body
   316  	req, _ := http.NewRequest("POST", "/v1/connect/intentions", nil)
   317  	resp := httptest.NewRecorder()
   318  	_, err := a.srv.IntentionCreate(resp, req)
   319  	require.Error(t, err)
   320  }
   321  
   322  func TestIntentionsSpecificGet_good(t *testing.T) {
   323  	t.Parallel()
   324  
   325  	assert := assert.New(t)
   326  	a := NewTestAgent(t, t.Name(), "")
   327  	defer a.Shutdown()
   328  
   329  	// The intention
   330  	ixn := structs.TestIntention(t)
   331  
   332  	// Create an intention directly
   333  	var reply string
   334  	{
   335  		req := structs.IntentionRequest{
   336  			Datacenter: "dc1",
   337  			Op:         structs.IntentionOpCreate,
   338  			Intention:  ixn,
   339  		}
   340  		assert.Nil(a.RPC("Intention.Apply", &req, &reply))
   341  	}
   342  
   343  	// Get the value
   344  	req, _ := http.NewRequest("GET", fmt.Sprintf("/v1/connect/intentions/%s", reply), nil)
   345  	resp := httptest.NewRecorder()
   346  	obj, err := a.srv.IntentionSpecific(resp, req)
   347  	assert.Nil(err)
   348  
   349  	value := obj.(*structs.Intention)
   350  	assert.Equal(reply, value.ID)
   351  
   352  	ixn.ID = value.ID
   353  	ixn.RaftIndex = value.RaftIndex
   354  	ixn.CreatedAt, ixn.UpdatedAt = value.CreatedAt, value.UpdatedAt
   355  	assert.Equal(ixn, value)
   356  }
   357  
   358  func TestIntentionsSpecificGet_invalidId(t *testing.T) {
   359  	t.Parallel()
   360  
   361  	require := require.New(t)
   362  	a := NewTestAgent(t, t.Name(), "")
   363  	defer a.Shutdown()
   364  
   365  	// Read intention with bad ID
   366  	req, _ := http.NewRequest("GET", "/v1/connect/intentions/hello", nil)
   367  	resp := httptest.NewRecorder()
   368  	obj, err := a.srv.IntentionSpecific(resp, req)
   369  	require.Nil(obj)
   370  	require.Error(err)
   371  	require.IsType(BadRequestError{}, err)
   372  	require.Contains(err.Error(), "UUID")
   373  }
   374  
   375  func TestIntentionsSpecificUpdate_good(t *testing.T) {
   376  	t.Parallel()
   377  
   378  	assert := assert.New(t)
   379  	a := NewTestAgent(t, t.Name(), "")
   380  	defer a.Shutdown()
   381  
   382  	// The intention
   383  	ixn := structs.TestIntention(t)
   384  
   385  	// Create an intention directly
   386  	var reply string
   387  	{
   388  		req := structs.IntentionRequest{
   389  			Datacenter: "dc1",
   390  			Op:         structs.IntentionOpCreate,
   391  			Intention:  ixn,
   392  		}
   393  		assert.Nil(a.RPC("Intention.Apply", &req, &reply))
   394  	}
   395  
   396  	// Update the intention
   397  	ixn.ID = "bogus"
   398  	ixn.SourceName = "bar"
   399  	req, _ := http.NewRequest("PUT", fmt.Sprintf("/v1/connect/intentions/%s", reply), jsonReader(ixn))
   400  	resp := httptest.NewRecorder()
   401  	obj, err := a.srv.IntentionSpecific(resp, req)
   402  	assert.Nil(err)
   403  
   404  	value := obj.(intentionCreateResponse)
   405  	assert.Equal(reply, value.ID)
   406  
   407  	// Read the value
   408  	{
   409  		req := &structs.IntentionQueryRequest{
   410  			Datacenter:  "dc1",
   411  			IntentionID: reply,
   412  		}
   413  		var resp structs.IndexedIntentions
   414  		assert.Nil(a.RPC("Intention.Get", req, &resp))
   415  		assert.Len(resp.Intentions, 1)
   416  		actual := resp.Intentions[0]
   417  		assert.Equal("bar", actual.SourceName)
   418  	}
   419  }
   420  
   421  func TestIntentionsSpecificDelete_good(t *testing.T) {
   422  	t.Parallel()
   423  
   424  	assert := assert.New(t)
   425  	a := NewTestAgent(t, t.Name(), "")
   426  	defer a.Shutdown()
   427  
   428  	// The intention
   429  	ixn := structs.TestIntention(t)
   430  	ixn.SourceName = "foo"
   431  
   432  	// Create an intention directly
   433  	var reply string
   434  	{
   435  		req := structs.IntentionRequest{
   436  			Datacenter: "dc1",
   437  			Op:         structs.IntentionOpCreate,
   438  			Intention:  ixn,
   439  		}
   440  		assert.Nil(a.RPC("Intention.Apply", &req, &reply))
   441  	}
   442  
   443  	// Sanity check that the intention exists
   444  	{
   445  		req := &structs.IntentionQueryRequest{
   446  			Datacenter:  "dc1",
   447  			IntentionID: reply,
   448  		}
   449  		var resp structs.IndexedIntentions
   450  		assert.Nil(a.RPC("Intention.Get", req, &resp))
   451  		assert.Len(resp.Intentions, 1)
   452  		actual := resp.Intentions[0]
   453  		assert.Equal("foo", actual.SourceName)
   454  	}
   455  
   456  	// Delete the intention
   457  	req, _ := http.NewRequest("DELETE", fmt.Sprintf("/v1/connect/intentions/%s", reply), nil)
   458  	resp := httptest.NewRecorder()
   459  	obj, err := a.srv.IntentionSpecific(resp, req)
   460  	assert.Nil(err)
   461  	assert.Equal(true, obj)
   462  
   463  	// Verify the intention is gone
   464  	{
   465  		req := &structs.IntentionQueryRequest{
   466  			Datacenter:  "dc1",
   467  			IntentionID: reply,
   468  		}
   469  		var resp structs.IndexedIntentions
   470  		err := a.RPC("Intention.Get", req, &resp)
   471  		assert.NotNil(err)
   472  		assert.Contains(err.Error(), "not found")
   473  	}
   474  }
   475  
   476  func TestParseIntentionMatchEntry(t *testing.T) {
   477  	cases := []struct {
   478  		Input    string
   479  		Expected structs.IntentionMatchEntry
   480  		Err      bool
   481  	}{
   482  		{
   483  			"foo",
   484  			structs.IntentionMatchEntry{
   485  				Namespace: structs.IntentionDefaultNamespace,
   486  				Name:      "foo",
   487  			},
   488  			false,
   489  		},
   490  
   491  		{
   492  			"foo/bar",
   493  			structs.IntentionMatchEntry{
   494  				Namespace: "foo",
   495  				Name:      "bar",
   496  			},
   497  			false,
   498  		},
   499  
   500  		{
   501  			"foo/bar/baz",
   502  			structs.IntentionMatchEntry{},
   503  			true,
   504  		},
   505  	}
   506  
   507  	for _, tc := range cases {
   508  		t.Run(tc.Input, func(t *testing.T) {
   509  			assert := assert.New(t)
   510  			actual, err := parseIntentionMatchEntry(tc.Input)
   511  			assert.Equal(err != nil, tc.Err, err)
   512  			if err != nil {
   513  				return
   514  			}
   515  
   516  			assert.Equal(tc.Expected, actual)
   517  		})
   518  	}
   519  }