Skip to content

Commit ec21bd5

Browse files
csrkibanamachine
andauthored
[Osquery] Add Scout API tests for saved queries, packs, and response actions (elastic#258534)
This PR adds initial Scout API tests for the Osquery platform plugin. This is a very early step towards breaking down the wonderful work started by PR elastic#252216 in multiple easy-to-review pieces, while still ensuring the tests follow Scout's [best practices](https://www.elastic.co/docs/extend/kibana/scout/best-practices). Note that some of the Cypress tests intercept API calls, which we usually don't recommend doing (a dedicated Scout API test ensure an isolated + clear testing environment). These API tests tests cover saved queries, packs, and detection rule response actions with RBAC permission boundaries so UI tests don't have to intercept these calls or verify this data correctness behavior. These are 7 new Scout API tests. Exact breakdown details available below. ### Coverage Parity Report | Scout spec | Focus | Migrated from FTR? | |---|---|---| | `packs_admin.spec.ts` | Profile UID on create/find | Yes (from `packs.ts`) | | `packs_editor.spec.ts` | Packs CRUD, multi-query, search/filter | Yes (from `packs.ts`) | | `packs_viewer.spec.ts` | RBAC: read allowed, write denied | **New** | | `saved_queries_admin.spec.ts` | Profile UID on create/read/find | Yes (from `saved_queries.ts`) | | `saved_queries_editor.spec.ts` | Saved queries CRUD, search/filter | Yes (from `saved_queries.ts`) | | `saved_queries_viewer.spec.ts` | RBAC: read allowed, write denied | **New** | | `response_actions_rules.spec.ts` | Detection rules with osquery actions | **New** | ### FTR tests still remaining (6 files) -- NOT migrated | FTR file | Tests | API type | Migration notes | |---|---|---|---| | `packs.ts` (2 remaining) | Fleet config multi-line/single-line query format | Internal + Fleet | Requires Fleet agent/package policy setup; consider migrating if Scout supports Fleet fixtures | | `assets.ts` | Prebuilt pack assets status, install/update | Internal (`/internal/osquery/assets`) | Good candidate for Scout migration | | `fleet_wrapper.ts` | 7 Fleet wrapper endpoints (agents, policies, package policies) | Internal (`/internal/osquery/fleet_wrapper/*`) | Requires Fleet agent enrollment; depends on `fleetAndAgents` FTR service | | `privileges_check.ts` | Superuser privileges check | Internal (`/internal/osquery/privileges_check`) | Simple test, easy to migrate | | `status.ts` | Osquery installation status | Internal (`/internal/osquery/status`) | Requires osquery package install; moderate to migrate | | `live_queries.ts` | Live query details and results | Public (`/api/osquery/live_queries`) | Uses ES directly to seed action docs; moderate to migrate | | `history_tags.ts` | 17 tag CRUD tests + aggregation + validation | Public + Internal (`/api/osquery/history`, `/internal/osquery/history/tags`) | Large test suite; uses ES directly for action/response docs; good candidate but substantial effort | ### Follow-ups Further PRs will focus on migrating some of the Cypress tests. This initial PR is a way for me / the team to get acquainted with Osquery / ensure we have some very early (though for now limited) Scout coverage. Feedback welcome! --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
1 parent 5c2278f commit ec21bd5

16 files changed

Lines changed: 1278 additions & 162 deletions

.buildkite/scout_ci_config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ plugins:
2626
- navigation
2727
- observability
2828
- observability_onboarding
29+
- osquery
2930
- painless_lab
3031
- profiling
3132
- search_getting_started

x-pack/platform/plugins/shared/osquery/moon.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ dependsOn:
8585
- '@kbn/unified-search-plugin'
8686
- '@kbn/core-notifications-browser'
8787
- '@kbn/core-chrome-browser'
88+
- '@kbn/scout'
8889
tags:
8990
- plugin
9091
- prod
@@ -98,6 +99,7 @@ fileGroups:
9899
- scripts/**/*
99100
- scripts/**/*.json
100101
- server/**/*
102+
- test/scout/**/*
101103
- public/common/schemas/*/*.json
102104
- '!target/**/*'
103105
tasks:
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { OSQUERY_API_VERSION } from '../../common/constants';
9+
10+
export { OSQUERY_API_VERSION };
11+
12+
export const COMMON_HEADERS = {
13+
'kbn-xsrf': 'some-xsrf-token',
14+
'x-elastic-internal-origin': 'kibana',
15+
'Content-Type': 'application/json;charset=UTF-8',
16+
'elastic-api-version': OSQUERY_API_VERSION,
17+
} as const;
18+
19+
export const API_PATHS = {
20+
DETECTION_RULES: 'api/detection_engine/rules',
21+
OSQUERY_SAVED_QUERIES: 'api/osquery/saved_queries',
22+
OSQUERY_PACKS: 'api/osquery/packs',
23+
} as const;
24+
25+
const uniqueId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
26+
27+
export const getMinimalRule = (overrides: Record<string, unknown> = {}) => ({
28+
type: 'query',
29+
index: ['auditbeat-*'],
30+
language: 'kuery',
31+
query: '_id:*',
32+
name: `Test rule ${uniqueId()}`,
33+
description: 'Test rule for Osquery response actions',
34+
risk_score: 21,
35+
severity: 'low',
36+
interval: '5m',
37+
from: 'now-360s',
38+
to: 'now',
39+
enabled: false,
40+
...overrides,
41+
});
42+
43+
export const getMinimalPack = (overrides: Record<string, unknown> = {}) => ({
44+
name: `test-pack-${uniqueId()}`,
45+
description: 'Test pack for Osquery Scout tests',
46+
enabled: true,
47+
queries: {
48+
testQuery: {
49+
query: 'select * from uptime;',
50+
interval: 3600,
51+
},
52+
},
53+
shards: {},
54+
...overrides,
55+
});
56+
57+
export const getMinimalSavedQuery = (overrides: Record<string, unknown> = {}) => ({
58+
id: `test-saved-query-${uniqueId()}`,
59+
query: 'select 1;',
60+
interval: '3600',
61+
...overrides,
62+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { ApiServicesFixture, ScoutTestFixtures, ScoutWorkerFixtures } from '@kbn/scout';
9+
import { apiTest as baseApiTest } from '@kbn/scout';
10+
import {
11+
getOsqueryApiService,
12+
type OsqueryApiService,
13+
} from '../../common/services/osquery_api_service';
14+
15+
export interface OsqueryApiServicesFixture extends ApiServicesFixture {
16+
osquery: OsqueryApiService;
17+
}
18+
19+
export const apiTest = baseApiTest.extend<
20+
ScoutTestFixtures,
21+
{ apiServices: OsqueryApiServicesFixture }
22+
>({
23+
apiServices: [
24+
async (
25+
{
26+
apiServices,
27+
kbnClient,
28+
log,
29+
}: {
30+
apiServices: ApiServicesFixture;
31+
kbnClient: ScoutWorkerFixtures['kbnClient'];
32+
log: ScoutWorkerFixtures['log'];
33+
},
34+
use: (extendedApiServices: OsqueryApiServicesFixture) => Promise<void>
35+
) => {
36+
const extendedApiServices = apiServices as OsqueryApiServicesFixture;
37+
extendedApiServices.osquery = getOsqueryApiService({ kbnClient, log });
38+
await use(extendedApiServices);
39+
},
40+
{ scope: 'worker' },
41+
],
42+
});
43+
44+
export * as testData from './constants';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { createPlaywrightConfig } from '@kbn/scout';
9+
10+
export default createPlaywrightConfig({
11+
testDir: './tests',
12+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { RoleSessionCredentials } from '@kbn/scout';
9+
import { expect } from '@kbn/scout/api';
10+
import { tags } from '@kbn/scout';
11+
import { apiTest, testData } from '../fixtures';
12+
13+
// TODO: run on ECH once PR #258866 makes it to prod
14+
apiTest.describe(
15+
'Osquery packs - admin',
16+
{
17+
tag: ['@local-stateful-classic', ...tags.serverless.security.complete],
18+
},
19+
() => {
20+
let adminCredentials: RoleSessionCredentials;
21+
const createdPackIds: string[] = [];
22+
23+
apiTest.beforeAll(async ({ samlAuth }) => {
24+
// TODO: investigate why this test only passes with cookie-based authentication while similar
25+
// tests (saved_queries_admin.spec.ts) pass with API key-based authentication
26+
adminCredentials = await samlAuth.asInteractiveUser('admin');
27+
});
28+
29+
apiTest.afterAll(async ({ apiServices }) => {
30+
for (const packId of createdPackIds) {
31+
await apiServices.osquery.packs.delete(packId);
32+
}
33+
});
34+
35+
apiTest('includes profile_uid fields on create and find', async ({ apiClient }) => {
36+
const createResponse = await apiClient.post(testData.API_PATHS.OSQUERY_PACKS, {
37+
headers: { ...testData.COMMON_HEADERS, ...adminCredentials.cookieHeader },
38+
body: testData.getMinimalPack(),
39+
responseType: 'json',
40+
});
41+
expect(createResponse).toHaveStatusCode(200);
42+
expect(createResponse.body.data).toBeDefined();
43+
createdPackIds.push(createResponse.body.data.saved_object_id);
44+
45+
expect('created_by_profile_uid' in createResponse.body.data).toBe(true);
46+
expect('updated_by_profile_uid' in createResponse.body.data).toBe(true);
47+
expect(createResponse.body.data.created_by).toBeDefined();
48+
49+
const findResponse = await apiClient.get(
50+
`${testData.API_PATHS.OSQUERY_PACKS}?search=${createResponse.body.data.name}`,
51+
{
52+
headers: { ...testData.COMMON_HEADERS, ...adminCredentials.cookieHeader },
53+
responseType: 'json',
54+
}
55+
);
56+
expect(findResponse).toHaveStatusCode(200);
57+
expect(findResponse.body.data).toBeDefined();
58+
expect('created_by_profile_uid' in findResponse.body.data[0]).toBe(true);
59+
});
60+
}
61+
);

0 commit comments

Comments
 (0)