From d72e91abf323c5b0043f979609252017d66bae6e Mon Sep 17 00:00:00 2001 From: James Ramirez Date: Wed, 8 May 2024 19:02:49 +0100 Subject: [PATCH 1/2] fix: ensure person open issues is unmarshalled Signed-off-by: James Ramirez --- kolide/table_kolide_person_open_issue.go | 10 ++++++++-- kolide/utils.go | 15 +++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/kolide/table_kolide_person_open_issue.go b/kolide/table_kolide_person_open_issue.go index 9a7d227..daa1112 100644 --- a/kolide/table_kolide_person_open_issue.go +++ b/kolide/table_kolide_person_open_issue.go @@ -17,7 +17,7 @@ func tableKolidePersonOpenIssue(_ context.Context) *plugin.Table { Description: "Unresolved and non-exempt issues created when a device owned by a specific person fails a check; some checks, when they fail, will produce multiple Issues, each with a unique primary_key_value.", Columns: []*plugin.Column{ // Filterable "top" columns - {Name: "person_id", Description: "Canonical identifier for the person whose device this issue relates to.", Type: proto.ColumnType_STRING}, + {Name: "person_id", Description: "Canonical identifier for the person whose device this issue relates to.", Type: proto.ColumnType_STRING, Transform: transform.FromQual("person_id")}, {Name: "issue_key", Description: "Primary key that distinguishes one issue from another in the context of a single check; only applicable for checks that can produce multiple issues.", Type: proto.ColumnType_STRING}, {Name: "issue_value", Description: "Primary identifying value that distinguishes one issue from another in the context of a single check; only applicable for checks that can produce multiple issues.", Type: proto.ColumnType_STRING}, {Name: "title", Description: "Descriptive title for this issue.", Type: proto.ColumnType_STRING}, @@ -61,5 +61,11 @@ func listIssuesByPerson(ctx context.Context, d *plugin.QueryData, h *plugin.Hydr return client.GetIssuesByPerson(id, cursor, limit, searches...) } - return listAnythingById(ctx, d, h, "kolide_person_open_issue.listIssuesByPerson", "person_id", visitor, "Issues") + // Because the Kolide K2 API uses 'person_id' as a path parameter but does not, in this endpoint, return the value + // or support it as a searchable field AND because Steampipe requires it be specified as a KeyColumn in order to + // require it to be present in the SQL WHERE clause, we need to ensure it is removed from the search terms to be + // serialised by the Kolide client; otherwise it returns a 'HTTP 422 Unprocessable Entity' + var exclude = "person_id" + + return listAnythingById(ctx, d, h, "kolide_person_open_issue.listIssuesByPerson", "person_id", visitor, "Issues", exclude) } diff --git a/kolide/utils.go b/kolide/utils.go index 3cf1e3e..017ddbb 100644 --- a/kolide/utils.go +++ b/kolide/utils.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "reflect" + "slices" kolide "github.com/grendel-consulting/steampipe-plugin-kolide/kolide/client" "github.com/turbot/steampipe-plugin-sdk/v5/plugin" @@ -129,14 +130,15 @@ func getAnything(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData } -func listAnythingById(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData, callee string, id string, visitor ListByIdPredicate, target string) (interface{}, error) { +func listAnythingById(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData, callee string, id string, visitor ListByIdPredicate, target string, unfriendlies ...string) (interface{}, error) { // Fail early if unique identifier is not present uid := d.EqualsQualString(id) + if uid == "" { return nil, nil } // Create a slice to hold search queries - searches, err := query(ctx, d) + searches, err := query(ctx, d, unfriendlies...) if err != nil { plugin.Logger(ctx).Error(callee, "qualifier_operator_error", err) return nil, err @@ -196,7 +198,7 @@ func listAnythingById(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydrat return nil, nil } -func query(_ context.Context, d *plugin.QueryData) ([]kolide.Search, error) { +func query(_ context.Context, d *plugin.QueryData, unfriendlies ...string) ([]kolide.Search, error) { // Create a slice to hold search queries searches := make([]kolide.Search, 0) @@ -207,7 +209,12 @@ func query(_ context.Context, d *plugin.QueryData) ([]kolide.Search, error) { if err != nil { return nil, err } - searches = append(searches, search) + + // Exclude this item if it is not a searchable field + if !slices.Contains(unfriendlies, item.Name) { + searches = append(searches, search) + } + } } From f77210f9b31158c6035e8b5634a57f8ae8b2a45b Mon Sep 17 00:00:00 2001 From: James Ramirez Date: Wed, 8 May 2024 20:29:47 +0100 Subject: [PATCH 2/2] doc(tests): regression against #129 Signed-off-by: James Ramirez --- .../_query/kolide_unprocessable_entity.sql | 9 ++++ .../_results/kolide_unprocessable_entity.bash | 11 ++++ .../kolide_unprocessable_entity.bats | 54 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 test/end-to-end/_query/kolide_unprocessable_entity.sql create mode 100644 test/end-to-end/_results/kolide_unprocessable_entity.bash create mode 100644 test/end-to-end/kolide_unprocessable_entity.bats diff --git a/test/end-to-end/_query/kolide_unprocessable_entity.sql b/test/end-to-end/_query/kolide_unprocessable_entity.sql new file mode 100644 index 0000000..0fe82fc --- /dev/null +++ b/test/end-to-end/_query/kolide_unprocessable_entity.sql @@ -0,0 +1,9 @@ +select + title, + person_id +from + kolide_person_open_issue +where + person_id = '1607' +order by + detected_at asc; diff --git a/test/end-to-end/_results/kolide_unprocessable_entity.bash b/test/end-to-end/_results/kolide_unprocessable_entity.bash new file mode 100644 index 0000000..f1e9479 --- /dev/null +++ b/test/end-to-end/_results/kolide_unprocessable_entity.bash @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +define_test_results(){ + # Unexpected behaviour in Kolide API under K2; this endpoint returns an empty list + export EXPECTED_COUNT="0" + # export TITLE="" + # export DETECTED_AT="" + # export BLOCKS_DEVICE_AT="" + # export RESOLVED_AT="" + # export EXEMPTED="" +} diff --git a/test/end-to-end/kolide_unprocessable_entity.bats b/test/end-to-end/kolide_unprocessable_entity.bats new file mode 100644 index 0000000..e05a824 --- /dev/null +++ b/test/end-to-end/kolide_unprocessable_entity.bats @@ -0,0 +1,54 @@ +# bats file_tags=table:kolide_person_open_issue, output:issue + +setup_file() { + load "${BATS_TEST_DIRNAME}/_support/globals.bash" + define_file_globals + + define_common_test_results + + if [[ -f $EXPECTED_RESULTS ]]; then + load $EXPECTED_RESULTS + fi +} + +setup() { + load "${BATS_TEST_DIRNAME}/_support/extensions.bash" + load_helpers +} + +# Regression test for https://github.com/grendel-consulting/steampipe-plugin-kolide/issues/129 +#bats test_tags=scope:regression +@test "can_execute_query_via_steampipe" { + steampipe query $QUERY_UNDER_TEST --output json > $QUERY_RESULTS + assert_exists $QUERY_RESULTS +} + +@test "has_expected_number_of_results" { + if [[ ! -e $QUERY_RESULTS ]]; then skip ; fi + + run bash -c "cat $QUERY_RESULTS | jq -r '. | length'" + + if [[ -z "$EXPECTED_COUNT" ]]; then assert_output $EXPECTED_COUNT ; else assert [ "$output" -ge "1" ] ; fi +} + +#bats test_tags=exactness:fuzzy +@test "has_expected_title" { + if [[ ! -e $QUERY_RESULTS ]]; then skip ; fi + + run bash -c "cat $QUERY_RESULTS | jq -r '.[0].title'" + if [[ -z "$TITLE" ]]; then assert_output --partial $TITLE ; else assert_success ; fi +} + +#bats test_tags=exactness:fuzzy +@test "has_expected_person_id" { + if [[ ! -e $QUERY_RESULTS ]]; then skip ; fi + + run bash -c "cat $QUERY_RESULTS | jq -r '.[0].detected_at'" + if [[ -z "$DETECTED_AT" ]]; then assert_output --partial $DETECTED_AT ; else assert_success ; fi +} + +teardown_file(){ + if [[ -f $QUERY_RESULTS ]]; then + rm -f $QUERY_RESULTS + fi +}