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 }