open-cluster-management.io/governance-policy-propagator@v0.13.0/test/e2e/case18_compliance_api_test.go (about) 1 // Copyright Contributors to the Open Cluster Management project 2 3 package e2e 4 5 import ( 6 "bytes" 7 "context" 8 "crypto/tls" 9 "database/sql" 10 "encoding/csv" 11 "encoding/json" 12 "errors" 13 "fmt" 14 "io" 15 "net/http" 16 "strings" 17 "time" 18 19 "github.com/lib/pq" 20 . "github.com/onsi/ginkgo/v2" 21 . "github.com/onsi/gomega" 22 v1 "k8s.io/api/core/v1" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/client-go/kubernetes" 25 "k8s.io/client-go/rest" 26 ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 27 28 "open-cluster-management.io/governance-policy-propagator/controllers/complianceeventsapi" 29 "open-cluster-management.io/governance-policy-propagator/test/utils" 30 ) 31 32 const ( 33 eventsEndpoint = "http://localhost:8385/api/v1/compliance-events" 34 csvEndpoint = "http://localhost:8385/api/v1/reports/compliance-events" 35 ) 36 37 var httpClient = http.Client{ 38 Timeout: 30 * time.Second, 39 Transport: &http.Transport{ 40 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 41 }, 42 } 43 44 var ( 45 wrongSAToken string 46 subsetSAToken string 47 ) 48 49 const ( 50 wrongSAYaml = "../resources/case18_compliance_api_test/wrong_service_account.yaml" 51 subsetSAYaml = "../resources/case18_compliance_api_test/subset_service_account.yaml" 52 ) 53 54 func getTableNames(db *sql.DB) ([]string, error) { 55 tableNameRows, err := db.Query("SELECT tablename FROM pg_tables WHERE schemaname = current_schema()") 56 if err != nil { 57 return nil, err 58 } else if tableNameRows.Err() != nil { 59 return nil, err 60 } 61 62 defer tableNameRows.Close() 63 64 tableNames := []string{} 65 66 for tableNameRows.Next() { 67 var tableName string 68 69 err := tableNameRows.Scan(&tableName) 70 if err != nil { 71 return nil, err 72 } 73 74 tableNames = append(tableNames, tableName) 75 } 76 77 return tableNames, nil 78 } 79 80 // Note: These tests require a running Postgres server running in the Kind cluster from the "postgres" Make target. 81 var _ = Describe("Test the compliance events API", Label("compliance-events-api"), Serial, Ordered, func() { 82 var k8sConfig *rest.Config 83 var k8sClient *kubernetes.Clientset 84 var db *sql.DB 85 86 BeforeAll(func(ctx context.Context) { 87 var err error 88 89 k8sConfig, err = LoadConfig("", "", "") 90 Expect(err).ToNot(HaveOccurred()) 91 92 Expect(clientToken).ToNot(BeEmpty(), "Ensure you use the service account kubeconfig (kubeconfig_hub)") 93 94 k8sClient, err = kubernetes.NewForConfig(k8sConfig) 95 Expect(err).ToNot(HaveOccurred()) 96 97 connectionURL := "postgresql://grc:grc@localhost:5432/ocm-compliance-history?sslmode=disable" 98 db, err = sql.Open("postgres", connectionURL) 99 DeferCleanup(func() { 100 if db == nil { 101 return 102 } 103 104 Expect(db.Close()).To(Succeed()) 105 }) 106 107 Expect(err).ToNot(HaveOccurred()) 108 109 Expect(db.PingContext(ctx)).To(Succeed()) 110 111 // Drop all tables to start fresh 112 tableNameRows, err := db.Query("SELECT tablename FROM pg_tables WHERE schemaname = current_schema()") 113 Expect(err).ToNot(HaveOccurred()) 114 115 defer tableNameRows.Close() 116 117 tableNames, err := getTableNames(db) 118 Expect(err).ToNot(HaveOccurred()) 119 120 for _, tableName := range tableNames { 121 _, err := db.ExecContext(ctx, "DROP TABLE IF EXISTS "+tableName+" CASCADE") 122 Expect(err).ToNot(HaveOccurred()) 123 } 124 125 ctrllog.SetLogger(GinkgoLogr) 126 127 complianceServerCtx, err := complianceeventsapi.NewComplianceServerCtx(connectionURL, "unknown") 128 Expect(err).ToNot(HaveOccurred()) 129 130 err = complianceServerCtx.MigrateDB(ctx, k8sClient, "open-cluster-management") 131 Expect(err).ToNot(HaveOccurred()) 132 133 complianceAPI := complianceeventsapi.NewComplianceAPIServer("localhost:8385", k8sConfig, nil) 134 135 httpCtx, httpCtxCancel := context.WithCancel(context.Background()) 136 137 go func() { 138 defer GinkgoRecover() 139 140 err = complianceAPI.Start(httpCtx, complianceServerCtx) 141 Expect(err).ToNot(HaveOccurred()) 142 }() 143 144 DeferCleanup(func() { 145 httpCtxCancel() 146 }) 147 148 Expect(err).ToNot(HaveOccurred()) 149 150 By("Add a new wrong-service account") 151 utils.Kubectl("apply", "-f", wrongSAYaml, "--kubeconfig="+kubeconfigHub) 152 utils.Kubectl("apply", "-f", subsetSAYaml, "--kubeconfig="+kubeconfigHub) 153 154 wrongSAToken = getToken(ctx, "default", "wrong-sa") 155 subsetSAToken = getToken(ctx, "default", "subset-sa") 156 }) 157 158 Describe("Test the database migrations", func() { 159 It("Migrates from a clean database", func(ctx context.Context) { 160 tableNames, err := getTableNames(db) 161 Expect(err).ToNot(HaveOccurred()) 162 Expect(tableNames).To(ContainElements("clusters", "parent_policies", "policies", "compliance_events")) 163 164 migrationVersionRows := db.QueryRow("SELECT version, dirty FROM schema_migrations") 165 var version int 166 var dirty bool 167 err = migrationVersionRows.Scan(&version, &dirty) 168 Expect(err).ToNot(HaveOccurred()) 169 Expect(version).To(Equal(1)) 170 Expect(dirty).To(BeFalse()) 171 }) 172 }) 173 174 Describe("Test POSTing Events", func() { 175 Describe("POST one valid event with including all the optional fields", func() { 176 payload := []byte(`{ 177 "cluster": { 178 "name": "managed1", 179 "cluster_id": "test1-managed1-fake-uuid-1" 180 }, 181 "parent_policy": { 182 "name": "etcd-encryption1", 183 "namespace": "policies", 184 "categories": ["cat-1", "cat-2"], 185 "controls": ["ctrl-1"], 186 "standards": ["stand-1"] 187 }, 188 "policy": { 189 "apiGroup": "policy.open-cluster-management.io", 190 "kind": "ConfigurationPolicy", 191 "name": "etcd-encryption1", 192 "namespace": "local-cluster", 193 "spec": {"test": "one", "severity": "low"}, 194 "severity": "low" 195 }, 196 "event": { 197 "compliance": "NonCompliant", 198 "message": "configmaps [etcd] not found in namespace default", 199 "timestamp": "2023-01-01T01:01:01.111Z", 200 "metadata": {"test": true}, 201 "reported_by": "optional-test" 202 } 203 }`) 204 205 BeforeAll(func(ctx context.Context) { 206 By("POST the event") 207 Eventually(postEvent(ctx, payload, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 208 }) 209 210 It("Should have created the cluster in a table", func() { 211 rows, err := db.Query("SELECT * FROM clusters WHERE cluster_id = $1", "test1-managed1-fake-uuid-1") 212 Expect(err).ToNot(HaveOccurred()) 213 214 count := 0 215 for rows.Next() { 216 var ( 217 id int 218 name string 219 clusterID string 220 ) 221 err := rows.Scan(&id, &name, &clusterID) 222 Expect(err).ToNot(HaveOccurred()) 223 224 Expect(id).NotTo(Equal(0)) 225 Expect(name).To(Equal("managed1")) 226 count++ 227 } 228 229 Expect(count).To(Equal(1)) 230 }) 231 232 It("Should have created the parent policy in a table", func() { 233 rows, err := db.Query( 234 "SELECT * FROM parent_policies WHERE name = $1 AND namespace= $2", "etcd-encryption1", "policies", 235 ) 236 Expect(err).ToNot(HaveOccurred()) 237 238 count := 0 239 for rows.Next() { 240 var ( 241 id int 242 name string 243 namespace string 244 cats pq.StringArray 245 ctrls pq.StringArray 246 stands pq.StringArray 247 ) 248 249 err := rows.Scan(&id, &name, &namespace, &cats, &ctrls, &stands) 250 Expect(err).ToNot(HaveOccurred()) 251 252 Expect(id).NotTo(Equal(0)) 253 Expect(cats).To(ContainElements("cat-1", "cat-2")) 254 Expect(ctrls).To(ContainElements("ctrl-1")) 255 Expect(stands).To(ContainElements("stand-1")) 256 count++ 257 } 258 259 Expect(count).To(Equal(1)) 260 }) 261 262 It("Should have created the policy in a table", func() { 263 rows, err := db.Query("SELECT * FROM policies WHERE name = $1", "etcd-encryption1") 264 Expect(err).ToNot(HaveOccurred()) 265 266 count := 0 267 for rows.Next() { 268 var ( 269 id int 270 kind string 271 apiGroup string 272 name string 273 ns *string 274 spec complianceeventsapi.JSONMap 275 severity *string 276 ) 277 278 err := rows.Scan(&id, &kind, &apiGroup, &name, &ns, &spec, &severity) 279 Expect(err).ToNot(HaveOccurred()) 280 281 Expect(id).NotTo(Equal(0)) 282 Expect(kind).To(Equal("ConfigurationPolicy")) 283 Expect(apiGroup).To(Equal("policy.open-cluster-management.io")) 284 Expect(ns).ToNot(BeNil()) 285 Expect(*ns).To(Equal("local-cluster")) 286 Expect(spec).ToNot(BeNil()) 287 Expect(spec).To(BeEquivalentTo(map[string]any{"test": "one", "severity": "low"})) 288 Expect(severity).ToNot(BeNil()) 289 Expect(*severity).To(Equal("low")) 290 291 count++ 292 } 293 294 Expect(count).To(Equal(1)) 295 }) 296 297 It("Should have created the event in a table", func() { 298 rows, err := db.Query("SELECT * FROM compliance_events WHERE timestamp = $1", 299 "2023-01-01T01:01:01.111Z") 300 Expect(err).ToNot(HaveOccurred()) 301 302 count := 0 303 for rows.Next() { 304 var ( 305 id int 306 clusterID int 307 policyID int 308 parentPolicyID *int 309 compliance string 310 message string 311 timestamp string 312 metadata complianceeventsapi.JSONMap 313 reportedBy *string 314 ) 315 316 err := rows.Scan(&id, &clusterID, &policyID, &parentPolicyID, &compliance, &message, ×tamp, 317 &metadata, &reportedBy) 318 Expect(err).ToNot(HaveOccurred()) 319 320 Expect(id).To(Equal(1)) 321 Expect(clusterID).To(Equal(1)) 322 Expect(policyID).To(Equal(1)) 323 Expect(parentPolicyID).NotTo(BeNil()) 324 Expect(*parentPolicyID).To(Equal(1)) 325 Expect(compliance).To(Equal("NonCompliant")) 326 Expect(message).To(Equal("configmaps [etcd] not found in namespace default")) 327 Expect(timestamp).To(Equal("2023-01-01T01:01:01.111Z")) 328 Expect(metadata).To(HaveKeyWithValue("test", true)) 329 Expect(reportedBy).ToNot(BeNil()) 330 Expect(*reportedBy).To(Equal("optional-test")) 331 count++ 332 } 333 334 Expect(count).To(Equal(1)) 335 }) 336 337 It("Should return the compliance event from the API", func(ctx context.Context) { 338 respJSON, err := listEvents(ctx, clientToken) 339 Expect(err).ToNot(HaveOccurred()) 340 341 complianceEvent := map[string]any{ 342 "cluster": map[string]any{ 343 "cluster_id": "test1-managed1-fake-uuid-1", 344 "name": "managed1", 345 }, 346 "event": map[string]any{ 347 "compliance": "NonCompliant", 348 "message": "configmaps [etcd] not found in namespace default", 349 "metadata": map[string]any{"test": true}, 350 "reported_by": "optional-test", 351 "timestamp": "2023-01-01T01:01:01.111Z", 352 }, 353 "id": float64(1), 354 "parent_policy": map[string]any{ 355 "categories": []any{"cat-1", "cat-2"}, 356 "controls": []any{"ctrl-1"}, 357 "id": float64(1), 358 "name": "etcd-encryption1", 359 "namespace": "policies", 360 "standards": []any{"stand-1"}, 361 }, 362 "policy": map[string]any{ 363 "apiGroup": "policy.open-cluster-management.io", 364 "id": float64(1), 365 "kind": "ConfigurationPolicy", 366 "name": "etcd-encryption1", 367 "namespace": "local-cluster", 368 "severity": "low", 369 }, 370 } 371 372 expected := map[string]any{ 373 "data": []any{complianceEvent}, 374 "metadata": map[string]any{ 375 "page": float64(1), 376 "pages": float64(1), 377 "per_page": float64(20), 378 "total": float64(1), 379 }, 380 } 381 382 Expect(respJSON).To(Equal(expected)) 383 384 // Get just the single compliance event 385 req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"/1", nil) 386 Expect(err).ToNot(HaveOccurred()) 387 388 // Set auth token 389 req.Header.Set("Authorization", "Bearer "+clientToken) 390 391 resp, err := httpClient.Do(req) 392 Expect(err).ToNot(HaveOccurred()) 393 394 defer resp.Body.Close() 395 396 body, err := io.ReadAll(resp.Body) 397 Expect(err).ToNot(HaveOccurred()) 398 399 respJSON = map[string]any{} 400 401 err = json.Unmarshal(body, &respJSON) 402 Expect(err).ToNot(HaveOccurred()) 403 404 complianceEvent["policy"].(map[string]any)["spec"] = map[string]any{ 405 "severity": "low", 406 "test": "one", 407 } 408 409 Expect(respJSON).To(Equal(complianceEvent)) 410 }) 411 412 It("Should return the compliance event with the spec from the API", func(ctx context.Context) { 413 respJSON, err := listEvents(ctx, clientToken, "include_spec") 414 Expect(err).ToNot(HaveOccurred()) 415 416 data := respJSON["data"].([]any) 417 Expect(data).To(HaveLen(1)) 418 419 spec := data[0].(map[string]any)["policy"].(map[string]any)["spec"] 420 expected := map[string]any{"test": "one", "severity": "low"} 421 422 Expect(spec).To(Equal(expected)) 423 }) 424 }) 425 426 Describe("POST two minimally-valid events on different clusters and policies", func() { 427 payload1 := []byte(`{ 428 "cluster": { 429 "name": "managed2", 430 "cluster_id": "test2-managed2-fake-uuid-2" 431 }, 432 "policy": { 433 "apiGroup": "policy.open-cluster-management.io", 434 "kind": "ConfigurationPolicy", 435 "name": "etcd-encryption2", 436 "spec": {"test": "two"} 437 }, 438 "event": { 439 "compliance": "NonCompliant", 440 "message": "configmaps [etcd] not found in namespace default", 441 "timestamp": "2023-02-02T02:02:02.222Z" 442 } 443 }`) 444 445 payload2 := []byte(`{ 446 "cluster": { 447 "name": "managed3", 448 "cluster_id": "test2-managed3-fake-uuid-3" 449 }, 450 "policy": { 451 "apiGroup": "policy.open-cluster-management.io", 452 "kind": "ConfigurationPolicy", 453 "name": "etcd-encryption2", 454 "spec": {"different-spec-test": "two-and-a-half"} 455 }, 456 "event": { 457 "compliance": "Compliant", 458 "message": "configmaps [etcd] found in namespace default", 459 "timestamp": "2023-02-02T02:02:02.222Z" 460 } 461 }`) 462 463 BeforeAll(func(ctx context.Context) { 464 By("POST the events") 465 Eventually(postEvent(ctx, payload1, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 466 Eventually(postEvent(ctx, payload2, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 467 }) 468 469 It("Should have created both clusters in a table", func() { 470 rows, err := db.Query("SELECT * FROM clusters") 471 Expect(err).ToNot(HaveOccurred()) 472 473 clusternames := make([]string, 0) 474 475 for rows.Next() { 476 var ( 477 id int 478 name string 479 clusterID string 480 ) 481 err := rows.Scan(&id, &name, &clusterID) 482 Expect(err).ToNot(HaveOccurred()) 483 484 clusternames = append(clusternames, name) 485 } 486 487 Expect(clusternames).To(ContainElements("managed2", "managed3")) 488 }) 489 490 It("Should have created two policies in a table despite having the same name", func() { 491 rows, err := db.Query("SELECT * FROM policies WHERE name = $1", "etcd-encryption2") 492 Expect(err).ToNot(HaveOccurred()) 493 494 rowCount := 0 495 496 for rows.Next() { 497 var ( 498 id int 499 kind string 500 apiGroup string 501 name string 502 ns *string 503 spec complianceeventsapi.JSONMap 504 severity *string 505 ) 506 507 err := rows.Scan(&id, &kind, &apiGroup, &name, &ns, &spec, &severity) 508 Expect(err).ToNot(HaveOccurred()) 509 510 rowCount++ 511 Expect(id).To(Equal(1 + rowCount)) 512 } 513 514 Expect(rowCount).To(Equal(2)) 515 }) 516 517 It("Should have created both events in a table", func() { 518 rows, err := db.Query("SELECT * FROM compliance_events WHERE timestamp = $1", 519 "2023-02-02T02:02:02.222Z") 520 Expect(err).ToNot(HaveOccurred()) 521 522 messages := make([]string, 0) 523 for rows.Next() { 524 var ( 525 id int 526 clusterID int 527 policyID int 528 parentPolicyID *int 529 compliance string 530 message string 531 timestamp string 532 metadata *string 533 reportedBy *string 534 ) 535 536 err := rows.Scan(&id, &clusterID, &policyID, &parentPolicyID, &compliance, &message, ×tamp, 537 &metadata, &reportedBy) 538 Expect(err).ToNot(HaveOccurred()) 539 540 messages = append(messages, message) 541 542 Expect(id).NotTo(Equal(0)) 543 Expect(clusterID).NotTo(Equal(0)) 544 Expect(policyID).To(Equal(1 + len(messages))) 545 Expect(parentPolicyID).To(BeNil()) 546 } 547 548 Expect(messages).To(ConsistOf( 549 "configmaps [etcd] found in namespace default", 550 "configmaps [etcd] not found in namespace default", 551 )) 552 }) 553 }) 554 555 Describe("API pagination", func() { 556 It("Should have correct default pagination", func(ctx context.Context) { 557 respJSON, err := listEvents(ctx, clientToken) 558 Expect(err).ToNot(HaveOccurred()) 559 560 metadata := respJSON["metadata"].(map[string]interface{}) 561 Expect(metadata["page"]).To(BeEquivalentTo(1)) 562 Expect(metadata["pages"]).To(BeEquivalentTo(1)) 563 Expect(metadata["per_page"]).To(BeEquivalentTo(20)) 564 Expect(metadata["total"]).To(BeEquivalentTo(3)) 565 566 data := respJSON["data"].([]any) 567 Expect(data).To(HaveLen(3)) 568 }) 569 It("Should have accept page=2", func(ctx context.Context) { 570 respJSON, err := listEvents(ctx, clientToken, "page=2") 571 Expect(err).ToNot(HaveOccurred()) 572 573 metadata := respJSON["metadata"].(map[string]interface{}) 574 Expect(metadata["page"]).To(BeEquivalentTo(2)) 575 Expect(metadata["pages"]).To(BeEquivalentTo(1)) 576 Expect(metadata["per_page"]).To(BeEquivalentTo(20)) 577 Expect(metadata["total"]).To(BeEquivalentTo(3)) 578 579 data := respJSON["data"].([]any) 580 Expect(data).To(BeEmpty()) 581 }) 582 583 It("Should accept per_page=2 and page=2", func(ctx context.Context) { 584 respJSON, err := listEvents(ctx, clientToken, "per_page=2", "page=2") 585 Expect(err).ToNot(HaveOccurred()) 586 587 metadata := respJSON["metadata"].(map[string]interface{}) 588 Expect(metadata["page"]).To(BeEquivalentTo(2)) 589 Expect(metadata["pages"]).To(BeEquivalentTo(2)) 590 Expect(metadata["per_page"]).To(BeEquivalentTo(2)) 591 Expect(metadata["total"]).To(BeEquivalentTo(3)) 592 593 data := respJSON["data"].([]any) 594 Expect(data).To(HaveLen(1)) 595 // The default sort is descending order by event timestamp, so the last event in the pagination is 596 // the first event. 597 Expect(data[0].(map[string]any)["id"]).To(BeEquivalentTo(1)) 598 }) 599 600 It("Should not accept page=150", func(ctx context.Context) { 601 // Too many per_page 602 _, err := listEvents(ctx, clientToken, "per_page=150", "page=2") 603 Expect(err).To(HaveOccurred()) 604 Expect(err).To(MatchError(ContainSubstring("per_page must be a value between 1 and 100"))) 605 }) 606 607 It("Should not accept per_page=-5", func(ctx context.Context) { 608 // Too little per_page 609 _, err := listEvents(ctx, clientToken, "per_page=-5", "page=2") 610 Expect(err).To(HaveOccurred()) 611 Expect(err).To(MatchError(ContainSubstring("per_page must be a value between 1 and 100"))) 612 }) 613 614 It("Should not accept page=-5", func(ctx context.Context) { 615 // Too little per_page 616 _, err := listEvents(ctx, clientToken, "page=-5") 617 Expect(err).To(HaveOccurred()) 618 Expect(err).To(MatchError(ContainSubstring("page must be a positive integer"))) 619 }) 620 }) 621 622 DescribeTable("API sorting", 623 func(ctx context.Context, queryArgs []string, expectedIDs []float64) { 624 respJSON, err := listEvents(ctx, clientToken, queryArgs...) 625 Expect(err).ToNot(HaveOccurred()) 626 627 data, ok := respJSON["data"].([]any) 628 Expect(ok).To(BeTrue()) 629 Expect(data).To(HaveLen(3)) 630 631 actualIDs := make([]float64, 0, 3) 632 633 for _, event := range data { 634 actualIDs = append(actualIDs, event.(map[string]any)["id"].(float64)) 635 } 636 637 Expect(actualIDs).To(Equal(expectedIDs)) 638 }, 639 Entry( 640 "Sort descending by cluster.cluster_id", 641 []string{"sort=cluster.cluster_id", "direction=desc"}, 642 []float64{3, 2, 1}, 643 ), 644 Entry( 645 "Sort ascending by cluster.cluster_id", 646 []string{"sort=cluster.cluster_id", "direction=asc"}, 647 []float64{1, 2, 3}, 648 ), 649 Entry( 650 "Sort descending by cluster.name", 651 []string{"sort=cluster.name", "direction=desc"}, 652 []float64{3, 2, 1}, 653 ), 654 Entry( 655 "Sort ascending by cluster.name", 656 []string{"sort=cluster.name", "direction=asc"}, 657 []float64{1, 2, 3}, 658 ), 659 Entry( 660 "Sort descending by event.compliance", 661 []string{"sort=event.compliance", "direction=desc"}, 662 []float64{2, 1, 3}, 663 ), 664 Entry( 665 "Sort ascending by event.compliance", 666 []string{"sort=event.compliance", "direction=asc"}, 667 []float64{3, 1, 2}, 668 ), 669 Entry( 670 "Sort descending by event.message", 671 []string{"sort=event.message", "direction=desc"}, 672 []float64{1, 2, 3}, 673 ), 674 Entry( 675 "Sort ascending by event.message", 676 []string{"sort=event.message", "direction=asc"}, 677 []float64{3, 1, 2}, 678 ), 679 Entry( 680 "Sort descending by event.reported_by", 681 []string{"sort=event.reported_by", "direction=desc"}, 682 []float64{3, 2, 1}, 683 ), 684 Entry( 685 "Sort ascending by event.reported_by", 686 []string{"sort=event.reported_by", "direction=asc"}, 687 []float64{1, 2, 3}, 688 ), 689 Entry( 690 "Sort descending by event.timestamp (default)", 691 []string{}, 692 []float64{3, 2, 1}, 693 ), 694 Entry( 695 "Sort descending by event.timestamp", 696 []string{"sort=event.timestamp", "direction=desc"}, 697 []float64{3, 2, 1}, 698 ), 699 Entry( 700 "Sort ascending by event.timestamp", 701 []string{"sort=event.timestamp", "direction=asc"}, 702 []float64{1, 2, 3}, 703 ), 704 Entry( 705 "Sort descending by parent_policy.categories", 706 []string{"sort=parent_policy.categories", "direction=desc"}, 707 []float64{2, 3, 1}, 708 ), 709 Entry( 710 "Sort ascending by parent_policy.categories", 711 []string{"sort=parent_policy.categories", "direction=asc"}, 712 []float64{1, 2, 3}, 713 ), 714 Entry( 715 "Sort descending by parent_policy.controls", 716 []string{"sort=parent_policy.controls", "direction=desc"}, 717 []float64{2, 3, 1}, 718 ), 719 Entry( 720 "Sort ascending by parent_policy.controls", 721 []string{"sort=parent_policy.controls", "direction=asc"}, 722 []float64{1, 2, 3}, 723 ), 724 Entry( 725 "Sort descending by parent_policy.id", 726 []string{"sort=parent_policy.id", "direction=desc"}, 727 []float64{2, 3, 1}, 728 ), 729 Entry( 730 "Sort ascending by parent_policy.id", 731 []string{"sort=parent_policy.id", "direction=asc"}, 732 []float64{1, 2, 3}, 733 ), 734 Entry( 735 "Sort descending by parent_policy.name", 736 []string{"sort=parent_policy.name", "direction=desc"}, 737 []float64{2, 3, 1}, 738 ), 739 Entry( 740 "Sort ascending by parent_policy.name", 741 []string{"sort=parent_policy.name", "direction=asc"}, 742 []float64{1, 2, 3}, 743 ), 744 Entry( 745 "Sort descending by parent_policy.namespace", 746 []string{"sort=parent_policy.namespace", "direction=desc"}, 747 []float64{2, 3, 1}, 748 ), 749 Entry( 750 "Sort ascending by parent_policy.namespace", 751 []string{"sort=parent_policy.namespace", "direction=asc"}, 752 []float64{1, 2, 3}, 753 ), 754 Entry( 755 "Sort descending by parent_policy.standards", 756 []string{"sort=parent_policy.standards", "direction=desc"}, 757 []float64{2, 3, 1}, 758 ), 759 Entry( 760 "Sort ascending by parent_policy.standards", 761 []string{"sort=parent_policy.standards", "direction=asc"}, 762 []float64{1, 2, 3}, 763 ), 764 Entry( 765 "Sort descending by policy.apiGroup", 766 []string{"sort=policy.apiGroup", "direction=desc"}, 767 []float64{1, 2, 3}, 768 ), 769 Entry( 770 "Sort ascending by policy.apiGroup", 771 []string{"sort=policy.apiGroup", "direction=asc"}, 772 []float64{1, 2, 3}, 773 ), 774 Entry( 775 "Sort descending by policy.id", 776 []string{"sort=policy.id", "direction=desc"}, 777 []float64{3, 2, 1}, 778 ), 779 Entry( 780 "Sort ascending by policy.id", 781 []string{"sort=policy.id", "direction=asc"}, 782 []float64{1, 2, 3}, 783 ), 784 Entry( 785 "Sort descending by policy.kind", 786 []string{"sort=policy.kind", "direction=desc"}, 787 []float64{1, 2, 3}, 788 ), 789 Entry( 790 "Sort ascending by policy.kind", 791 []string{"sort=policy.kind", "direction=asc"}, 792 []float64{1, 2, 3}, 793 ), 794 Entry( 795 "Sort descending by policy.name", 796 []string{"sort=policy.name", "direction=desc"}, 797 []float64{2, 3, 1}, 798 ), 799 Entry( 800 "Sort ascending by policy.name", 801 []string{"sort=policy.name", "direction=asc"}, 802 []float64{1, 2, 3}, 803 ), 804 Entry( 805 "Sort descending by policy.namespace", 806 []string{"sort=policy.namespace", "direction=desc"}, 807 []float64{2, 3, 1}, 808 ), 809 Entry( 810 "Sort ascending by policy.namespace", 811 []string{"sort=policy.namespace", "direction=asc"}, 812 []float64{1, 2, 3}, 813 ), 814 Entry( 815 "Sort descending by policy.severity", 816 []string{"sort=policy.severity", "direction=desc"}, 817 []float64{2, 3, 1}, 818 ), 819 Entry( 820 "Sort ascending by policy.severity", 821 []string{"sort=policy.severity", "direction=asc"}, 822 []float64{1, 2, 3}, 823 ), 824 Entry( 825 "Sort descending by parent_policy.id and policy.id", 826 []string{"sort=parent_policy.id,policy.id", "direction=asc"}, 827 []float64{1, 2, 3}, 828 ), 829 Entry( 830 "Sort descending by id", 831 []string{"sort=id", "direction=desc"}, 832 []float64{3, 2, 1}, 833 ), 834 Entry( 835 "Sort ascending by id", 836 []string{"sort=id", "direction=asc"}, 837 []float64{1, 2, 3}, 838 ), 839 ) 840 841 Describe("Invalid event ID", func() { 842 It("Compliance event is not found", func(ctx context.Context) { 843 req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"/1231291", nil) 844 Expect(err).ToNot(HaveOccurred()) 845 846 // Set auth token 847 req.Header.Set("Authorization", "Bearer "+clientToken) 848 849 resp, err := httpClient.Do(req) 850 Expect(err).ToNot(HaveOccurred()) 851 852 defer resp.Body.Close() 853 854 Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) 855 856 body, err := io.ReadAll(resp.Body) 857 Expect(err).ToNot(HaveOccurred()) 858 859 respJSON := map[string]any{} 860 861 err = json.Unmarshal(body, &respJSON) 862 Expect(err).ToNot(HaveOccurred()) 863 864 Expect(respJSON["message"].(string)).To(Equal("The requested compliance event was not found")) 865 }) 866 867 It("Compliance event ID is invalid", func(ctx context.Context) { 868 req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"/sql-injections-lose", nil) 869 Expect(err).ToNot(HaveOccurred()) 870 871 // Set auth token 872 req.Header.Set("Authorization", "Bearer "+clientToken) 873 874 resp, err := httpClient.Do(req) 875 Expect(err).ToNot(HaveOccurred()) 876 877 defer resp.Body.Close() 878 879 Expect(resp.StatusCode).To(Equal(http.StatusBadRequest)) 880 881 body, err := io.ReadAll(resp.Body) 882 Expect(err).ToNot(HaveOccurred()) 883 884 respJSON := map[string]any{} 885 886 err = json.Unmarshal(body, &respJSON) 887 Expect(err).ToNot(HaveOccurred()) 888 889 Expect(respJSON["message"].(string)).To(Equal("The provided compliance event ID is invalid")) 890 }) 891 }) 892 893 Describe("Invalid sort options", func() { 894 It("An invalid sort of sort=my-laundry", func(ctx context.Context) { 895 _, err := listEvents(ctx, clientToken, "sort=my-laundry") 896 Expect(err).To(HaveOccurred()) 897 expected := "an invalid sort option was provided, choose from: cluster.cluster_id, cluster.name, " + 898 "event.compliance, event.message, event.reported_by, event.timestamp, id, " + 899 "parent_policy.categories, parent_policy.controls, parent_policy.id, parent_policy.name, " + 900 "parent_policy.namespace, parent_policy.standards, policy.apiGroup, policy.id, policy.kind, " + 901 "policy.name, policy.namespace, policy.severity" 902 Expect(err).To(MatchError(ContainSubstring(expected))) 903 }) 904 905 It("An invalid sort direction", func(ctx context.Context) { 906 _, err := listEvents(ctx, clientToken, "direction=up") 907 Expect(err).To(HaveOccurred()) 908 Expect(err).To(MatchError(ContainSubstring("direction must be one of: asc, desc"))) 909 }) 910 }) 911 912 Describe("Invalid query arguments", func() { 913 It("An invalid query argument", func(ctx context.Context) { 914 _, err := listEvents(ctx, clientToken, "make_it_compliant=please") 915 expected := "an invalid query argument was provided, choose from: cluster.cluster_id, cluster.name, " + 916 "direction, event.compliance, event.message, event.message_includes, event.message_like, " + 917 "event.reported_by, event.timestamp, event.timestamp_after, event.timestamp_before, id, " + 918 "include_spec, page, parent_policy.categories, parent_policy.controls, parent_policy.id, " + 919 "parent_policy.name, parent_policy.namespace, parent_policy.standards, per_page, " + 920 "policy.apiGroup, policy.id, policy.kind, policy.name, policy.namespace, policy.severity, sort" 921 Expect(err).To(HaveOccurred()) 922 Expect(err).To(MatchError(ContainSubstring(expected))) 923 }) 924 925 It("An invalid include_spec=yes-please", func(ctx context.Context) { 926 _, err := listEvents(ctx, clientToken, "include_spec=yes-please") 927 Expect(err).To(HaveOccurred()) 928 Expect(err).To(MatchError(ContainSubstring("include_spec is a flag and does not accept a value"))) 929 }) 930 931 It("An invalid sort direction", func(ctx context.Context) { 932 _, err := listEvents(ctx, clientToken, "direction=up") 933 Expect(err).To(HaveOccurred()) 934 Expect(err).To(MatchError(ContainSubstring("direction must be one of: asc, desc"))) 935 }) 936 }) 937 938 Describe("POST three events on the same cluster and policy", func() { 939 // payload1 defines most things, and should cause the cluster, parent, and policy to be created. 940 payload1 := []byte(`{ 941 "cluster": { 942 "name": "managed4", 943 "cluster_id": "test3-managed4-fake-uuid-4" 944 }, 945 "parent_policy": { 946 "name": "common-parent", 947 "namespace": "policies", 948 "categories": ["cat-3", "cat-4"], 949 "controls": ["ctrl-2"], 950 "standards": ["stand-2"] 951 }, 952 "policy": { 953 "apiGroup": "policy.open-cluster-management.io", 954 "kind": "ConfigurationPolicy", 955 "name": "common", 956 "spec": {"test": "three", "severity": "low"}, 957 "severity": "low" 958 }, 959 "event": { 960 "compliance": "NonCompliant", 961 "message": "configmaps [common] not found in namespace default", 962 "timestamp": "2023-03-03T03:03:03.333Z" 963 } 964 }`) 965 966 // payload2 just uses the ids for the policy and parent_policy. 967 payload2 := []byte(`{ 968 "cluster": { 969 "name": "managed4", 970 "cluster_id": "test3-managed4-fake-uuid-4" 971 }, 972 "parent_policy": { 973 "id": 2 974 }, 975 "policy": { 976 "id": 4 977 }, 978 "event": { 979 "compliance": "NonCompliant", 980 "message": "configmaps [common] not found in namespace default", 981 "timestamp": "2023-04-04T04:04:04.444Z" 982 } 983 }`) 984 985 // payload3 redefines most things, and should cause the cluster, parent, and policy to be reused from the 986 // cache. 987 payload3 := []byte(`{ 988 "cluster": { 989 "name": "managed4", 990 "cluster_id": "test3-managed4-fake-uuid-4" 991 }, 992 "parent_policy": { 993 "name": "common-parent", 994 "namespace": "policies", 995 "categories": ["cat-3", "cat-4"], 996 "controls": ["ctrl-2"], 997 "standards": ["stand-2"] 998 }, 999 "policy": { 1000 "apiGroup": "policy.open-cluster-management.io", 1001 "kind": "ConfigurationPolicy", 1002 "name": "common", 1003 "spec": {"test": "three", "severity": "low"}, 1004 "severity": "low" 1005 }, 1006 "event": { 1007 "compliance": "NonCompliant", 1008 "message": "configmaps [common] not found in namespace default", 1009 "timestamp": "2023-05-05T05:05:05.555Z" 1010 } 1011 }`) 1012 1013 BeforeAll(func(ctx context.Context) { 1014 By("POST the events") 1015 Eventually(postEvent(ctx, payload1, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 1016 Eventually(postEvent(ctx, payload2, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 1017 Eventually(postEvent(ctx, payload3, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 1018 }) 1019 1020 It("Should have only created one cluster in the table", func() { 1021 rows, err := db.Query("SELECT * FROM clusters WHERE name = $1", "managed4") 1022 Expect(err).ToNot(HaveOccurred()) 1023 1024 count := 0 1025 for rows.Next() { 1026 var ( 1027 id int 1028 name string 1029 clusterID string 1030 ) 1031 err := rows.Scan(&id, &name, &clusterID) 1032 Expect(err).ToNot(HaveOccurred()) 1033 1034 Expect(id).NotTo(Equal(0)) 1035 count++ 1036 } 1037 1038 Expect(count).To(Equal(1)) 1039 }) 1040 1041 It("Should have only created one parent policy in a table", func() { 1042 rows, err := db.Query( 1043 "SELECT * FROM parent_policies WHERE name = $1 AND namespace = $2", "common-parent", "policies", 1044 ) 1045 Expect(err).ToNot(HaveOccurred()) 1046 1047 count := 0 1048 for rows.Next() { 1049 var ( 1050 id int 1051 name string 1052 namespace string 1053 cats pq.StringArray 1054 ctrls pq.StringArray 1055 stands pq.StringArray 1056 ) 1057 1058 err := rows.Scan(&id, &name, &namespace, &cats, &ctrls, &stands) 1059 Expect(err).ToNot(HaveOccurred()) 1060 1061 Expect(id).NotTo(Equal(0)) 1062 count++ 1063 } 1064 1065 Expect(count).To(Equal(1)) 1066 }) 1067 1068 It("Should have only created one policy in a table", func() { 1069 rows, err := db.Query("SELECT * FROM policies WHERE name = $1", "common") 1070 Expect(err).ToNot(HaveOccurred()) 1071 1072 specs := make([]complianceeventsapi.JSONMap, 0, 1) 1073 for rows.Next() { 1074 var ( 1075 id int 1076 kind string 1077 apiGroup string 1078 name string 1079 ns *string 1080 spec complianceeventsapi.JSONMap 1081 severity *string 1082 ) 1083 1084 err := rows.Scan(&id, &kind, &apiGroup, &name, &ns, &spec, &severity) 1085 Expect(err).ToNot(HaveOccurred()) 1086 1087 Expect(id).NotTo(Equal(0)) 1088 specs = append(specs, spec) 1089 } 1090 1091 Expect(specs).To(HaveLen(1)) 1092 Expect(specs[0]).To(BeEquivalentTo(map[string]any{"test": "three", "severity": "low"})) 1093 }) 1094 1095 It("Should have created three events in a table", func() { 1096 rows, err := db.Query("SELECT * FROM compliance_events WHERE message = $1", 1097 "configmaps [common] not found in namespace default") 1098 Expect(err).ToNot(HaveOccurred()) 1099 1100 timestamps := make([]string, 0, 3) 1101 for rows.Next() { 1102 var ( 1103 id int 1104 clusterID int 1105 policyID int 1106 parentPolicyID *int 1107 compliance string 1108 message string 1109 timestamp string 1110 metadata *string 1111 reportedBy *string 1112 ) 1113 1114 err := rows.Scan(&id, &clusterID, &policyID, &parentPolicyID, &compliance, &message, ×tamp, 1115 &metadata, &reportedBy) 1116 Expect(err).ToNot(HaveOccurred()) 1117 1118 Expect(id).NotTo(Equal(0)) 1119 Expect(clusterID).NotTo(Equal(0)) 1120 Expect(policyID).To(Equal(4)) 1121 Expect(parentPolicyID).NotTo(BeNil()) 1122 Expect(*parentPolicyID).To(Equal(2)) 1123 1124 timestamps = append(timestamps, timestamp) 1125 } 1126 1127 Expect(timestamps).To(ConsistOf( 1128 "2023-03-03T03:03:03.333Z", 1129 "2023-04-04T04:04:04.444Z", 1130 "2023-05-05T05:05:05.555Z", 1131 )) 1132 }) 1133 }) 1134 1135 Describe("POST events to check parent policy matching", func() { 1136 // payload1 defines most things, and should cause the cluster, parent, and policy to be created. 1137 payload1 := []byte(`{ 1138 "cluster": { 1139 "name": "managed5", 1140 "cluster_id": "test5-managed5-fake-uuid-5" 1141 }, 1142 "parent_policy": { 1143 "name": "parent-a", 1144 "namespace": "policies", 1145 "standards": ["stand-3"] 1146 }, 1147 "policy": { 1148 "apiGroup": "policy.open-cluster-management.io", 1149 "kind": "ConfigurationPolicy", 1150 "name": "common-a", 1151 "spec": {"test": "four", "severity": "low"}, 1152 "severity": "low" 1153 }, 1154 "event": { 1155 "compliance": "Compliant", 1156 "message": "configmaps [common] found in namespace default", 1157 "timestamp": "2023-05-05T05:05:05.555Z" 1158 } 1159 }`) 1160 1161 // payload2 skips the standards array on the parent policy, 1162 // which should create a new parent policy 1163 payload2 := []byte(`{ 1164 "cluster": { 1165 "name": "managed5", 1166 "cluster_id": "test5-managed5-fake-uuid-5" 1167 }, 1168 "parent_policy": { 1169 "name": "parent-a", 1170 "namespace": "policies" 1171 }, 1172 "policy": { 1173 "apiGroup": "policy.open-cluster-management.io", 1174 "kind": "ConfigurationPolicy", 1175 "name": "common-a", 1176 "spec": {"test": "four", "severity": "low"}, 1177 "severity": "low" 1178 }, 1179 "event": { 1180 "compliance": "Compliant", 1181 "message": "configmaps [common] found in namespace default", 1182 "timestamp": "2023-06-06T06:06:06.666Z" 1183 } 1184 }`) 1185 1186 // payload3 defines the standards with an empty array, 1187 // which should be the same as not specifying it at all (payload2) 1188 payload3 := []byte(`{ 1189 "cluster": { 1190 "name": "managed5", 1191 "cluster_id": "test5-managed5-fake-uuid-5" 1192 }, 1193 "parent_policy": { 1194 "name": "parent-a", 1195 "namespace": "policies", 1196 "standards": [] 1197 }, 1198 "policy": { 1199 "apiGroup": "policy.open-cluster-management.io", 1200 "kind": "ConfigurationPolicy", 1201 "name": "common-a", 1202 "spec": {"test": "four", "severity": "low"}, 1203 "severity": "low" 1204 }, 1205 "event": { 1206 "compliance": "Compliant", 1207 "message": "configmaps [common] found in namespace default", 1208 "timestamp": "2023-07-07T07:07:07.777Z" 1209 } 1210 }`) 1211 1212 BeforeAll(func(ctx context.Context) { 1213 By("POST the events") 1214 Eventually(postEvent(ctx, payload1, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 1215 Eventually(postEvent(ctx, payload2, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 1216 Eventually(postEvent(ctx, payload3, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 1217 }) 1218 1219 It("Should have created two parent policies", func() { 1220 rows, err := db.Query( 1221 "SELECT * FROM parent_policies WHERE name = $1 AND namespace = $2", "parent-a", "policies", 1222 ) 1223 Expect(err).ToNot(HaveOccurred()) 1224 1225 standardArrays := make([]pq.StringArray, 0) 1226 for rows.Next() { 1227 var ( 1228 id int 1229 name string 1230 namespace string 1231 cats pq.StringArray 1232 ctrls pq.StringArray 1233 stands pq.StringArray 1234 ) 1235 1236 err := rows.Scan(&id, &name, &namespace, &cats, &ctrls, &stands) 1237 Expect(err).ToNot(HaveOccurred()) 1238 1239 Expect(id).NotTo(Equal(0)) 1240 standardArrays = append(standardArrays, stands) 1241 } 1242 1243 Expect(standardArrays).To(ConsistOf( 1244 pq.StringArray{"stand-3"}, 1245 nil, 1246 )) 1247 }) 1248 1249 It("Should have created a single policy", func() { 1250 rows, err := db.Query("SELECT * FROM policies WHERE name = $1", "common-a") 1251 Expect(err).ToNot(HaveOccurred()) 1252 1253 ids := make([]int, 0) 1254 for rows.Next() { 1255 var ( 1256 id int 1257 kind string 1258 apiGroup string 1259 name string 1260 ns *string 1261 spec complianceeventsapi.JSONMap 1262 severity *string 1263 ) 1264 1265 err := rows.Scan(&id, &kind, &apiGroup, &name, &ns, &spec, &severity) 1266 Expect(err).ToNot(HaveOccurred()) 1267 1268 Expect(id).NotTo(Equal(0)) 1269 ids = append(ids, id) 1270 } 1271 1272 Expect(ids).To(HaveLen(1)) 1273 }) 1274 }) 1275 1276 Describe("POST events to check policy namespace matching", func() { 1277 // payload1 should cause the cluster, parent, and policy to be created. 1278 payload1 := []byte(`{ 1279 "cluster": { 1280 "name": "managed6", 1281 "cluster_id": "test6-managed6-fake-uuid-6" 1282 }, 1283 "parent_policy": { 1284 "name": "parent-b", 1285 "namespace": "policies" 1286 }, 1287 "policy": { 1288 "apiGroup": "policy.open-cluster-management.io", 1289 "kind": "ConfigurationPolicy", 1290 "name": "common-b", 1291 "spec": {"test": "four", "severity": "low"}, 1292 "severity": "low", 1293 "namespace": "default" 1294 }, 1295 "event": { 1296 "compliance": "Compliant", 1297 "message": "configmaps [common] found in namespace default", 1298 "timestamp": "2023-01-02T03:04:05.111Z" 1299 } 1300 }`) 1301 1302 // payload2 skips the namespace, which should create a new policy 1303 payload2 := []byte(`{ 1304 "cluster": { 1305 "name": "managed6", 1306 "cluster_id": "test6-managed6-fake-uuid-6" 1307 }, 1308 "parent_policy": { 1309 "name": "parent-b", 1310 "namespace": "policies" 1311 }, 1312 "policy": { 1313 "apiGroup": "policy.open-cluster-management.io", 1314 "kind": "ConfigurationPolicy", 1315 "name": "common-b", 1316 "spec": {"test": "four", "severity": "low"}, 1317 "severity": "low" 1318 }, 1319 "event": { 1320 "compliance": "Compliant", 1321 "message": "configmaps [common] found in namespace default", 1322 "timestamp": "2023-01-02T03:04:05.222Z" 1323 } 1324 }`) 1325 1326 BeforeAll(func(ctx context.Context) { 1327 By("POST the events") 1328 Eventually(postEvent(ctx, payload1, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 1329 Eventually(postEvent(ctx, payload2, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 1330 }) 1331 1332 It("Should have created one parent policy", func() { 1333 rows, err := db.Query( 1334 "SELECT * FROM parent_policies WHERE name = $1 AND namespace = $2", "parent-b", "policies", 1335 ) 1336 Expect(err).ToNot(HaveOccurred()) 1337 1338 count := 0 1339 for rows.Next() { 1340 var ( 1341 id int 1342 name string 1343 namespace string 1344 cats pq.StringArray 1345 ctrls pq.StringArray 1346 stands pq.StringArray 1347 ) 1348 1349 err := rows.Scan(&id, &name, &namespace, &cats, &ctrls, &stands) 1350 Expect(err).ToNot(HaveOccurred()) 1351 Expect(id).NotTo(Equal(0)) 1352 count++ 1353 } 1354 1355 Expect(count).To(Equal(1)) 1356 }) 1357 1358 It("Should have created two policies in the table, with different namespaces", func() { 1359 rows, err := db.Query("SELECT * FROM policies WHERE name = $1", "common-b") 1360 Expect(err).ToNot(HaveOccurred()) 1361 1362 ids := make([]int, 0) 1363 names := make([]string, 0) 1364 namespaces := make([]string, 0) 1365 specs := make([]complianceeventsapi.JSONMap, 0, 2) 1366 for rows.Next() { 1367 var ( 1368 id int 1369 kind string 1370 apiGroup string 1371 name string 1372 ns *string 1373 spec complianceeventsapi.JSONMap 1374 severity *string 1375 ) 1376 1377 err := rows.Scan(&id, &kind, &apiGroup, &name, &ns, &spec, &severity) 1378 Expect(err).ToNot(HaveOccurred()) 1379 1380 Expect(id).NotTo(Equal(0)) 1381 ids = append(ids, id) 1382 names = append(names, name) 1383 specs = append(specs, spec) 1384 1385 if ns != nil { 1386 namespaces = append(namespaces, *ns) 1387 } 1388 } 1389 1390 Expect(ids).To(HaveLen(2)) 1391 Expect(ids[0]).ToNot(Equal(ids[1])) 1392 Expect(names[0]).To(Equal(names[1])) 1393 Expect(namespaces).To(ConsistOf("default")) 1394 Expect(specs[0]).To(Equal(specs[1])) 1395 }) 1396 }) 1397 1398 Describe("POST invalid events", func() { 1399 It("should require the cluster to be specified", func(ctx context.Context) { 1400 Eventually(postEvent(ctx, []byte(`{ 1401 "parent_policy": { 1402 "name": "validity-parent", 1403 "namespace": "policies" 1404 }, 1405 "policy": { 1406 "apiGroup": "policy.open-cluster-management.io", 1407 "kind": "ConfigurationPolicy", 1408 "name": "validity", 1409 "spec": {"test":"validity", "severity": "low"} 1410 }, 1411 "event": { 1412 "compliance": "Compliant", 1413 "message": "configmaps [valid] valid in namespace valid", 1414 "timestamp": "2023-09-09T09:09:09.999Z" 1415 } 1416 }`), clientToken), "5s", "1s").Should( 1417 MatchError(ContainSubstring("Got non-201 status code 400")), 1418 ) 1419 }) 1420 1421 It("should require the parent policy namespace to be specified", func(ctx context.Context) { 1422 Eventually(postEvent(ctx, []byte(`{ 1423 "cluster": { 1424 "name": "validity-test", 1425 "cluster_id": "test-validity-fake-uuid" 1426 }, 1427 "parent_policy": { 1428 "name": "validity-parent" 1429 }, 1430 "policy": { 1431 "apiGroup": "policy.open-cluster-management.io", 1432 "kind": "ConfigurationPolicy", 1433 "name": "validity", 1434 "spec": {"test":"validity", "severity": "low"}, 1435 "severity": "low" 1436 }, 1437 "event": { 1438 "compliance": "Compliant", 1439 "message": "configmaps [valid] valid in namespace valid", 1440 "timestamp": "2023-09-09T09:09:09.999Z" 1441 } 1442 }`), clientToken), "5s", "1s").Should( 1443 MatchError(ContainSubstring("Got non-201 status code 400")), 1444 ) 1445 }) 1446 1447 It("should require the event time to be specified", func(ctx context.Context) { 1448 Eventually(postEvent(ctx, []byte(`{ 1449 "cluster": { 1450 "name": "validity-test", 1451 "cluster_id": "test-validity-fake-uuid" 1452 }, 1453 "parent_policy": { 1454 "name": "validity-parent", 1455 "namespace": "policies" 1456 }, 1457 "policy": { 1458 "apiGroup": "policy.open-cluster-management.io", 1459 "kind": "ConfigurationPolicy", 1460 "name": "validity", 1461 "spec": {"test": "validity", "severity": "low"}, 1462 "severity": "low" 1463 }, 1464 "event": { 1465 "compliance": "Compliant", 1466 "message": "configmaps [valid] valid in namespace valid" 1467 } 1468 }`), clientToken), "5s", "1s").Should( 1469 MatchError(ContainSubstring("Got non-201 status code 400")), 1470 ) 1471 }) 1472 1473 It("should require the parent policy to have fields when specified", func(ctx context.Context) { 1474 Eventually(postEvent(ctx, []byte(`{ 1475 "cluster": { 1476 "name": "validity-test", 1477 "cluster_id": "test-validity-fake-uuid" 1478 }, 1479 "parent_policy": {}, 1480 "policy": { 1481 "apiGroup": "policy.open-cluster-management.io", 1482 "kind": "ConfigurationPolicy", 1483 "name": "validity", 1484 "spec": {"test": "validity", "severity": "low"}, 1485 "severity": "low" 1486 }, 1487 "event": { 1488 "compliance": "Compliant", 1489 "message": "configmaps [valid] valid in namespace valid", 1490 "timestamp": "2023-09-09T09:09:09.999Z" 1491 } 1492 }`), clientToken), "5s", "1s").Should( 1493 MatchError(ContainSubstring("Got non-201 status code 400")), 1494 ) 1495 }) 1496 1497 It("should require the policy to be defined", func(ctx context.Context) { 1498 Eventually(postEvent(ctx, []byte(`{ 1499 "cluster": { 1500 "name": "validity-test", 1501 "cluster_id": "test-validity-fake-uuid" 1502 }, 1503 "parent_policy": { 1504 "name": "validity-parent", 1505 "namespace": "policies" 1506 }, 1507 "policy": {}, 1508 "event": { 1509 "compliance": "Compliant", 1510 "message": "configmaps [valid] valid in namespace valid", 1511 "timestamp": "2023-09-09T09:09:09.999Z" 1512 } 1513 }`), clientToken), "5s", "1s").Should( 1514 MatchError(ContainSubstring("Got non-201 status code 400")), 1515 ) 1516 }) 1517 1518 It("should require the input to be valid JSON", func(ctx context.Context) { 1519 Eventually(postEvent(ctx, []byte(`{ 1520 foo: bar: baz 1521 "cluster": { 1522 "name": "validity-test", 1523 "cluster_id": "test-validity-fake-uuid" 1524 }, 1525 "parent_policy": { 1526 "name": "validity-parent", 1527 "namespace": "policies" 1528 }, 1529 "policy": { 1530 "apiGroup": "policy.open-cluster-management.io", 1531 "kind": "ConfigurationPolicy", 1532 "name": "validity", 1533 "spec": {"test": "validity", "severity": "low"}, 1534 "severity": "low", 1535 "specHash": "foobar" 1536 }, 1537 "event": { 1538 "compliance": "Compliant", 1539 "message": "configmaps [valid] valid in namespace valid", 1540 "timestamp": "2023-09-09T09:09:09.999Z" 1541 } 1542 }`), clientToken), "5s", "1s").Should( 1543 MatchError(ContainSubstring("Got non-201 status code 400")), 1544 ) 1545 }) 1546 1547 It("should require the spec when inputting a new policy", func(ctx context.Context) { 1548 Eventually(postEvent(ctx, []byte(`{ 1549 "cluster": { 1550 "name": "validity-test", 1551 "cluster_id": "test-validity-fake-uuid" 1552 }, 1553 "parent_policy": { 1554 "id": 1231234 1555 }, 1556 "policy": { 1557 "id": 123123 1558 }, 1559 "event": { 1560 "compliance": "Compliant", 1561 "message": "configmaps [valid] valid in namespace valid", 1562 "timestamp": "2023-09-09T09:09:09.999Z" 1563 } 1564 }`), clientToken), "5s", "1s").Should(MatchError(ContainSubstring( 1565 `invalid input: parent_policy.id not found\\ninvalid input: policy.id not found`, 1566 ))) 1567 }) 1568 }) 1569 1570 DescribeTable("API filtering", 1571 func(ctx context.Context, queryArgs []string, expectedIDs []float64) { 1572 respJSON, err := listEvents(ctx, clientToken, queryArgs...) 1573 Expect(err).ToNot(HaveOccurred()) 1574 1575 data, ok := respJSON["data"].([]any) 1576 Expect(ok).To(BeTrue()) 1577 1578 actualIDs := []float64{} 1579 1580 for _, event := range data { 1581 actualIDs = append(actualIDs, event.(map[string]any)["id"].(float64)) 1582 } 1583 1584 Expect(actualIDs).To(Equal(expectedIDs)) 1585 }, 1586 Entry( 1587 "Filter by cluster.cluster_id", 1588 []string{"cluster.cluster_id=test1-managed1-fake-uuid-1,test6-managed6-fake-uuid-6"}, 1589 []float64{11, 10, 1}, 1590 ), 1591 Entry( 1592 "Filter by cluster.name", 1593 []string{"cluster.name=managed1,managed6"}, 1594 []float64{11, 10, 1}, 1595 ), 1596 Entry( 1597 "Filter by event.compliance", 1598 []string{"event.compliance=Compliant"}, 1599 []float64{9, 8, 7, 3, 11, 10}, 1600 ), 1601 Entry( 1602 "Filter by event.message", 1603 []string{"event.message=configmaps%20%5Bcommon%5D%20not%20found%20in%20namespace%20default"}, 1604 []float64{6, 5, 4}, 1605 ), 1606 Entry( 1607 "Filter by event.message_includes", 1608 []string{"event.message_includes=etcd"}, 1609 []float64{2, 3, 1}, 1610 ), 1611 Entry( 1612 "Filter by event.message_includes and ensure special characters are escaped", 1613 []string{"event.message_includes=co_m%25n"}, 1614 []float64{}, 1615 ), 1616 Entry( 1617 "Filter by event.message_like", 1618 []string{"event.message_like=configmaps%20%5B%25common%25%5D%25"}, 1619 []float64{9, 8, 6, 7, 5, 4, 11, 10}, 1620 ), 1621 Entry( 1622 "Filter by event.timestamp", 1623 []string{"event.timestamp=2023-01-01T01:01:01.111Z"}, 1624 []float64{1}, 1625 ), 1626 Entry( 1627 "Filter by event.timestamp_after", 1628 []string{"event.timestamp_after=2023-04-01T01:01:01.111Z"}, 1629 []float64{9, 8, 7, 6, 5}, 1630 ), 1631 Entry( 1632 "Filter by event.timestamp_before", 1633 []string{"event.timestamp_before=2023-04-01T01:01:01.111Z"}, 1634 []float64{4, 3, 2, 11, 10, 1}, 1635 ), 1636 Entry( 1637 "Filter by event.timestamp_after and event.timestamp_before", 1638 []string{ 1639 "event.timestamp_after=2023-01-01T01:01:01.111Z", "event.timestamp_before=2023-04-01T01:01:01.111Z", 1640 }, 1641 []float64{4, 2, 3, 11, 10}, 1642 ), 1643 Entry( 1644 "Filter by parent_policy.categories", 1645 []string{"parent_policy.categories=cat-1,cat-3"}, 1646 []float64{6, 5, 4, 1}, 1647 ), 1648 Entry( 1649 "Filter by parent_policy.categories is null", 1650 []string{"parent_policy.categories"}, 1651 []float64{9, 8, 7, 2, 3, 11, 10}, 1652 ), 1653 Entry( 1654 "Filter by parent_policy.controls", 1655 []string{"parent_policy.controls=ctrl-2"}, 1656 []float64{6, 5, 4}, 1657 ), 1658 Entry( 1659 "Filter by parent_policy.controls is null", 1660 []string{"parent_policy.controls"}, 1661 []float64{9, 8, 7, 2, 3, 11, 10}, 1662 ), 1663 Entry( 1664 "Filter by parent_policy.id", 1665 []string{"parent_policy.id=2"}, 1666 []float64{6, 5, 4}, 1667 ), 1668 Entry( 1669 "Filter by parent_policy.name", 1670 []string{"parent_policy.name=etcd-encryption1"}, 1671 []float64{1}, 1672 ), 1673 Entry( 1674 "Filter by parent_policy.namespace", 1675 []string{"parent_policy.namespace=policies"}, 1676 []float64{9, 8, 6, 7, 5, 4, 11, 10, 1}, 1677 ), 1678 Entry( 1679 "Filter by parent_policy.standards", 1680 []string{"parent_policy.standards=stand-2"}, 1681 []float64{6, 5, 4}, 1682 ), 1683 Entry( 1684 "Filter by parent_policy.standards is null", 1685 []string{"parent_policy.standards"}, 1686 []float64{9, 8, 2, 3, 11, 10}, 1687 ), 1688 Entry( 1689 "Filter by policy.apiGroup", 1690 []string{"policy.apiGroup=policy.open-cluster-management.io"}, 1691 []float64{9, 8, 6, 7, 5, 4, 3, 2, 11, 10, 1}, 1692 ), 1693 Entry( 1694 "Filter by policy.apiGroup no results", 1695 []string{"policy.apiGroup=does-not-exist"}, 1696 []float64{}, 1697 ), 1698 Entry( 1699 "Filter by policy.id", 1700 []string{"policy.id=4"}, 1701 []float64{6, 5, 4}, 1702 ), 1703 Entry( 1704 "Filter by policy.kind", 1705 []string{"policy.kind=ConfigurationPolicy"}, 1706 []float64{9, 8, 6, 7, 5, 4, 3, 2, 11, 10, 1}, 1707 ), 1708 Entry( 1709 "Filter by policy.kind no results", 1710 []string{"policy.kind=something-else"}, 1711 []float64{}, 1712 ), 1713 Entry( 1714 "Filter by policy.name", 1715 []string{"policy.name=common-b"}, 1716 []float64{11, 10}, 1717 ), 1718 Entry( 1719 "Filter by policy.namespace", 1720 []string{"policy.namespace=default"}, 1721 []float64{10}, 1722 ), 1723 Entry( 1724 "Filter by policy.namespace is null", 1725 []string{"policy.namespace"}, 1726 []float64{9, 8, 6, 7, 5, 4, 2, 3, 11}, 1727 ), 1728 Entry( 1729 "Filter by policy.severity", 1730 []string{"policy.severity=low"}, 1731 []float64{9, 8, 6, 7, 5, 4, 11, 10, 1}, 1732 ), 1733 Entry( 1734 "Filter by policy.severity is null", 1735 []string{"policy.severity"}, 1736 []float64{2, 3}, 1737 ), 1738 ) 1739 1740 DescribeTable("Invalid API filtering", 1741 func(ctx context.Context, queryArgs []string, expectedErrMsg string) { 1742 _, err := listEvents(ctx, clientToken, queryArgs...) 1743 Expect(err).To(MatchError(ContainSubstring(expectedErrMsg))) 1744 }, 1745 Entry( 1746 "Filter by empty event.timestamp_before is invalid", 1747 []string{"event.timestamp_before"}, 1748 "invalid query argument: event.timestamp_before must have a value", 1749 ), 1750 Entry( 1751 "Filter by invalid event.timestamp_before", 1752 []string{"event.timestamp_before=1993"}, 1753 "invalid query argument: event.timestamp_before must be in the format of RFC 3339", 1754 ), 1755 Entry( 1756 "Filter by invalid event.timestamp_after", 1757 []string{"event.timestamp_after=1993"}, 1758 "invalid query argument: event.timestamp_after must be in the format of RFC 3339", 1759 ), 1760 ) 1761 1762 Describe("Test the /api/v1/reports/compliance-events endpoint", func() { 1763 It("should send CSV file in http response", func(ctx context.Context) { 1764 req, err := http.NewRequestWithContext(ctx, http.MethodGet, csvEndpoint, nil) 1765 Expect(err).ShouldNot(HaveOccurred()) 1766 1767 req.Header.Set("Authorization", "Bearer "+clientToken) 1768 1769 resp, err := httpClient.Do(req) 1770 Expect(err).ShouldNot(HaveOccurred()) 1771 1772 defer resp.Body.Close() 1773 1774 By("Content-type should be CSV") 1775 Expect(resp.Header.Get("Content-Type")).Should(Equal("text/csv")) 1776 1777 csvReader := csv.NewReader(resp.Body) 1778 1779 records, err := csvReader.ReadAll() 1780 Expect(err).ShouldNot(HaveOccurred()) 1781 1782 Expect(len(records)).Should(BeNumerically(">", 10)) 1783 1784 By("First line should be the titles") 1785 Expect(records[0]).Should(ContainElements([]string{ 1786 "compliance_events_id", 1787 "compliance_events_compliance", 1788 "compliance_events_message", 1789 "compliance_events_metadata", 1790 "compliance_events_reported_by", 1791 "compliance_events_timestamp", 1792 "clusters_cluster_id", 1793 "clusters_name", 1794 "parent_policies_id", 1795 "parent_policies_name", 1796 "parent_policies_namespace", 1797 "parent_policies_categories", 1798 "parent_policies_controls", 1799 "parent_policies_standards", 1800 "policies_id", 1801 "policies_api_group", 1802 "policies_kind", 1803 "policies_name", 1804 "policies_namespace", 1805 "policies_severity", 1806 })) 1807 1808 By("All line should have 20 columns") 1809 for _, r := range records { 1810 Expect(r).Should(HaveLen(20)) 1811 } 1812 }) 1813 It("Should return only header when SA does not have any GET verb to managedCluster", 1814 func(ctx context.Context) { 1815 req, err := http.NewRequestWithContext(ctx, http.MethodGet, csvEndpoint, nil) 1816 Expect(err).ShouldNot(HaveOccurred()) 1817 1818 req.Header.Set("Content-Type", "application/json") 1819 // Set auth token 1820 req.Header.Set("Authorization", "Bearer "+wrongSAToken) 1821 1822 resp, err := httpClient.Do(req) 1823 Expect(err).ShouldNot(HaveOccurred()) 1824 1825 defer resp.Body.Close() 1826 1827 By("Content-type should be CSV") 1828 Expect(resp.Header.Get("Content-Type")).Should(Equal("text/csv")) 1829 1830 csvReader := csv.NewReader(resp.Body) 1831 1832 records, err := csvReader.ReadAll() 1833 Expect(err).ShouldNot(HaveOccurred()) 1834 1835 By("Should return only header") 1836 Expect(records).Should(HaveLen(1)) 1837 1838 Expect(records[0]).Should(ContainElements([]string{ 1839 "compliance_events_id", 1840 "compliance_events_compliance", 1841 "compliance_events_message", 1842 "compliance_events_metadata", 1843 "compliance_events_reported_by", 1844 "compliance_events_timestamp", 1845 "clusters_cluster_id", 1846 "clusters_name", 1847 "parent_policies_id", 1848 "parent_policies_name", 1849 "parent_policies_namespace", 1850 "parent_policies_categories", 1851 "parent_policies_controls", 1852 "parent_policies_standards", 1853 "policies_id", 1854 "policies_api_group", 1855 "policies_kind", 1856 "policies_name", 1857 "policies_namespace", 1858 "policies_severity", 1859 })) 1860 }) 1861 1862 DescribeTable("Should filter CSV file", 1863 func(ctx context.Context, queryArgs []string, expectedLine int) { 1864 endpoints := csvEndpoint 1865 1866 endpoints += "?" + strings.Join(queryArgs, "&") 1867 1868 req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoints, nil) 1869 Expect(err).ShouldNot(HaveOccurred()) 1870 1871 // Set auth token 1872 req.Header.Set("Authorization", "Bearer "+clientToken) 1873 1874 resp, err := httpClient.Do(req) 1875 Expect(err).ShouldNot(HaveOccurred()) 1876 1877 defer resp.Body.Close() 1878 1879 csvReader := csv.NewReader(resp.Body) 1880 records, err := csvReader.ReadAll() 1881 Expect(err).ShouldNot(HaveOccurred()) 1882 1883 // The first element is title 1884 Expect(records).Should(HaveLen(expectedLine)) 1885 }, 1886 Entry( 1887 "Filter by cluster.cluster_id", 1888 []string{"cluster.cluster_id=test1-managed1-fake-uuid-1,test6-managed6-fake-uuid-6"}, 1889 // titles + actual data 1890 4, 1891 ), 1892 Entry( 1893 "Filter by cluster.name", 1894 []string{"cluster.name=managed1,managed6"}, 1895 4, 1896 ), 1897 Entry( 1898 "Filter by event.compliance", 1899 []string{"event.compliance=Compliant"}, 1900 7, 1901 ), 1902 Entry( 1903 "Filter by event.message", 1904 []string{"event.message=configmaps%20%5Bcommon%5D%20not%20found%20in%20namespace%20default"}, 1905 4, 1906 ), 1907 Entry( 1908 "Filter by event.message_includes", 1909 []string{"event.message_includes=etcd"}, 1910 4, 1911 ), 1912 Entry( 1913 "Filter by event.message_like", 1914 []string{"event.message_like=configmaps%20%5B%25common%25%5D%25"}, 1915 9, 1916 ), 1917 Entry( 1918 "Filter by event.timestamp", 1919 []string{"event.timestamp=2023-01-01T01:01:01.111Z"}, 1920 2, 1921 ), 1922 Entry( 1923 "Filter by event.timestamp_after", 1924 []string{"event.timestamp_after=2023-04-01T01:01:01.111Z"}, 1925 6, 1926 ), 1927 Entry( 1928 "Filter by event.timestamp_before", 1929 []string{"event.timestamp_before=2023-04-01T01:01:01.111Z"}, 1930 7, 1931 ), 1932 Entry( 1933 "Filter by event.timestamp_after and event.timestamp_before", 1934 []string{ 1935 "event.timestamp_after=2023-01-01T01:01:01.111Z", 1936 "event.timestamp_before=2023-04-01T01:01:01.111Z", 1937 }, 1938 6, 1939 ), 1940 Entry( 1941 "Filter by parent_policy.categories", 1942 []string{"parent_policy.categories=cat-1,cat-3"}, 1943 5, 1944 ), 1945 Entry( 1946 "Filter by parent_policy.controls", 1947 []string{"parent_policy.controls=ctrl-2"}, 1948 4, 1949 ), 1950 Entry( 1951 "Filter by parent_policy.id", 1952 []string{"parent_policy.id=2"}, 1953 4, 1954 ), 1955 Entry( 1956 "Filter by parent_policy.name", 1957 []string{"parent_policy.name=etcd-encryption1"}, 1958 2, 1959 ), 1960 Entry( 1961 "Filter by parent_policy.namespace", 1962 []string{"parent_policy.namespace=policies"}, 1963 10, 1964 ), 1965 Entry( 1966 "Filter by parent_policy.standards", 1967 []string{"parent_policy.standards=stand-2"}, 1968 4, 1969 ), 1970 Entry( 1971 "Filter by policy.apiGroup", 1972 []string{"policy.apiGroup=policy.open-cluster-management.io"}, 1973 12, 1974 ), 1975 Entry( 1976 "Filter by policy.apiGroup no results", 1977 []string{"policy.apiGroup=does-not-exist"}, 1978 1, 1979 ), 1980 Entry( 1981 "Filter by policy.id", 1982 []string{"policy.id=4"}, 1983 4, 1984 ), 1985 Entry( 1986 "Filter by policy.kind", 1987 []string{"policy.kind=ConfigurationPolicy"}, 1988 12, 1989 ), 1990 Entry( 1991 "Filter by policy.kind no results", 1992 []string{"policy.kind=something-else"}, 1993 1, 1994 ), 1995 Entry( 1996 "Filter by policy.name", 1997 []string{"policy.name=common-b"}, 1998 3, 1999 ), 2000 Entry( 2001 "Filter by policy.namespace", 2002 []string{"policy.namespace=default"}, 2003 2, 2004 ), 2005 Entry( 2006 "Filter by policy.severity", 2007 []string{"policy.severity=low"}, 2008 10, 2009 ), 2010 Entry( 2011 "Filter by policy.severity is null", 2012 []string{"policy.severity"}, 2013 3, 2014 ), 2015 ) 2016 }) 2017 }) 2018 2019 Describe("Duplicate compliance event", func() { 2020 payload1 := []byte(`{ 2021 "cluster": { 2022 "name": "managed2", 2023 "cluster_id": "test2-managed2-fake-uuid-2" 2024 }, 2025 "policy": { 2026 "apiGroup": "policy.open-cluster-management.io", 2027 "kind": "ConfigurationPolicy", 2028 "name": "duplicate-test", 2029 "spec": {"test": "two"} 2030 }, 2031 "event": { 2032 "compliance": "NonCompliant", 2033 "message": "configmaps [etcd] not found in namespace default", 2034 "timestamp": "2023-02-02T02:02:02.222Z" 2035 } 2036 }`) 2037 2038 BeforeAll(func(ctx context.Context) { 2039 By("POST the initial event") 2040 Eventually(postEvent(ctx, payload1, clientToken), "5s", "1s").ShouldNot(HaveOccurred()) 2041 }) 2042 2043 It("Should fail when posting the same compliance event", func(ctx context.Context) { 2044 err := postEvent(ctx, payload1, clientToken) 2045 Expect(err).To(MatchError(ContainSubstring("The compliance event already exists"))) 2046 }) 2047 }) 2048 2049 Describe("Test authorization", func() { 2050 Describe("Test method Get", func() { 2051 It("Should return unauthorized when it is empty token", func(ctx context.Context) { 2052 req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"/1", nil) 2053 Expect(err).ToNot(HaveOccurred()) 2054 2055 res, err := httpClient.Do(req) 2056 Expect(res.StatusCode).Should(Equal(http.StatusUnauthorized)) 2057 Expect(err).ShouldNot(HaveOccurred()) 2058 2059 req, err = http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint, nil) 2060 Expect(err).ToNot(HaveOccurred()) 2061 2062 res, err = httpClient.Do(req) 2063 Expect(res.StatusCode).Should(Equal(http.StatusUnauthorized)) 2064 Expect(err).ShouldNot(HaveOccurred()) 2065 2066 req, err = http.NewRequestWithContext(ctx, http.MethodGet, csvEndpoint, nil) 2067 Expect(err).ToNot(HaveOccurred()) 2068 2069 res, err = httpClient.Do(req) 2070 Expect(res.StatusCode).Should(Equal(http.StatusUnauthorized)) 2071 Expect(err).ShouldNot(HaveOccurred()) 2072 }) 2073 It("Should return empty data when SA does not have any GET verb to managedCluster", 2074 func(ctx context.Context) { 2075 req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint, nil) 2076 Expect(err).ShouldNot(HaveOccurred()) 2077 2078 req.Header.Set("Content-Type", "application/json") 2079 // Set auth token 2080 req.Header.Set("Authorization", "Bearer "+wrongSAToken) 2081 2082 resp, err := httpClient.Do(req) 2083 Expect(err).ShouldNot(HaveOccurred()) 2084 2085 defer resp.Body.Close() 2086 2087 body, err := io.ReadAll(resp.Body) 2088 Expect(err).ShouldNot(HaveOccurred()) 2089 2090 respJSON := map[string]any{} 2091 2092 err = json.Unmarshal(body, &respJSON) 2093 Expect(err).ShouldNot(HaveOccurred()) 2094 2095 rows, ok := respJSON["data"].([]interface{}) 2096 Expect(ok).To(BeTrue()) 2097 2098 By("Should return 0 rows") 2099 Expect(rows).Should(BeEmpty()) 2100 }) 2101 2102 It("Should return empty data when only unknown cluster IDs are provided", 2103 func(ctx context.Context) { 2104 endpoint := eventsEndpoint + "?cluster.cluster_id=does-not-exist,does-also-not-exist" 2105 req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) 2106 Expect(err).ShouldNot(HaveOccurred()) 2107 req.Header.Set("Authorization", "Bearer "+clientToken) 2108 2109 resp, err := httpClient.Do(req) 2110 Expect(err).ShouldNot(HaveOccurred()) 2111 2112 defer resp.Body.Close() 2113 2114 body, err := io.ReadAll(resp.Body) 2115 Expect(err).ShouldNot(HaveOccurred()) 2116 2117 respJSON := map[string]any{} 2118 2119 err = json.Unmarshal(body, &respJSON) 2120 Expect(err).ShouldNot(HaveOccurred()) 2121 2122 rows, ok := respJSON["data"].([]interface{}) 2123 Expect(ok).To(BeTrue()) 2124 2125 By("Should return 0 rows") 2126 Expect(rows).Should(BeEmpty()) 2127 }, 2128 ) 2129 2130 It("Should return a forbidden error when SA has only managed1 auth", 2131 func(ctx context.Context) { 2132 argument := "cluster.name=managed1,managed2,managed3" 2133 2134 By("governance-policy-propagator SA Should be able to access all") 2135 2136 respJSON, err := listEvents(ctx, clientToken, argument) 2137 Expect(err).ShouldNot(HaveOccurred()) 2138 2139 data, ok := respJSON["data"].([]any) 2140 Expect(ok).Should(BeTrue()) 2141 2142 By("Should include at least managed2 or managed3") 2143 hasVariousClusters := false 2144 for _, d := range data { 2145 complianceEvent, ok := d.(map[string]interface{}) 2146 Expect(ok).To(BeTrue()) 2147 2148 name, ok := complianceEvent["cluster"].(map[string]interface{})["name"].(string) 2149 Expect(ok).To(BeTrue()) 2150 2151 if name == "managed2" || name == "managed3" { 2152 hasVariousClusters = true 2153 2154 break 2155 } 2156 } 2157 Expect(hasVariousClusters).Should(BeTrue()) 2158 2159 req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"?"+argument, nil) 2160 Expect(err).ShouldNot(HaveOccurred()) 2161 2162 req.Header.Set("Content-Type", "application/json") 2163 // Set auth token 2164 req.Header.Set("Authorization", "Bearer "+subsetSAToken) 2165 2166 resp, err := httpClient.Do(req) 2167 Expect(err).ShouldNot(HaveOccurred()) 2168 2169 defer resp.Body.Close() 2170 2171 body, err := io.ReadAll(resp.Body) 2172 Expect(err).ShouldNot(HaveOccurred()) 2173 2174 respJSON = map[string]any{} 2175 2176 err = json.Unmarshal(body, &respJSON) 2177 Expect(err).ShouldNot(HaveOccurred()) 2178 2179 message, ok := respJSON["message"].(string) 2180 Expect(ok).To(BeTrue()) 2181 2182 Expect(message). 2183 Should(Equal("the request is not allowed: the following cluster filters are not authorized: " + 2184 "managed2, managed3")) 2185 2186 Expect(resp.StatusCode).Should(Equal(http.StatusForbidden)) 2187 Expect(err).ShouldNot(HaveOccurred()) 2188 }) 2189 It("Should return a forbidden error when only unauthorized ID are passed as id", 2190 func(ctx context.Context) { 2191 argument := "cluster.cluster_id=wrong-id,test1-managed1-fake-uuid-1,test2-managed2-fake-uuid-2" 2192 2193 req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"?"+argument, nil) 2194 Expect(err).ShouldNot(HaveOccurred()) 2195 2196 req.Header.Set("Content-Type", "application/json") 2197 // Set auth token 2198 req.Header.Set("Authorization", "Bearer "+subsetSAToken) 2199 2200 resp, err := httpClient.Do(req) 2201 Expect(err).ShouldNot(HaveOccurred()) 2202 2203 defer resp.Body.Close() 2204 2205 body, err := io.ReadAll(resp.Body) 2206 Expect(err).ShouldNot(HaveOccurred()) 2207 2208 respJSON := map[string]any{} 2209 2210 err = json.Unmarshal(body, &respJSON) 2211 Expect(err).ShouldNot(HaveOccurred()) 2212 2213 message, ok := respJSON["message"].(string) 2214 Expect(ok).To(BeTrue()) 2215 2216 By("The error message should include test2-managed2-fake-uuid-2 except managed1") 2217 Expect(message). 2218 Should(Equal( 2219 "the request is not allowed: the following cluster filters are not authorized: " + 2220 "test2-managed2-fake-uuid-2")) 2221 2222 Expect(resp.StatusCode).Should(Equal(http.StatusForbidden)) 2223 Expect(err).ShouldNot(HaveOccurred()) 2224 }) 2225 It("Should return managed1 with subset SA when the query is empty", 2226 func(ctx context.Context) { 2227 req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint, nil) 2228 Expect(err).ShouldNot(HaveOccurred()) 2229 2230 req.Header.Set("Content-Type", "application/json") 2231 // Set auth token 2232 req.Header.Set("Authorization", "Bearer "+subsetSAToken) 2233 2234 resp, err := httpClient.Do(req) 2235 Expect(err).ShouldNot(HaveOccurred()) 2236 2237 defer resp.Body.Close() 2238 2239 body, err := io.ReadAll(resp.Body) 2240 Expect(err).ShouldNot(HaveOccurred()) 2241 2242 respJSON := map[string]any{} 2243 2244 err = json.Unmarshal(body, &respJSON) 2245 Expect(err).ShouldNot(HaveOccurred()) 2246 2247 rows, ok := respJSON["data"].([]interface{}) 2248 Expect(ok).To(BeTrue()) 2249 2250 By("Should return only managed1") 2251 Expect(rows).Should(HaveLen(1)) 2252 2253 id, ok := rows[0].(map[string]interface{})["id"].(float64) 2254 Expect(ok).To(BeTrue()) 2255 2256 Expect(int(id)).Should(Equal(1)) 2257 }) 2258 }) 2259 }) 2260 }) 2261 2262 var _ = Describe("Test query generation", Label("compliance-events-api"), func() { 2263 It("Tests the select query for a cluster", func() { 2264 cluster := complianceeventsapi.Cluster{ 2265 ClusterID: "my-cluster-id", 2266 Name: "my-cluster", 2267 } 2268 sql, vals := cluster.SelectQuery("id", "spec") 2269 Expect(sql).To(Equal("SELECT id, spec FROM clusters WHERE cluster_id=$1 AND name=$2")) 2270 Expect(vals).To(HaveLen(2)) 2271 }) 2272 2273 It("Tests the select query for a minimum parent policy", func() { 2274 parent := complianceeventsapi.ParentPolicy{ 2275 Name: "parent-a", 2276 Namespace: "policies", 2277 } 2278 sql, vals := parent.SelectQuery("id", "spec") 2279 Expect(sql).To(Equal( 2280 "SELECT id, spec FROM parent_policies WHERE name=$1 AND namespace=$2 AND categories IS NULL AND " + 2281 "controls IS NULL AND standards IS NULL", 2282 )) 2283 Expect(vals).To(HaveLen(2)) 2284 }) 2285 2286 It("Tests the select query for a parent policy with all options", func() { 2287 parent := complianceeventsapi.ParentPolicy{ 2288 Name: "parent-a", 2289 Namespace: "policies", 2290 Categories: pq.StringArray{"cat-1"}, 2291 Controls: pq.StringArray{"control-1", "control-2"}, 2292 Standards: pq.StringArray{"standard-1"}, 2293 } 2294 sql, vals := parent.SelectQuery("id") 2295 Expect(sql).To(Equal( 2296 "SELECT id FROM parent_policies WHERE name=$1 AND namespace=$2 AND categories=$3 AND controls=$4 " + 2297 "AND standards=$5", 2298 )) 2299 Expect(vals).To(HaveLen(5)) 2300 }) 2301 2302 It("Tests the select query for a minimum policy", func() { 2303 policy := complianceeventsapi.Policy{ 2304 Name: "parent-a", 2305 Kind: "ConfigurationPolicy", 2306 APIGroup: "policy.open-cluster-management.io", 2307 Spec: complianceeventsapi.JSONMap{"spec": "this-out"}, 2308 } 2309 sql, vals := policy.SelectQuery("id") 2310 Expect(sql).To(Equal( 2311 "SELECT id FROM policies WHERE api_group=$1 AND kind=$2 AND name=$3 AND spec=$4 AND namespace is NULL " + 2312 "AND severity is NULL", 2313 )) 2314 Expect(vals).To(HaveLen(4)) 2315 }) 2316 2317 It("Tests the select query for a policy with all options", func() { 2318 ns := "policies" 2319 severity := "critical" 2320 2321 policy := complianceeventsapi.Policy{ 2322 Name: "parent-a", 2323 Namespace: &ns, 2324 Kind: "ConfigurationPolicy", 2325 APIGroup: "policy.open-cluster-management.io", 2326 Spec: complianceeventsapi.JSONMap{"spec": "this-out"}, 2327 Severity: &severity, 2328 } 2329 sql, vals := policy.SelectQuery("id") 2330 Expect(sql).To(Equal( 2331 "SELECT id FROM policies WHERE api_group=$1 AND kind=$2 AND name=$3 AND spec=$4 AND namespace=$5 " + 2332 "AND severity=$6", 2333 )) 2334 Expect(vals).To(HaveLen(6)) 2335 }) 2336 }) 2337 2338 func postEvent(ctx context.Context, payload []byte, token string) error { 2339 req, err := http.NewRequestWithContext(ctx, http.MethodPost, eventsEndpoint, bytes.NewBuffer(payload)) 2340 if err != nil { 2341 return err 2342 } 2343 2344 req.Header.Set("Content-Type", "application/json") 2345 req.Header.Set("Authorization", "Bearer "+token) 2346 2347 errs := make([]error, 0) 2348 2349 resp, err := httpClient.Do(req) 2350 if err != nil { 2351 errs = append(errs, err) 2352 } 2353 2354 if resp != nil { 2355 defer resp.Body.Close() 2356 2357 body, err := io.ReadAll(resp.Body) 2358 if err != nil { 2359 errs = append(errs, err) 2360 } 2361 2362 if resp.StatusCode != http.StatusCreated { 2363 errs = append(errs, fmt.Errorf("Got non-201 status code %v; response: %q", resp.StatusCode, string(body))) 2364 } 2365 } 2366 2367 return errors.Join(errs...) 2368 } 2369 2370 func listEvents(ctx context.Context, token string, queryArgs ...string) (map[string]any, error) { 2371 url := eventsEndpoint 2372 2373 if len(queryArgs) > 0 { 2374 url += "?" + strings.Join(queryArgs, "&") 2375 } 2376 2377 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 2378 if err != nil { 2379 return nil, err 2380 } 2381 2382 // Set auth token 2383 req.Header.Set("Authorization", "Bearer "+token) 2384 2385 resp, err := httpClient.Do(req) 2386 if err != nil { 2387 return nil, err 2388 } 2389 2390 defer resp.Body.Close() 2391 2392 body, err := io.ReadAll(resp.Body) 2393 if err != nil { 2394 return nil, err 2395 } 2396 2397 respJSON := map[string]any{} 2398 2399 err = json.Unmarshal(body, &respJSON) 2400 if err != nil { 2401 return nil, err 2402 } 2403 2404 if resp.StatusCode != http.StatusOK { 2405 return respJSON, fmt.Errorf("Got non-200 status code %v; response: %q", resp.StatusCode, string(body)) 2406 } 2407 2408 return respJSON, nil 2409 } 2410 2411 func getToken(ctx context.Context, ns, saName string) string { 2412 secret := &v1.Secret{} 2413 var err error 2414 2415 Eventually(func(g Gomega) error { 2416 secret, err = clientHub.CoreV1().Secrets(ns). 2417 Get(ctx, saName, metav1.GetOptions{}) 2418 2419 _, ok := secret.Data["token"] 2420 g.Expect(ok).Should(BeTrue()) 2421 2422 return err 2423 }).ShouldNot(HaveOccurred()) 2424 2425 _, ok := secret.Data["token"] 2426 Expect(ok).Should(BeTrue()) 2427 2428 return string(secret.Data["token"]) 2429 }