mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 03:01:21 -04:00
[FTR] support "deployment agnostic" api-integration tests (#189853)
## Summary
### This PR introduces a new type of API integration tests in FTR:
deployment-agnostic

#### Test suite is considered deployment-agnostic when it fulfils the
following criteria:
**Functionality**: It tests Kibana APIs that are **logically identical
in both stateful and serverless environments** for the same SAML roles.
**Design**: The test design is **clean and does not require additional
logic** to execute in either stateful or serverless environments.
### How It Works
Most existing stateful tests use basic authentication for API testing.
In contrast, serverless tests use SAML authentication with
project-specific role mapping.
Since stateful deployments also support SAML, deployment-agnostic tests
**configure Elasticsearch and Kibana with SAML authentication in both
cases**. For roles, stateful deployments define 'viewer', 'editor', and
'admin' roles with serverless-alike privileges.
New `samlAuth` service has `AuthProvider` interface with 2 different
implementations: depending on environment context (serverless or
stateful) appropriate implementation is used. But it remains on service
level and hidden in test suite.
test example
```
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
const samlAuth = getService('samlAuth');
const supertestWithoutAuth = getService('supertestWithoutAuth');
let roleAuthc: RoleCredentials;
let internalHeaders: InternalRequestHeader;
describe('GET /api/console/api_server', () => {
before(async () => {
roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin');
internalHeaders = samlAuth.getInternalRequestHeader();
});
after(async () => {
await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc);
});
it('returns autocomplete definitions', async () => {
const { body } = await supertestWithoutAuth
.get('/api/console/api_server')
.set(roleAuthc.apiKeyHeader)
.set(internalHeaders)
.set('kbn-xsrf', 'true')
.expect(200);
expect(body.es).to.be.ok();
const {
es: { name, globals, endpoints },
} = body;
expect(name).to.be.ok();
expect(Object.keys(globals).length).to.be.above(0);
expect(Object.keys(endpoints).length).to.be.above(0);
});
});
}
```
Please read
[readme](966822ac87/x-pack/test/api_integration/deployment_agnostic/README.md
)
for more details and step-by-step guide. It should help migrating
existing serverless tests to deployment-agnostic, assuming requirements
are met.
### Examples
Deployment-agnostic tests:
```
x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.ts
x-pack/test/api_integration/deployment_agnostic/apis/core/compression.ts
x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/painless_lab.ts
```
Configs to run it:
```
node scripts/functional_tests --config x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts
node scripts/functional_tests --config x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts
node scripts/functional_tests --config x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts
node scripts/functional_tests --config x-pack/test/api_integration/deployment_agnostic/stateful.config.ts
```
PR is a compact version of #188737 with reduced changes in existing
serverless tests.
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: elena-shostak <165678770+elena-shostak@users.noreply.github.com>
Co-authored-by: Aleh Zasypkin <aleh.zasypkin@gmail.com>
This commit is contained in:
parent
f3aeb81fd6
commit
7df01e99c1
72 changed files with 1758 additions and 422 deletions
|
@ -1,6 +1,8 @@
|
||||||
disabled:
|
disabled:
|
||||||
# Base config files, only necessary to inform config finding script
|
# Base config files, only necessary to inform config finding script
|
||||||
|
|
||||||
|
# Serverless deployment-agnostic default config for api-integration tests
|
||||||
|
- x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts
|
||||||
# Serverless base config files
|
# Serverless base config files
|
||||||
- x-pack/test_serverless/api_integration/config.base.ts
|
- x-pack/test_serverless/api_integration/config.base.ts
|
||||||
- x-pack/test_serverless/functional/config.base.ts
|
- x-pack/test_serverless/functional/config.base.ts
|
||||||
|
|
|
@ -26,3 +26,5 @@ enabled:
|
||||||
- x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group5.ts
|
- x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group5.ts
|
||||||
- x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group6.ts
|
- x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group6.ts
|
||||||
- x-pack/test_serverless/functional/test_suites/observability/config.screenshots.ts
|
- x-pack/test_serverless/functional/test_suites/observability/config.screenshots.ts
|
||||||
|
# serverless config files that run deployment-agnostic tests
|
||||||
|
- x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
disabled:
|
disabled:
|
||||||
|
# Stateful base config for deployment-agnostic tests
|
||||||
|
- x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts
|
||||||
# Base config files, only necessary to inform config finding script
|
# Base config files, only necessary to inform config finding script
|
||||||
- test/functional/config.base.js
|
- test/functional/config.base.js
|
||||||
- test/functional/firefox/config.base.ts
|
- test/functional/firefox/config.base.ts
|
||||||
|
@ -155,7 +157,6 @@ enabled:
|
||||||
- x-pack/test/api_integration/apis/monitoring/config.ts
|
- x-pack/test/api_integration/apis/monitoring/config.ts
|
||||||
- x-pack/test/api_integration/apis/monitoring_collection/config.ts
|
- x-pack/test/api_integration/apis/monitoring_collection/config.ts
|
||||||
- x-pack/test/api_integration/apis/osquery/config.ts
|
- x-pack/test/api_integration/apis/osquery/config.ts
|
||||||
- x-pack/test/api_integration/apis/painless_lab/config.ts
|
|
||||||
- x-pack/test/api_integration/apis/search/config.ts
|
- x-pack/test/api_integration/apis/search/config.ts
|
||||||
- x-pack/test/api_integration/apis/searchprofiler/config.ts
|
- x-pack/test/api_integration/apis/searchprofiler/config.ts
|
||||||
- x-pack/test/api_integration/apis/security/config.ts
|
- x-pack/test/api_integration/apis/security/config.ts
|
||||||
|
@ -359,3 +360,5 @@ enabled:
|
||||||
- x-pack/performance/journeys_e2e/apm_service_inventory.ts
|
- x-pack/performance/journeys_e2e/apm_service_inventory.ts
|
||||||
- x-pack/performance/journeys_e2e/infra_hosts_view.ts
|
- x-pack/performance/journeys_e2e/infra_hosts_view.ts
|
||||||
- x-pack/test/custom_branding/config.ts
|
- x-pack/test/custom_branding/config.ts
|
||||||
|
# stateful config files that run deployment-agnostic tests
|
||||||
|
- x-pack/test/api_integration/deployment_agnostic/stateful.config.ts
|
||||||
|
|
|
@ -16,3 +16,5 @@ enabled:
|
||||||
- x-pack/test_serverless/functional/test_suites/search/common_configs/config.group4.ts
|
- x-pack/test_serverless/functional/test_suites/search/common_configs/config.group4.ts
|
||||||
- x-pack/test_serverless/functional/test_suites/search/common_configs/config.group5.ts
|
- x-pack/test_serverless/functional/test_suites/search/common_configs/config.group5.ts
|
||||||
- x-pack/test_serverless/functional/test_suites/search/common_configs/config.group6.ts
|
- x-pack/test_serverless/functional/test_suites/search/common_configs/config.group6.ts
|
||||||
|
# serverless config files that run deployment-agnostic tests
|
||||||
|
- x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts
|
||||||
|
|
|
@ -97,3 +97,5 @@ enabled:
|
||||||
- x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/configs/serverless.config.ts
|
- x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/configs/serverless.config.ts
|
||||||
- x-pack/test/security_solution_endpoint/configs/serverless.endpoint.config.ts
|
- x-pack/test/security_solution_endpoint/configs/serverless.endpoint.config.ts
|
||||||
- x-pack/test/security_solution_endpoint/configs/serverless.integrations.config.ts
|
- x-pack/test/security_solution_endpoint/configs/serverless.integrations.config.ts
|
||||||
|
# serverless config files that run deployment-agnostic tests
|
||||||
|
- x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts
|
||||||
|
|
|
@ -618,7 +618,7 @@ module.exports = {
|
||||||
'test/*/*.config.ts',
|
'test/*/*.config.ts',
|
||||||
'test/*/{tests,test_suites,apis,apps}/**/*',
|
'test/*/{tests,test_suites,apis,apps}/**/*',
|
||||||
'test/server_integration/**/*.ts',
|
'test/server_integration/**/*.ts',
|
||||||
'x-pack/test/*/{tests,test_suites,apis,apps}/**/*',
|
'x-pack/test/*/{tests,test_suites,apis,apps,deployment_agnostic}/**/*',
|
||||||
'x-pack/test/*/*config.*ts',
|
'x-pack/test/*/*config.*ts',
|
||||||
'x-pack/test/saved_object_api_integration/*/apis/**/*',
|
'x-pack/test/saved_object_api_integration/*/apis/**/*',
|
||||||
'x-pack/test/ui_capabilities/*/tests/**/*',
|
'x-pack/test/ui_capabilities/*/tests/**/*',
|
||||||
|
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -1274,6 +1274,7 @@ x-pack/test/observability_ai_assistant_functional @elastic/obs-ai-assistant
|
||||||
/x-pack/test_serverless/functional/test_suites/security/ftr/ @elastic/appex-qa
|
/x-pack/test_serverless/functional/test_suites/security/ftr/ @elastic/appex-qa
|
||||||
/x-pack/test_serverless/functional/test_suites/common/home_page/ @elastic/appex-qa
|
/x-pack/test_serverless/functional/test_suites/common/home_page/ @elastic/appex-qa
|
||||||
/x-pack/test_serverless/**/services/ @elastic/appex-qa
|
/x-pack/test_serverless/**/services/ @elastic/appex-qa
|
||||||
|
/packages/kbn-es/src/stateful_resources/roles.yml @elastic/appex-qa
|
||||||
|
|
||||||
# Core
|
# Core
|
||||||
/config/ @elastic/kibana-core
|
/config/ @elastic/kibana-core
|
||||||
|
|
|
@ -21,4 +21,4 @@ export {
|
||||||
readRolesDescriptorsFromResource,
|
readRolesDescriptorsFromResource,
|
||||||
} from './src/utils';
|
} from './src/utils';
|
||||||
export type { ArtifactLicense } from './src/artifact';
|
export type { ArtifactLicense } from './src/artifact';
|
||||||
export { SERVERLESS_ROLES_ROOT_PATH } from './src/paths';
|
export { SERVERLESS_ROLES_ROOT_PATH, STATEFUL_ROLES_ROOT_PATH } from './src/paths';
|
||||||
|
|
|
@ -25,6 +25,8 @@ export const ES_CONFIG = 'config/elasticsearch.yml';
|
||||||
|
|
||||||
export const ES_KEYSTORE_BIN = maybeUseBat('./bin/elasticsearch-keystore');
|
export const ES_KEYSTORE_BIN = maybeUseBat('./bin/elasticsearch-keystore');
|
||||||
|
|
||||||
|
export const STATEFUL_ROLES_ROOT_PATH = resolve(__dirname, './stateful_resources');
|
||||||
|
|
||||||
export const SERVERLESS_OPERATOR_USERS_PATH = resolve(
|
export const SERVERLESS_OPERATOR_USERS_PATH = resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
'./serverless_resources/operator_users.yml'
|
'./serverless_resources/operator_users.yml'
|
||||||
|
|
130
packages/kbn-es/src/stateful_resources/roles.yml
Normal file
130
packages/kbn-es/src/stateful_resources/roles.yml
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
# -----
|
||||||
|
# This file is for information purpose only. 'viewer' and 'editor' roles are defined in stateful Elasticsearch by default
|
||||||
|
# Source: https://github.com/elastic/elasticsearch/blob/4272164530807787d4d8b991e3095a6e79176dbf/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java#L861-L952
|
||||||
|
# Note: inconsistency between these roles definition and the same roles of serverless project may break FTR deployment-agnostic tests
|
||||||
|
# -----
|
||||||
|
viewer:
|
||||||
|
cluster: []
|
||||||
|
indices:
|
||||||
|
- names:
|
||||||
|
- '.alerts*'
|
||||||
|
- '.preview.alerts*'
|
||||||
|
privileges:
|
||||||
|
- 'read'
|
||||||
|
- 'view_index_metadata'
|
||||||
|
allow_restricted_indices: false
|
||||||
|
- names:
|
||||||
|
- '.items-*'
|
||||||
|
- '.lists-*'
|
||||||
|
- '.siem-signals*'
|
||||||
|
privileges:
|
||||||
|
- 'read'
|
||||||
|
- 'view_index_metadata'
|
||||||
|
allow_restricted_indices: false
|
||||||
|
- names:
|
||||||
|
- '/~(([.]|ilm-history-).*)/'
|
||||||
|
privileges:
|
||||||
|
- 'read'
|
||||||
|
- 'view_index_metadata'
|
||||||
|
allow_restricted_indices: false
|
||||||
|
- names:
|
||||||
|
- '.profiling-*'
|
||||||
|
- 'profiling-*'
|
||||||
|
privileges:
|
||||||
|
- 'read'
|
||||||
|
- 'view_index_metadata'
|
||||||
|
allow_restricted_indices: false
|
||||||
|
applications:
|
||||||
|
- application: 'kibana-.kibana'
|
||||||
|
privileges:
|
||||||
|
- 'read'
|
||||||
|
resources:
|
||||||
|
- '*'
|
||||||
|
run_as: []
|
||||||
|
|
||||||
|
editor:
|
||||||
|
cluster: []
|
||||||
|
indices:
|
||||||
|
- names:
|
||||||
|
- 'observability-annotations'
|
||||||
|
privileges:
|
||||||
|
- 'read'
|
||||||
|
- 'view_index_metadata'
|
||||||
|
- 'write'
|
||||||
|
allow_restricted_indices: false
|
||||||
|
- names:
|
||||||
|
- '.items-*'
|
||||||
|
- '.lists-*'
|
||||||
|
- '.siem-signals*'
|
||||||
|
privileges:
|
||||||
|
- 'maintenance'
|
||||||
|
- 'read'
|
||||||
|
- 'view_index_metadata'
|
||||||
|
- 'write'
|
||||||
|
allow_restricted_indices: false
|
||||||
|
- names:
|
||||||
|
- '/~(([.]|ilm-history-).*)/'
|
||||||
|
privileges:
|
||||||
|
- 'read'
|
||||||
|
- 'view_index_metadata'
|
||||||
|
allow_restricted_indices: false
|
||||||
|
- names:
|
||||||
|
- '.profiling-*'
|
||||||
|
- 'profiling-*'
|
||||||
|
privileges:
|
||||||
|
- 'read'
|
||||||
|
- 'view_index_metadata'
|
||||||
|
allow_restricted_indices: false
|
||||||
|
- names:
|
||||||
|
- '.alerts*'
|
||||||
|
- '.internal.alerts*'
|
||||||
|
- '.internal.preview.alerts*'
|
||||||
|
- '.preview.alerts*'
|
||||||
|
privileges:
|
||||||
|
- 'maintenance'
|
||||||
|
- 'read'
|
||||||
|
- 'view_index_metadata'
|
||||||
|
- 'write'
|
||||||
|
allow_restricted_indices: false
|
||||||
|
applications:
|
||||||
|
- application: 'kibana-.kibana'
|
||||||
|
privileges:
|
||||||
|
- 'all'
|
||||||
|
resources:
|
||||||
|
- '*'
|
||||||
|
run_as: []
|
||||||
|
|
||||||
|
# Admin role without 'remote_indices' access definition
|
||||||
|
# There is no such built-in role in stateful, and it's a role "similar" to the built-in 'admin' role in serverless
|
||||||
|
admin:
|
||||||
|
# TODO: 'all' should be replaced with explicit list both here and serverless for deployment-agnostic tests with 'admin' role to be compatible
|
||||||
|
cluster: ['all']
|
||||||
|
indices:
|
||||||
|
- names: ['*']
|
||||||
|
privileges: ['all']
|
||||||
|
allow_restricted_indices: false
|
||||||
|
- names: ['*']
|
||||||
|
privileges:
|
||||||
|
- 'monitor'
|
||||||
|
- 'read'
|
||||||
|
- 'read_cross_cluster'
|
||||||
|
- 'view_index_metadata'
|
||||||
|
allow_restricted_indices: true
|
||||||
|
applications:
|
||||||
|
- application: '*'
|
||||||
|
privileges: ['*']
|
||||||
|
resources: ['*']
|
||||||
|
run_as: ['*']
|
||||||
|
|
||||||
|
# temporarily added for testing purpose
|
||||||
|
system_indices_superuser:
|
||||||
|
cluster: ['all']
|
||||||
|
indices:
|
||||||
|
- names: ['*']
|
||||||
|
privileges: ['all']
|
||||||
|
allow_restricted_indices: true
|
||||||
|
applications:
|
||||||
|
- application: '*'
|
||||||
|
privileges: ['*']
|
||||||
|
resources: ['*']
|
||||||
|
run_as: ['*']
|
|
@ -23,4 +23,6 @@ export type Es = ProvidedType<typeof EsProvider>;
|
||||||
import { SupertestWithoutAuthProvider } from './services/supertest_without_auth';
|
import { SupertestWithoutAuthProvider } from './services/supertest_without_auth';
|
||||||
export type SupertestWithoutAuthProviderType = ProvidedType<typeof SupertestWithoutAuthProvider>;
|
export type SupertestWithoutAuthProviderType = ProvidedType<typeof SupertestWithoutAuthProvider>;
|
||||||
|
|
||||||
|
export type { InternalRequestHeader, RoleCredentials } from './services/saml_auth';
|
||||||
|
|
||||||
export type { FtrProviderContext } from './services/ftr_provider_context';
|
export type { FtrProviderContext } from './services/ftr_provider_context';
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { EsProvider } from './es';
|
||||||
import { KibanaServerProvider } from './kibana_server';
|
import { KibanaServerProvider } from './kibana_server';
|
||||||
import { RetryService } from './retry';
|
import { RetryService } from './retry';
|
||||||
import { SupertestWithoutAuthProvider } from './supertest_without_auth';
|
import { SupertestWithoutAuthProvider } from './supertest_without_auth';
|
||||||
|
import { SamlAuthProvider } from './saml_auth';
|
||||||
|
|
||||||
export const services = {
|
export const services = {
|
||||||
es: EsProvider,
|
es: EsProvider,
|
||||||
|
@ -18,4 +19,5 @@ export const services = {
|
||||||
esArchiver: EsArchiverProvider,
|
esArchiver: EsArchiverProvider,
|
||||||
retry: RetryService,
|
retry: RetryService,
|
||||||
supertestWithoutAuth: SupertestWithoutAuthProvider,
|
supertestWithoutAuth: SupertestWithoutAuthProvider,
|
||||||
|
samlAuth: SamlAuthProvider,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const COMMON_REQUEST_HEADERS = {
|
||||||
|
'kbn-xsrf': 'some-xsrf-token',
|
||||||
|
};
|
||||||
|
|
||||||
|
// possible change in 9.0 to match serverless
|
||||||
|
const STATEFUL_INTERNAL_REQUEST_HEADERS = {
|
||||||
|
...COMMON_REQUEST_HEADERS,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SERVERLESS_INTERNAL_REQUEST_HEADERS = {
|
||||||
|
...COMMON_REQUEST_HEADERS,
|
||||||
|
'x-elastic-internal-origin': 'kibana',
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InternalRequestHeader =
|
||||||
|
| typeof STATEFUL_INTERNAL_REQUEST_HEADERS
|
||||||
|
| typeof SERVERLESS_INTERNAL_REQUEST_HEADERS;
|
||||||
|
|
||||||
|
export const getServerlessInternalRequestHeaders = (): InternalRequestHeader => {
|
||||||
|
return SERVERLESS_INTERNAL_REQUEST_HEADERS;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStatefulInternalRequestHeaders = (): InternalRequestHeader => {
|
||||||
|
return STATEFUL_INTERNAL_REQUEST_HEADERS;
|
||||||
|
};
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import { type Config } from '@kbn/test';
|
||||||
|
import { ToolingLog } from '@kbn/tooling-log';
|
||||||
|
import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils';
|
||||||
|
import { KibanaServer } from '../..';
|
||||||
|
|
||||||
|
import { ServerlessAuthProvider } from './serverless/auth_provider';
|
||||||
|
import { StatefulAuthProvider } from './stateful/auth_provider';
|
||||||
|
import { createRole, createRoleMapping } from './stateful/create_role_mapping';
|
||||||
|
|
||||||
|
const STATEFUL_ADMIN_ROLE_MAPPING_PATH = './stateful/admin_mapping';
|
||||||
|
|
||||||
|
export interface AuthProvider {
|
||||||
|
getSupportedRoleDescriptors(): any;
|
||||||
|
getDefaultRole(): string;
|
||||||
|
getRolesDefinitionPath(): string;
|
||||||
|
getCommonRequestHeader(): { [key: string]: string };
|
||||||
|
getInternalRequestHeader(): { [key: string]: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthProviderProps {
|
||||||
|
config: Config;
|
||||||
|
kibanaServer: KibanaServer;
|
||||||
|
log: ToolingLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAuthProvider = async (props: AuthProviderProps) => {
|
||||||
|
const { config, log, kibanaServer } = props;
|
||||||
|
const isServerless = !!props.config.get('serverless');
|
||||||
|
if (isServerless) {
|
||||||
|
return new ServerlessAuthProvider(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = new StatefulAuthProvider();
|
||||||
|
// TODO: Move it to @kbn-es package, so that roles and its mapping are created before FTR services loading starts.
|
||||||
|
// 'viewer' and 'editor' roles are available by default, but we have to create 'admin' role
|
||||||
|
const adminRoleMapping = JSON.parse(
|
||||||
|
fs.readFileSync(require.resolve(STATEFUL_ADMIN_ROLE_MAPPING_PATH), 'utf8')
|
||||||
|
);
|
||||||
|
await createRole({ roleName: 'admin', roleMapping: adminRoleMapping, kibanaServer, log });
|
||||||
|
const roles = Object.keys(provider.getSupportedRoleDescriptors());
|
||||||
|
// Creating roles mapping for mock-idp
|
||||||
|
await createRoleMapping({ name: MOCK_IDP_REALM_NAME, roles, config, log });
|
||||||
|
return provider;
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
export { SamlAuthProvider } from './saml_auth_provider';
|
||||||
|
export type { RoleCredentials } from './saml_auth_provider';
|
||||||
|
export type { InternalRequestHeader } from './default_request_headers';
|
|
@ -1,18 +1,18 @@
|
||||||
/*
|
/*
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
* 2.0.
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ServerlessProjectType, SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es';
|
|
||||||
import { SamlSessionManager } from '@kbn/test';
|
import { SamlSessionManager } from '@kbn/test';
|
||||||
import { readRolesDescriptorsFromResource } from '@kbn/es';
|
|
||||||
import { resolve } from 'path';
|
|
||||||
import { Role } from '@kbn/test/src/auth/types';
|
|
||||||
import { isServerlessProjectType } from '@kbn/es/src/utils';
|
|
||||||
import expect from '@kbn/expect';
|
import expect from '@kbn/expect';
|
||||||
import { FtrProviderContext } from '../../functional/ftr_provider_context';
|
import { REPO_ROOT } from '@kbn/repo-info';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { FtrProviderContext } from '../ftr_provider_context';
|
||||||
|
import { getAuthProvider } from './get_auth_provider';
|
||||||
|
import { InternalRequestHeader } from './default_request_headers';
|
||||||
|
|
||||||
export interface RoleCredentials {
|
export interface RoleCredentials {
|
||||||
apiKey: { id: string; name: string };
|
apiKey: { id: string; name: string };
|
||||||
|
@ -20,63 +20,41 @@ export interface RoleCredentials {
|
||||||
cookieHeader: { Cookie: string };
|
cookieHeader: { Cookie: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SvlUserManagerProvider({ getService }: FtrProviderContext) {
|
export async function SamlAuthProvider({ getService }: FtrProviderContext) {
|
||||||
const config = getService('config');
|
const config = getService('config');
|
||||||
const log = getService('log');
|
const log = getService('log');
|
||||||
const svlCommonApi = getService('svlCommonApi');
|
const kibanaServer = getService('kibanaServer');
|
||||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||||
const isCloud = !!process.env.TEST_CLOUD;
|
const isCloud = !!process.env.TEST_CLOUD;
|
||||||
const kbnServerArgs = config.get('kbnTestServer.serverArgs') as string[];
|
|
||||||
const projectType = kbnServerArgs
|
|
||||||
.filter((arg) => arg.startsWith('--serverless'))
|
|
||||||
.reduce((acc, arg) => {
|
|
||||||
const match = arg.match(/--serverless[=\s](\w+)/);
|
|
||||||
return acc + (match ? match[1] : '');
|
|
||||||
}, '') as ServerlessProjectType;
|
|
||||||
|
|
||||||
if (!isServerlessProjectType(projectType)) {
|
|
||||||
throw new Error(`Unsupported serverless projectType: ${projectType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const supportedRoleDescriptors = readRolesDescriptorsFromResource(
|
|
||||||
resolve(SERVERLESS_ROLES_ROOT_PATH, projectType, 'roles.yml')
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const authRoleProvider = await getAuthProvider({ config, kibanaServer, log });
|
||||||
|
const supportedRoleDescriptors = authRoleProvider.getSupportedRoleDescriptors();
|
||||||
const supportedRoles = Object.keys(supportedRoleDescriptors);
|
const supportedRoles = Object.keys(supportedRoleDescriptors);
|
||||||
|
|
||||||
const defaultRolesToMap = new Map<string, Role>([
|
|
||||||
['es', 'developer'],
|
|
||||||
['security', 'editor'],
|
|
||||||
['oblt', 'editor'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
const getDefaultRole = () => {
|
|
||||||
if (defaultRolesToMap.has(projectType)) {
|
|
||||||
return defaultRolesToMap.get(projectType)!;
|
|
||||||
} else {
|
|
||||||
throw new Error(`Default role is not defined for ${projectType} project`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const customRolesFileName: string | undefined = process.env.ROLES_FILENAME_OVERRIDE;
|
const customRolesFileName: string | undefined = process.env.ROLES_FILENAME_OVERRIDE;
|
||||||
// Sharing the instance within FTR config run means cookies are persistent for each role between tests.
|
const cloudUsersFilePath = resolve(REPO_ROOT, '.ftr', customRolesFileName ?? 'role_users.json');
|
||||||
const sessionManager = new SamlSessionManager(
|
|
||||||
{
|
|
||||||
hostOptions: {
|
|
||||||
protocol: config.get('servers.kibana.protocol'),
|
|
||||||
hostname: config.get('servers.kibana.hostname'),
|
|
||||||
port: isCloud ? undefined : config.get('servers.kibana.port'),
|
|
||||||
username: config.get('servers.kibana.username'),
|
|
||||||
password: config.get('servers.kibana.password'),
|
|
||||||
},
|
|
||||||
log,
|
|
||||||
isCloud,
|
|
||||||
supportedRoles,
|
|
||||||
},
|
|
||||||
customRolesFileName
|
|
||||||
);
|
|
||||||
|
|
||||||
const DEFAULT_ROLE = getDefaultRole();
|
// Sharing the instance within FTR config run means cookies are persistent for each role between tests.
|
||||||
|
const sessionManager = new SamlSessionManager({
|
||||||
|
hostOptions: {
|
||||||
|
protocol: config.get('servers.kibana.protocol'),
|
||||||
|
hostname: config.get('servers.kibana.hostname'),
|
||||||
|
port: isCloud ? undefined : config.get('servers.kibana.port'),
|
||||||
|
username: config.get('servers.kibana.username'),
|
||||||
|
password: config.get('servers.kibana.password'),
|
||||||
|
},
|
||||||
|
log,
|
||||||
|
isCloud,
|
||||||
|
supportedRoles: {
|
||||||
|
roles: supportedRoles,
|
||||||
|
sourcePath: authRoleProvider.getRolesDefinitionPath(),
|
||||||
|
},
|
||||||
|
cloudUsersFilePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_ROLE = authRoleProvider.getDefaultRole();
|
||||||
|
const COMMON_REQUEST_HEADERS = authRoleProvider.getCommonRequestHeader();
|
||||||
|
const INTERNAL_REQUEST_HEADERS = authRoleProvider.getInternalRequestHeader();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async getInteractiveUserSessionCookieWithRoleScope(role: string) {
|
async getInteractiveUserSessionCookieWithRoleScope(role: string) {
|
||||||
|
@ -119,7 +97,7 @@ export function SvlUserManagerProvider({ getService }: FtrProviderContext) {
|
||||||
|
|
||||||
const { body, status } = await supertestWithoutAuth
|
const { body, status } = await supertestWithoutAuth
|
||||||
.post('/internal/security/api_key')
|
.post('/internal/security/api_key')
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(INTERNAL_REQUEST_HEADERS)
|
||||||
.set(adminCookieHeader)
|
.set(adminCookieHeader)
|
||||||
.send({
|
.send({
|
||||||
name: 'myTestApiKey',
|
name: 'myTestApiKey',
|
||||||
|
@ -147,12 +125,19 @@ export function SvlUserManagerProvider({ getService }: FtrProviderContext) {
|
||||||
|
|
||||||
const { status } = await supertestWithoutAuth
|
const { status } = await supertestWithoutAuth
|
||||||
.post('/internal/security/api_key/invalidate')
|
.post('/internal/security/api_key/invalidate')
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(INTERNAL_REQUEST_HEADERS)
|
||||||
.set(roleCredentials.cookieHeader)
|
.set(roleCredentials.cookieHeader)
|
||||||
.send(requestBody);
|
.send(requestBody);
|
||||||
|
|
||||||
expect(status).to.be(200);
|
expect(status).to.be(200);
|
||||||
},
|
},
|
||||||
|
getCommonRequestHeader() {
|
||||||
|
return COMMON_REQUEST_HEADERS;
|
||||||
|
},
|
||||||
|
|
||||||
|
getInternalRequestHeader(): InternalRequestHeader {
|
||||||
|
return INTERNAL_REQUEST_HEADERS;
|
||||||
|
},
|
||||||
DEFAULT_ROLE,
|
DEFAULT_ROLE,
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ServerlessProjectType, SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es';
|
||||||
|
import { type Config } from '@kbn/test';
|
||||||
|
import { isServerlessProjectType, readRolesDescriptorsFromResource } from '@kbn/es/src/utils';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { Role } from '@kbn/test/src/auth/types';
|
||||||
|
import {
|
||||||
|
getServerlessInternalRequestHeaders,
|
||||||
|
COMMON_REQUEST_HEADERS,
|
||||||
|
} from '../default_request_headers';
|
||||||
|
import { AuthProvider } from '../get_auth_provider';
|
||||||
|
|
||||||
|
const projectDefaultRoles = new Map<string, Role>([
|
||||||
|
['es', 'developer'],
|
||||||
|
['security', 'editor'],
|
||||||
|
['oblt', 'editor'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const getDefaultServerlessRole = (projectType: string) => {
|
||||||
|
if (projectDefaultRoles.has(projectType)) {
|
||||||
|
return projectDefaultRoles.get(projectType)!;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Default role is not defined for ${projectType} project`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ServerlessAuthProvider implements AuthProvider {
|
||||||
|
private readonly projectType: string;
|
||||||
|
private readonly rolesDefinitionPath: string;
|
||||||
|
|
||||||
|
constructor(config: Config) {
|
||||||
|
const kbnServerArgs = config.get('kbnTestServer.serverArgs') as string[];
|
||||||
|
this.projectType = kbnServerArgs.reduce((acc, arg) => {
|
||||||
|
const match = arg.match(/--serverless[=\s](\w+)/);
|
||||||
|
return acc + (match ? match[1] : '');
|
||||||
|
}, '') as ServerlessProjectType;
|
||||||
|
|
||||||
|
if (!isServerlessProjectType(this.projectType)) {
|
||||||
|
throw new Error(`Unsupported serverless projectType: ${this.projectType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rolesDefinitionPath = resolve(SERVERLESS_ROLES_ROOT_PATH, this.projectType, 'roles.yml');
|
||||||
|
}
|
||||||
|
|
||||||
|
getSupportedRoleDescriptors(): any {
|
||||||
|
return readRolesDescriptorsFromResource(this.rolesDefinitionPath);
|
||||||
|
}
|
||||||
|
getDefaultRole(): string {
|
||||||
|
return getDefaultServerlessRole(this.projectType);
|
||||||
|
}
|
||||||
|
getRolesDefinitionPath(): string {
|
||||||
|
return this.rolesDefinitionPath;
|
||||||
|
}
|
||||||
|
getCommonRequestHeader() {
|
||||||
|
return COMMON_REQUEST_HEADERS;
|
||||||
|
}
|
||||||
|
getInternalRequestHeader() {
|
||||||
|
return getServerlessInternalRequestHeaders();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"kibana":[
|
||||||
|
{
|
||||||
|
"base":[
|
||||||
|
"all"
|
||||||
|
],
|
||||||
|
"feature":{
|
||||||
|
|
||||||
|
},
|
||||||
|
"spaces":[
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"elasticsearch":{
|
||||||
|
"cluster":[
|
||||||
|
"all"
|
||||||
|
],
|
||||||
|
"indices":[
|
||||||
|
{
|
||||||
|
"names":[
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"privileges":[
|
||||||
|
"all"
|
||||||
|
],
|
||||||
|
"allow_restricted_indices":false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"names":[
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"privileges":[
|
||||||
|
"monitor",
|
||||||
|
"read",
|
||||||
|
"read_cross_cluster",
|
||||||
|
"view_index_metadata"
|
||||||
|
],
|
||||||
|
"allow_restricted_indices":true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"run_as":[
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { readRolesDescriptorsFromResource, STATEFUL_ROLES_ROOT_PATH } from '@kbn/es';
|
||||||
|
import { REPO_ROOT } from '@kbn/repo-info';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { AuthProvider } from '../get_auth_provider';
|
||||||
|
import {
|
||||||
|
getStatefulInternalRequestHeaders,
|
||||||
|
COMMON_REQUEST_HEADERS,
|
||||||
|
} from '../default_request_headers';
|
||||||
|
|
||||||
|
export class StatefulAuthProvider implements AuthProvider {
|
||||||
|
private readonly rolesDefinitionPath = resolve(REPO_ROOT, STATEFUL_ROLES_ROOT_PATH, 'roles.yml');
|
||||||
|
getSupportedRoleDescriptors(): any {
|
||||||
|
return readRolesDescriptorsFromResource(this.rolesDefinitionPath);
|
||||||
|
}
|
||||||
|
getDefaultRole() {
|
||||||
|
return 'editor';
|
||||||
|
}
|
||||||
|
getRolesDefinitionPath() {
|
||||||
|
return this.rolesDefinitionPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCommonRequestHeader() {
|
||||||
|
return COMMON_REQUEST_HEADERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInternalRequestHeader() {
|
||||||
|
return getStatefulInternalRequestHeaders();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Config, createEsClientForFtrConfig } from '@kbn/test';
|
||||||
|
import { ToolingLog } from '@kbn/tooling-log';
|
||||||
|
import { KibanaServer } from '../../..';
|
||||||
|
|
||||||
|
export interface CreateRoleProps {
|
||||||
|
roleName: string;
|
||||||
|
roleMapping: string[];
|
||||||
|
kibanaServer: KibanaServer;
|
||||||
|
log: ToolingLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateRoleMappingProps {
|
||||||
|
name: string;
|
||||||
|
roles: string[];
|
||||||
|
config: Config;
|
||||||
|
log: ToolingLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createRole(props: CreateRoleProps) {
|
||||||
|
const { roleName, roleMapping, kibanaServer, log } = props;
|
||||||
|
log.debug(`Adding a role: ${roleName}`);
|
||||||
|
const { status, statusText } = await kibanaServer.request({
|
||||||
|
path: `/api/security/role/${roleName}`,
|
||||||
|
method: 'PUT',
|
||||||
|
body: roleMapping,
|
||||||
|
retries: 0,
|
||||||
|
});
|
||||||
|
if (status !== 204) {
|
||||||
|
throw new Error(`Expected status code of 204, received ${status} ${statusText}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createRoleMapping(props: CreateRoleMappingProps) {
|
||||||
|
const { name, roles, config, log } = props;
|
||||||
|
log.debug(`Creating a role mapping: {realm.name: ${name}, roles: ${roles}}`);
|
||||||
|
const esClient = createEsClientForFtrConfig(config);
|
||||||
|
await esClient.security.putRoleMapping({
|
||||||
|
name,
|
||||||
|
roles,
|
||||||
|
enabled: true,
|
||||||
|
// @ts-ignore
|
||||||
|
rules: { field: { 'realm.name': name } },
|
||||||
|
});
|
||||||
|
}
|
|
@ -14,7 +14,11 @@
|
||||||
"@kbn/core-saved-objects-server",
|
"@kbn/core-saved-objects-server",
|
||||||
"@kbn/tooling-log",
|
"@kbn/tooling-log",
|
||||||
"@kbn/es-archiver",
|
"@kbn/es-archiver",
|
||||||
"@kbn/test"
|
"@kbn/test",
|
||||||
|
"@kbn/expect",
|
||||||
|
"@kbn/repo-info",
|
||||||
|
"@kbn/es",
|
||||||
|
"@kbn/mock-idp-utils"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"target/**/*",
|
"target/**/*",
|
||||||
|
|
|
@ -10,12 +10,13 @@ import * as fs from 'fs';
|
||||||
import { Role, User } from './types';
|
import { Role, User } from './types';
|
||||||
|
|
||||||
export const readCloudUsersFromFile = (filePath: string): Array<[Role, User]> => {
|
export const readCloudUsersFromFile = (filePath: string): Array<[Role, User]> => {
|
||||||
|
const defaultMessage = `Cannot read roles and email/password from ${filePath}`;
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
throw new Error(`Please define user roles with email/password in ${filePath}`);
|
throw new Error(`${defaultMessage}: file does not exist`);
|
||||||
}
|
}
|
||||||
const data = fs.readFileSync(filePath, 'utf8');
|
const data = fs.readFileSync(filePath, 'utf8');
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
throw new Error(`'${filePath}' is empty: no roles are defined`);
|
throw new Error(`${defaultMessage}: file is empty`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(JSON.parse(data)) as Array<[Role, User]>;
|
return Object.entries(JSON.parse(data)) as Array<[Role, User]>;
|
||||||
|
|
|
@ -6,10 +6,7 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es';
|
|
||||||
import { REPO_ROOT } from '@kbn/repo-info';
|
|
||||||
import { ToolingLog } from '@kbn/tooling-log';
|
import { ToolingLog } from '@kbn/tooling-log';
|
||||||
import { resolve } from 'path';
|
|
||||||
import Url from 'url';
|
import Url from 'url';
|
||||||
import { KbnClient } from '../kbn_client';
|
import { KbnClient } from '../kbn_client';
|
||||||
import { readCloudUsersFromFile } from './helper';
|
import { readCloudUsersFromFile } from './helper';
|
||||||
|
@ -32,31 +29,32 @@ export interface HostOptions {
|
||||||
export interface SamlSessionManagerOptions {
|
export interface SamlSessionManagerOptions {
|
||||||
hostOptions: HostOptions;
|
hostOptions: HostOptions;
|
||||||
isCloud: boolean;
|
isCloud: boolean;
|
||||||
supportedRoles?: string[];
|
supportedRoles?: SupportedRoles;
|
||||||
|
cloudUsersFilePath: string;
|
||||||
log: ToolingLog;
|
log: ToolingLog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SupportedRoles {
|
||||||
|
sourcePath: string;
|
||||||
|
roles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages cookies associated with user roles
|
* Manages cookies associated with user roles
|
||||||
*/
|
*/
|
||||||
export class SamlSessionManager {
|
export class SamlSessionManager {
|
||||||
private readonly DEFAULT_ROLES_FILE_NAME: string = 'role_users.json';
|
|
||||||
private readonly isCloud: boolean;
|
private readonly isCloud: boolean;
|
||||||
private readonly kbnHost: string;
|
private readonly kbnHost: string;
|
||||||
private readonly kbnClient: KbnClient;
|
private readonly kbnClient: KbnClient;
|
||||||
private readonly log: ToolingLog;
|
private readonly log: ToolingLog;
|
||||||
private readonly roleToUserMap: Map<Role, User>;
|
private readonly roleToUserMap: Map<Role, User>;
|
||||||
private readonly sessionCache: Map<Role, Session>;
|
private readonly sessionCache: Map<Role, Session>;
|
||||||
private readonly supportedRoles: string[];
|
private readonly supportedRoles?: SupportedRoles;
|
||||||
private readonly userRoleFilePath: string;
|
private readonly cloudUsersFilePath: string;
|
||||||
|
|
||||||
constructor(options: SamlSessionManagerOptions, rolesFilename?: string) {
|
constructor(options: SamlSessionManagerOptions) {
|
||||||
this.isCloud = options.isCloud;
|
this.isCloud = options.isCloud;
|
||||||
this.log = options.log;
|
this.log = options.log;
|
||||||
// if the rolesFilename is provided, respect it. Otherwise use DEFAULT_ROLES_FILE_NAME.
|
|
||||||
const rolesFile = rolesFilename ? rolesFilename : this.DEFAULT_ROLES_FILE_NAME;
|
|
||||||
this.log.info(`Using the file ${rolesFile} for the role users`);
|
|
||||||
this.userRoleFilePath = resolve(REPO_ROOT, '.ftr', rolesFile);
|
|
||||||
const hostOptionsWithoutAuth = {
|
const hostOptionsWithoutAuth = {
|
||||||
protocol: options.hostOptions.protocol,
|
protocol: options.hostOptions.protocol,
|
||||||
hostname: options.hostOptions.hostname,
|
hostname: options.hostOptions.hostname,
|
||||||
|
@ -70,9 +68,10 @@ export class SamlSessionManager {
|
||||||
auth: `${options.hostOptions.username}:${options.hostOptions.password}`,
|
auth: `${options.hostOptions.username}:${options.hostOptions.password}`,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
this.cloudUsersFilePath = options.cloudUsersFilePath;
|
||||||
this.sessionCache = new Map<Role, Session>();
|
this.sessionCache = new Map<Role, Session>();
|
||||||
this.roleToUserMap = new Map<Role, User>();
|
this.roleToUserMap = new Map<Role, User>();
|
||||||
this.supportedRoles = options.supportedRoles ?? [];
|
this.supportedRoles = options.supportedRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,7 +80,8 @@ export class SamlSessionManager {
|
||||||
*/
|
*/
|
||||||
private getCloudUsers = () => {
|
private getCloudUsers = () => {
|
||||||
if (this.roleToUserMap.size === 0) {
|
if (this.roleToUserMap.size === 0) {
|
||||||
const data = readCloudUsersFromFile(this.userRoleFilePath);
|
this.log.info(`Reading cloud user credentials from ${this.cloudUsersFilePath}`);
|
||||||
|
const data = readCloudUsersFromFile(this.cloudUsersFilePath);
|
||||||
for (const [roleName, user] of data) {
|
for (const [roleName, user] of data) {
|
||||||
this.roleToUserMap.set(roleName, user);
|
this.roleToUserMap.set(roleName, user);
|
||||||
}
|
}
|
||||||
|
@ -104,11 +104,11 @@ export class SamlSessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate role before creating SAML session
|
// Validate role before creating SAML session
|
||||||
if (this.supportedRoles.length && !this.supportedRoles.includes(role)) {
|
if (this.supportedRoles && !this.supportedRoles.roles.includes(role)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Role '${role}' is not defined in the supported list: ${this.supportedRoles.join(
|
`Role '${role}' is not in the supported list: ${this.supportedRoles.roles.join(
|
||||||
', '
|
', '
|
||||||
)}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing`
|
)}. Add role descriptor in ${this.supportedRoles.sourcePath} to enable it for testing`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,17 +9,23 @@
|
||||||
import { ToolingLog } from '@kbn/tooling-log';
|
import { ToolingLog } from '@kbn/tooling-log';
|
||||||
import { Cookie } from 'tough-cookie';
|
import { Cookie } from 'tough-cookie';
|
||||||
import { Session } from './saml_auth';
|
import { Session } from './saml_auth';
|
||||||
import { SamlSessionManager } from './session_manager';
|
import { SamlSessionManager, SupportedRoles } from './session_manager';
|
||||||
import * as samlAuth from './saml_auth';
|
import * as samlAuth from './saml_auth';
|
||||||
import * as helper from './helper';
|
import * as helper from './helper';
|
||||||
import { Role, User, UserProfile } from './types';
|
import { Role, User, UserProfile } from './types';
|
||||||
import { SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es';
|
import { SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { REPO_ROOT } from '@kbn/repo-info';
|
||||||
|
|
||||||
const log = new ToolingLog();
|
const log = new ToolingLog();
|
||||||
|
|
||||||
const supportedRoles = ['admin', 'editor', 'viewer'];
|
const supportedRoles: SupportedRoles = {
|
||||||
|
roles: ['admin', 'editor', 'viewer'],
|
||||||
|
sourcePath: 'test/roles.yml',
|
||||||
|
};
|
||||||
const roleViewer = 'viewer';
|
const roleViewer = 'viewer';
|
||||||
const roleEditor = 'editor';
|
const roleEditor = 'editor';
|
||||||
|
const cloudUsersFilePath = resolve(REPO_ROOT, SERVERLESS_ROLES_ROOT_PATH, 'role_users.json');
|
||||||
|
|
||||||
const createLocalSAMLSessionMock = jest.spyOn(samlAuth, 'createLocalSAMLSession');
|
const createLocalSAMLSessionMock = jest.spyOn(samlAuth, 'createLocalSAMLSession');
|
||||||
const createCloudSAMLSessionMock = jest.spyOn(samlAuth, 'createCloudSAMLSession');
|
const createCloudSAMLSessionMock = jest.spyOn(samlAuth, 'createCloudSAMLSession');
|
||||||
|
@ -58,7 +64,7 @@ describe('SamlSessionManager', () => {
|
||||||
hostOptions,
|
hostOptions,
|
||||||
isCloud,
|
isCloud,
|
||||||
log,
|
log,
|
||||||
supportedRoles,
|
cloudUsersFilePath,
|
||||||
};
|
};
|
||||||
const testEmail = 'testuser@elastic.com';
|
const testEmail = 'testuser@elastic.com';
|
||||||
const testFullname = 'Test User';
|
const testFullname = 'Test User';
|
||||||
|
@ -67,7 +73,7 @@ describe('SamlSessionManager', () => {
|
||||||
)!;
|
)!;
|
||||||
|
|
||||||
test('should create an instance of SamlSessionManager', () => {
|
test('should create an instance of SamlSessionManager', () => {
|
||||||
const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud });
|
const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions);
|
||||||
expect(samlSessionManager).toBeInstanceOf(SamlSessionManager);
|
expect(samlSessionManager).toBeInstanceOf(SamlSessionManager);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -118,10 +124,13 @@ describe('SamlSessionManager', () => {
|
||||||
|
|
||||||
test(`throws error when role is not in 'supportedRoles'`, async () => {
|
test(`throws error when role is not in 'supportedRoles'`, async () => {
|
||||||
const nonExistingRole = 'tester';
|
const nonExistingRole = 'tester';
|
||||||
const expectedErrorMessage = `Role '${nonExistingRole}' is not defined in the supported list: ${supportedRoles.join(
|
const expectedErrorMessage = `Role '${nonExistingRole}' is not in the supported list: ${supportedRoles.roles.join(
|
||||||
', '
|
', '
|
||||||
)}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing`;
|
)}. Add role descriptor in ${supportedRoles.sourcePath} to enable it for testing`;
|
||||||
const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions);
|
const samlSessionManager = new SamlSessionManager({
|
||||||
|
...samlSessionManagerOptions,
|
||||||
|
supportedRoles,
|
||||||
|
});
|
||||||
await expect(
|
await expect(
|
||||||
samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole)
|
samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole)
|
||||||
).rejects.toThrow(expectedErrorMessage);
|
).rejects.toThrow(expectedErrorMessage);
|
||||||
|
@ -145,11 +154,7 @@ describe('SamlSessionManager', () => {
|
||||||
elastic_cloud_user: false,
|
elastic_cloud_user: false,
|
||||||
};
|
};
|
||||||
getSecurityProfileMock.mockResolvedValueOnce(testData);
|
getSecurityProfileMock.mockResolvedValueOnce(testData);
|
||||||
const samlSessionManager = new SamlSessionManager({
|
const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions);
|
||||||
hostOptions,
|
|
||||||
log,
|
|
||||||
isCloud,
|
|
||||||
});
|
|
||||||
await samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole);
|
await samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole);
|
||||||
await samlSessionManager.getApiCredentialsForRole(nonExistingRole);
|
await samlSessionManager.getApiCredentialsForRole(nonExistingRole);
|
||||||
await samlSessionManager.getUserData(nonExistingRole);
|
await samlSessionManager.getUserData(nonExistingRole);
|
||||||
|
@ -171,7 +176,7 @@ describe('SamlSessionManager', () => {
|
||||||
hostOptions,
|
hostOptions,
|
||||||
isCloud,
|
isCloud,
|
||||||
log,
|
log,
|
||||||
supportedRoles,
|
cloudUsersFilePath,
|
||||||
};
|
};
|
||||||
const cloudCookieInstance = Cookie.parse(
|
const cloudCookieInstance = Cookie.parse(
|
||||||
'sid=cloud_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT'
|
'sid=cloud_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT'
|
||||||
|
@ -195,11 +200,7 @@ describe('SamlSessionManager', () => {
|
||||||
|
|
||||||
test('should throw error if TEST_CLOUD_HOST_NAME is not set', async () => {
|
test('should throw error if TEST_CLOUD_HOST_NAME is not set', async () => {
|
||||||
isValidHostnameMock.mockReturnValueOnce(false);
|
isValidHostnameMock.mockReturnValueOnce(false);
|
||||||
const samlSessionManager = new SamlSessionManager({
|
const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions);
|
||||||
hostOptions,
|
|
||||||
log,
|
|
||||||
isCloud,
|
|
||||||
});
|
|
||||||
await expect(
|
await expect(
|
||||||
samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(roleViewer)
|
samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(roleViewer)
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
|
@ -220,11 +221,7 @@ describe('SamlSessionManager', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should create an instance of SamlSessionManager', () => {
|
test('should create an instance of SamlSessionManager', () => {
|
||||||
const samlSessionManager = new SamlSessionManager({
|
const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions);
|
||||||
hostOptions,
|
|
||||||
log,
|
|
||||||
isCloud,
|
|
||||||
});
|
|
||||||
expect(samlSessionManager).toBeInstanceOf(SamlSessionManager);
|
expect(samlSessionManager).toBeInstanceOf(SamlSessionManager);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -276,10 +273,13 @@ describe('SamlSessionManager', () => {
|
||||||
|
|
||||||
test(`throws error for non-existing role when 'supportedRoles' is defined`, async () => {
|
test(`throws error for non-existing role when 'supportedRoles' is defined`, async () => {
|
||||||
const nonExistingRole = 'tester';
|
const nonExistingRole = 'tester';
|
||||||
const expectedErrorMessage = `Role '${nonExistingRole}' is not defined in the supported list: ${supportedRoles.join(
|
const expectedErrorMessage = `Role '${nonExistingRole}' is not in the supported list: ${supportedRoles.roles.join(
|
||||||
', '
|
', '
|
||||||
)}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing`;
|
)}. Add role descriptor in ${supportedRoles.sourcePath} to enable it for testing`;
|
||||||
const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions);
|
const samlSessionManager = new SamlSessionManager({
|
||||||
|
...samlSessionManagerOptions,
|
||||||
|
supportedRoles,
|
||||||
|
});
|
||||||
await expect(
|
await expect(
|
||||||
samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole)
|
samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole)
|
||||||
).rejects.toThrow(expectedErrorMessage);
|
).rejects.toThrow(expectedErrorMessage);
|
||||||
|
@ -294,11 +294,7 @@ describe('SamlSessionManager', () => {
|
||||||
|
|
||||||
test(`throws error for non-existing role when 'supportedRoles' is not defined`, async () => {
|
test(`throws error for non-existing role when 'supportedRoles' is not defined`, async () => {
|
||||||
const nonExistingRole = 'tester';
|
const nonExistingRole = 'tester';
|
||||||
const samlSessionManager = new SamlSessionManager({
|
const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions);
|
||||||
hostOptions,
|
|
||||||
log,
|
|
||||||
isCloud,
|
|
||||||
});
|
|
||||||
await expect(
|
await expect(
|
||||||
samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole)
|
samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole)
|
||||||
).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`);
|
).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`);
|
||||||
|
|
|
@ -125,7 +125,8 @@ export async function runCheckFtrConfigsCli() {
|
||||||
|
|
||||||
const invalid = possibleConfigs.filter((path) => !allFtrConfigs.includes(path));
|
const invalid = possibleConfigs.filter((path) => !allFtrConfigs.includes(path));
|
||||||
if (invalid.length) {
|
if (invalid.length) {
|
||||||
const invalidList = invalid.map((path) => Path.relative(REPO_ROOT, path)).join('\n - ');
|
const invalidList =
|
||||||
|
' - ' + invalid.map((path) => Path.relative(REPO_ROOT, path)).join('\n - ');
|
||||||
log.error(
|
log.error(
|
||||||
`The following files look like FTR configs which are not listed in one of manifest files:\n${invalidList}\n
|
`The following files look like FTR configs which are not listed in one of manifest files:\n${invalidList}\n
|
||||||
Make sure to add your new FTR config to the correct manifest file.\n
|
Make sure to add your new FTR config to the correct manifest file.\n
|
||||||
|
|
|
@ -13,6 +13,5 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
||||||
loadTestFile(require.resolve('./proxy_route'));
|
loadTestFile(require.resolve('./proxy_route'));
|
||||||
loadTestFile(require.resolve('./autocomplete_entities'));
|
loadTestFile(require.resolve('./autocomplete_entities'));
|
||||||
loadTestFile(require.resolve('./es_config'));
|
loadTestFile(require.resolve('./es_config'));
|
||||||
loadTestFile(require.resolve('./spec_definitions'));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import expect from '@kbn/expect';
|
|
||||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
|
||||||
|
|
||||||
export default function ({ getService }: FtrProviderContext) {
|
|
||||||
const supertest = getService('supertest');
|
|
||||||
|
|
||||||
describe('GET /api/console/api_server', () => {
|
|
||||||
it('returns autocomplete definitions', async () => {
|
|
||||||
const { body } = await supertest
|
|
||||||
.get('/api/console/api_server')
|
|
||||||
.set('kbn-xsrf', 'true')
|
|
||||||
.expect(200);
|
|
||||||
expect(body.es).to.be.ok();
|
|
||||||
const {
|
|
||||||
es: { name, globals, endpoints },
|
|
||||||
} = body;
|
|
||||||
expect(name).to.be.ok();
|
|
||||||
expect(Object.keys(globals).length).to.be.above(0);
|
|
||||||
expect(Object.keys(endpoints).length).to.be.above(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import expect from '@kbn/expect';
|
|
||||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
|
||||||
|
|
||||||
export default function ({ getService }: FtrProviderContext) {
|
|
||||||
const supertest = getService('supertest');
|
|
||||||
|
|
||||||
const compressionSuite = (url: string) => {
|
|
||||||
it(`uses compression when there isn't a referer`, async () => {
|
|
||||||
await supertest
|
|
||||||
.get(url)
|
|
||||||
.set('accept-encoding', 'gzip')
|
|
||||||
.then((response) => {
|
|
||||||
expect(response.header).to.have.property('content-encoding', 'gzip');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`uses compression when there is a whitelisted referer`, async () => {
|
|
||||||
await supertest
|
|
||||||
.get(url)
|
|
||||||
.set('accept-encoding', 'gzip')
|
|
||||||
.set('referer', 'https://some-host.com')
|
|
||||||
.then((response) => {
|
|
||||||
expect(response.header).to.have.property('content-encoding', 'gzip');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`doesn't use compression when there is a non-whitelisted referer`, async () => {
|
|
||||||
await supertest
|
|
||||||
.get(url)
|
|
||||||
.set('accept-encoding', 'gzip')
|
|
||||||
.set('referer', 'https://other.some-host.com')
|
|
||||||
.then((response) => {
|
|
||||||
expect(response.header).not.to.have.property('content-encoding');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`supports brotli compression`, async () => {
|
|
||||||
await supertest
|
|
||||||
.get(url)
|
|
||||||
.set('accept-encoding', 'br')
|
|
||||||
.then((response) => {
|
|
||||||
expect(response.header).to.have.property('content-encoding', 'br');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('compression', () => {
|
|
||||||
describe('against an application page', () => {
|
|
||||||
compressionSuite('/app/kibana');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -10,7 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
||||||
|
|
||||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||||
describe('core', () => {
|
describe('core', () => {
|
||||||
loadTestFile(require.resolve('./compression'));
|
|
||||||
loadTestFile(require.resolve('./translations'));
|
loadTestFile(require.resolve('./translations'));
|
||||||
loadTestFile(require.resolve('./capabilities'));
|
loadTestFile(require.resolve('./capabilities'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,8 +16,15 @@ import { IndexPatternsService } from './index_patterns';
|
||||||
import { BsearchService } from './bsearch';
|
import { BsearchService } from './bsearch';
|
||||||
import { ConsoleProvider } from './console';
|
import { ConsoleProvider } from './console';
|
||||||
|
|
||||||
|
// pick only services that work for any FTR config, e.g. 'samlAuth' requires SAML setup in config file
|
||||||
|
const { es, esArchiver, kibanaServer, retry, supertestWithoutAuth } = commonFunctionalServices;
|
||||||
|
|
||||||
export const services = {
|
export const services = {
|
||||||
...commonFunctionalServices,
|
es,
|
||||||
|
esArchiver,
|
||||||
|
kibanaServer,
|
||||||
|
retry,
|
||||||
|
supertestWithoutAuth,
|
||||||
deployment: DeploymentService,
|
deployment: DeploymentService,
|
||||||
randomness: RandomnessService,
|
randomness: RandomnessService,
|
||||||
security: SecurityServiceProvider,
|
security: SecurityServiceProvider,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { ToolingLog } from '@kbn/tooling-log';
|
||||||
import type { HostOptions } from '@kbn/test';
|
import type { HostOptions } from '@kbn/test';
|
||||||
import { SamlSessionManager } from '@kbn/test';
|
import { SamlSessionManager } from '@kbn/test';
|
||||||
import type { SecurityRoleName } from '../../../../common/test';
|
import type { SecurityRoleName } from '../../../../common/test';
|
||||||
|
import { resolveCloudUsersFilePath } from '../../../../scripts/endpoint/common/roles_users/serverless';
|
||||||
|
|
||||||
export const samlAuthentication = async (
|
export const samlAuthentication = async (
|
||||||
on: Cypress.PluginEvents,
|
on: Cypress.PluginEvents,
|
||||||
|
@ -34,15 +35,15 @@ export const samlAuthentication = async (
|
||||||
role: string | SecurityRoleName
|
role: string | SecurityRoleName
|
||||||
): Promise<{ cookie: string; username: string; password: string }> => {
|
): Promise<{ cookie: string; username: string; password: string }> => {
|
||||||
// If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles.
|
// If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles.
|
||||||
const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined;
|
const rolesFilename = config.env.PROXY_ORG
|
||||||
const sessionManager = new SamlSessionManager(
|
? `${config.env.PROXY_ORG}.json`
|
||||||
{
|
: 'role_users.json';
|
||||||
hostOptions,
|
const sessionManager = new SamlSessionManager({
|
||||||
log,
|
hostOptions,
|
||||||
isCloud: config.env.CLOUD_SERVERLESS,
|
log,
|
||||||
},
|
isCloud: config.env.CLOUD_SERVERLESS,
|
||||||
rolesFilename
|
cloudUsersFilePath: resolveCloudUsersFilePath(rolesFilename),
|
||||||
);
|
});
|
||||||
return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role).then((cookie) => {
|
return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role).then((cookie) => {
|
||||||
return {
|
return {
|
||||||
cookie,
|
cookie,
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import { resolve, join } from 'path';
|
import { resolve, join } from 'path';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
|
import { REPO_ROOT } from '@kbn/repo-info';
|
||||||
|
|
||||||
const ES_RESOURCES_DIR = resolve(__dirname, 'es_serverless_resources');
|
const ES_RESOURCES_DIR = resolve(__dirname, 'es_serverless_resources');
|
||||||
|
|
||||||
|
@ -16,6 +17,8 @@ export const ES_RESOURCES = Object.freeze({
|
||||||
users_roles: join(ES_RESOURCES_DIR, 'users_roles'),
|
users_roles: join(ES_RESOURCES_DIR, 'users_roles'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const resolveCloudUsersFilePath = (filename: string) => resolve(REPO_ROOT, '.ftr', filename);
|
||||||
|
|
||||||
export const ES_LOADED_USERS = readFileSync(ES_RESOURCES.users)
|
export const ES_LOADED_USERS = readFileSync(ES_RESOURCES.users)
|
||||||
.toString()
|
.toString()
|
||||||
.split(/\n/)
|
.split(/\n/)
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { FtrConfigProviderContext } from '@kbn/test';
|
|
||||||
|
|
||||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|
||||||
const baseIntegrationTestsConfig = await readConfigFile(require.resolve('../../config.ts'));
|
|
||||||
|
|
||||||
return {
|
|
||||||
...baseIntegrationTestsConfig.getAll(),
|
|
||||||
testFiles: [require.resolve('.')],
|
|
||||||
};
|
|
||||||
}
|
|
183
x-pack/test/api_integration/deployment_agnostic/README.md
Normal file
183
x-pack/test/api_integration/deployment_agnostic/README.md
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
# Deployment-Agnostic Tests Guidelines
|
||||||
|
|
||||||
|
## Definition
|
||||||
|
A deployment-agnostic API integration test is a test suite that fulfills the following criteria:
|
||||||
|
|
||||||
|
**Functionality**: It tests Kibana APIs that are logically identical in both stateful and serverless environments for the same roles.
|
||||||
|
|
||||||
|
**Design**: The test design is clean and does not require additional logic to execute in either stateful or serverless environments.
|
||||||
|
|
||||||
|
## Tests Design Requirements
|
||||||
|
A deployment-agnostic test is contained within a single test file and always utilizes the [DeploymentAgnosticFtrProviderContext](https://github.com/elastic/kibana/blob/main/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts) to load compatible FTR services. A compatible FTR service must support:
|
||||||
|
|
||||||
|
- **Serverless**: Both local environments and MKI (Managed Kubernetes Infrastructure).
|
||||||
|
- **Stateful**: Both local environments and Cloud deployments.
|
||||||
|
|
||||||
|
To achieve this, services cannot use `supertest`, which employs an operator user for serverless and a system index superuser for stateful setups. Instead, services should use a combination of `supertestWithoutAuth` and `samlAuth` to generate an API key for user roles and make API calls. For example, see the [data_view_api.ts](https://github.com/elastic/kibana/blob/main/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts) service.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
Most existing stateful tests use basic authentication for API testing. In contrast, serverless tests use SAML authentication with project-specific role mapping.
|
||||||
|
|
||||||
|
Since both Elastic Cloud (ESS) and Serverless rely on SAML authentication by default, and stateful deployments also support SAML, *deployment-agnostic tests configure Elasticsearch and Kibana with SAML authentication to use the same authentication approach in all cases*. For roles, stateful deployments define 'viewer', 'editor', and 'admin' roles with serverless-alike permissions.
|
||||||
|
|
||||||
|
### When to Create Separate Tests
|
||||||
|
While the deployment-agnostic testing approach is beneficial, it should not compromise the quality and simplicity of the tests. Here are some scenarios where separate test files are recommended:
|
||||||
|
|
||||||
|
- **Role-Specific Logic**: If API access or logic depends on roles that differ across deployments.
|
||||||
|
- **Environment Constraints**: If a test can only run locally and not on MKI or Cloud deployments.
|
||||||
|
- **Complex Logic**: If the test logic requires splitting across multiple locations.
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
We recommend following this structure to simplify maintenance and allow other teams to reuse code (e.g., FTR services) created by different teams:
|
||||||
|
|
||||||
|
```
|
||||||
|
x-pack/test/<my_own_api_integration_folder>
|
||||||
|
├─ deployment_agnostic
|
||||||
|
│ ├─ apis
|
||||||
|
│ │ ├─ <api_1>
|
||||||
|
│ │ │ ├─ <test_1_1>
|
||||||
|
│ │ │ ├─ <test_1_2>
|
||||||
|
│ │ ├─ <api_2>
|
||||||
|
│ │ │ ├─ <test_2_1>
|
||||||
|
│ │ │ ├─ <test_2_2>
|
||||||
|
│ ├─ services
|
||||||
|
│ │ ├─ index.ts // only services from 'x-pack/test/api_integration/deployment_agnostic/services'
|
||||||
|
│ │ ├─ <deployment_agnostic_service_1>.ts
|
||||||
|
│ │ ├─ <deployment_agnostic_service_2>.ts
|
||||||
|
│ ├─ ftr_provider_context.d.ts // with types of services from './services'
|
||||||
|
├─ stateful.index.ts
|
||||||
|
├─ stateful.config.ts
|
||||||
|
├─ <serverless_project>.index.ts // e.g., oblt.index.ts
|
||||||
|
├─ <serverless_project>.serverless.config.ts // e.g., oblt.serverless.config.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step-by-Step Guide
|
||||||
|
1. Define Deployment-Agnostic Services
|
||||||
|
|
||||||
|
Under `x-pack/test/<my_own_api_integration_folder>/deployment_agnostic/services`, create `index.ts` and load default services like `samlAuth` and `superuserWithoutAuth`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
|
||||||
|
import { services as deploymentAgnosticServices } from './../../api_integration/deployment_agnostic/services';
|
||||||
|
|
||||||
|
export type {
|
||||||
|
InternalRequestHeader,
|
||||||
|
RoleCredentials,
|
||||||
|
SupertestWithoutAuthProviderType,
|
||||||
|
} from '@kbn/ftr-common-functional-services';
|
||||||
|
|
||||||
|
export const services = {
|
||||||
|
...deploymentAgnosticServices,
|
||||||
|
// create a new deployment-agnostic service and load here
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
We suggest adding new services to `x-pack/test/api_integration/deployment_agnostic/services` so other teams can benefit from them.
|
||||||
|
|
||||||
|
2. Create `DeploymentAgnosticFtrProviderContext` with Services Defined in Step 2
|
||||||
|
|
||||||
|
Create `ftr_provider_context.d.ts` and export `DeploymentAgnosticFtrProviderContext`:
|
||||||
|
```ts
|
||||||
|
import { GenericFtrProviderContext } from '@kbn/test';
|
||||||
|
import { services } from './services';
|
||||||
|
|
||||||
|
export type DeploymentAgnosticFtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Add Tests
|
||||||
|
|
||||||
|
Add test files to `x-pack/test/<my_own_api_integration_folder>/deployment_agnostic/apis/<my_api>`:
|
||||||
|
|
||||||
|
test example
|
||||||
|
```ts
|
||||||
|
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
const samlAuth = getService('samlAuth');
|
||||||
|
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||||
|
let roleAuthc: RoleCredentials;
|
||||||
|
let internalHeaders: InternalRequestHeader;
|
||||||
|
|
||||||
|
describe('compression', () => {
|
||||||
|
before(async () => {
|
||||||
|
roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin');
|
||||||
|
internalHeaders = samlAuth.getInternalRequestHeader();
|
||||||
|
});
|
||||||
|
after(async () => {
|
||||||
|
await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc);
|
||||||
|
});
|
||||||
|
describe('against an application page', () => {
|
||||||
|
it(`uses compression when there isn't a referer`, async () => {
|
||||||
|
const response = await supertestWithoutAuth
|
||||||
|
.get('/app/kibana')
|
||||||
|
.set('accept-encoding', 'gzip')
|
||||||
|
.set(internalHeaders)
|
||||||
|
.set(roleAuthc.apiKeyHeader);
|
||||||
|
expect(response.header).to.have.property('content-encoding', 'gzip');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Load all test files in `index.ts` under the same folder.
|
||||||
|
|
||||||
|
4. Add Tests Entry File and FTR Config File for **Stateful** Deployment
|
||||||
|
|
||||||
|
Create `stateful.index.ts` tests entry file and load tests:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
describe('apis', () => {
|
||||||
|
loadTestFile(require.resolve('./apis/<my_api>'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `stateful.config.ts` and link tests entry file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createStatefulTestConfig } from './../../api_integration/deployment_agnostic/default_configs/stateful.config.base';
|
||||||
|
|
||||||
|
export default createStatefulTestConfig({
|
||||||
|
testFiles: [require.resolve('./stateful.index.ts')],
|
||||||
|
junit: {
|
||||||
|
reportName: 'Stateful - Deployment-agnostic API Integration Tests',
|
||||||
|
},
|
||||||
|
// extra arguments
|
||||||
|
esServerArgs: [],
|
||||||
|
kbnServerArgs: [],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
5. Add Tests Entry File and FTR Config File for Specific **Serverless** Project
|
||||||
|
|
||||||
|
Example for Observability project:
|
||||||
|
|
||||||
|
oblt.index.ts
|
||||||
|
```ts
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
describe('Serverless Observability - Deployment-agnostic api integration tests', () => {
|
||||||
|
loadTestFile(require.resolve('./apis/<my_api>'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
oblt.serverless.config.ts
|
||||||
|
```ts
|
||||||
|
import { createServerlessTestConfig } from './../../api_integration/deployment_agnostic/default_configs/serverless.config.base';
|
||||||
|
|
||||||
|
export default createServerlessTestConfig({
|
||||||
|
serverlessProject: 'oblt',
|
||||||
|
testFiles: [require.resolve('./oblt.index.ts')],
|
||||||
|
junit: {
|
||||||
|
reportName: 'Serverless Observability - Deployment-agnostic API Integration Tests',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
ES and Kibana project-specific arguments are defined and loaded from `serverless.config.base`. These arguments are copied from the Elasticsearch and Kibana controller repositories.
|
||||||
|
|
||||||
|
Note: The FTR (Functional Test Runner) does not have the capability to provision custom ES/Kibana server arguments into the serverless project. Any custom arguments listed explicitly in this config file will apply **only to a local environment**.
|
||||||
|
|
||||||
|
6. Add FTR Configs Path to FTR Manifest Files Located in `.buildkite/`
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
describe('console', () => {
|
||||||
|
loadTestFile(require.resolve('./spec_definitions'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import expect from '@kbn/expect';
|
||||||
|
import { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services';
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
const samlAuth = getService('samlAuth');
|
||||||
|
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||||
|
let roleAuthc: RoleCredentials;
|
||||||
|
let internalHeaders: InternalRequestHeader;
|
||||||
|
|
||||||
|
describe('GET /api/console/api_server', () => {
|
||||||
|
before(async () => {
|
||||||
|
roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin');
|
||||||
|
internalHeaders = samlAuth.getInternalRequestHeader();
|
||||||
|
});
|
||||||
|
after(async () => {
|
||||||
|
await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc);
|
||||||
|
});
|
||||||
|
it('returns autocomplete definitions', async () => {
|
||||||
|
const { body } = await supertestWithoutAuth
|
||||||
|
.get('/api/console/api_server')
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
|
.set(internalHeaders)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.expect(200);
|
||||||
|
expect(body.es).to.be.ok();
|
||||||
|
const {
|
||||||
|
es: { name, globals, endpoints },
|
||||||
|
} = body;
|
||||||
|
expect(name).to.be.ok();
|
||||||
|
expect(Object.keys(globals).length).to.be.above(0);
|
||||||
|
expect(Object.keys(endpoints).length).to.be.above(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import expect from '@kbn/expect';
|
||||||
|
import { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services';
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
const samlAuth = getService('samlAuth');
|
||||||
|
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||||
|
let roleAuthc: RoleCredentials;
|
||||||
|
let internalHeaders: InternalRequestHeader;
|
||||||
|
|
||||||
|
describe('compression', () => {
|
||||||
|
before(async () => {
|
||||||
|
roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin');
|
||||||
|
internalHeaders = samlAuth.getInternalRequestHeader();
|
||||||
|
});
|
||||||
|
after(async () => {
|
||||||
|
await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc);
|
||||||
|
});
|
||||||
|
describe('against an application page', () => {
|
||||||
|
it(`uses compression when there isn't a referer`, async () => {
|
||||||
|
const response = await supertestWithoutAuth
|
||||||
|
.get('/app/kibana')
|
||||||
|
.set('accept-encoding', 'gzip')
|
||||||
|
.set(internalHeaders)
|
||||||
|
.set(roleAuthc.apiKeyHeader);
|
||||||
|
expect(response.header).to.have.property('content-encoding', 'gzip');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`uses compression when there is a whitelisted referer`, async () => {
|
||||||
|
const response = await supertestWithoutAuth
|
||||||
|
.get('/app/kibana')
|
||||||
|
.set('accept-encoding', 'gzip')
|
||||||
|
.set(internalHeaders)
|
||||||
|
.set('referer', 'https://some-host.com')
|
||||||
|
.set(roleAuthc.apiKeyHeader);
|
||||||
|
expect(response.header).to.have.property('content-encoding', 'gzip');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
describe('core', () => {
|
||||||
|
loadTestFile(require.resolve('./compression'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -5,9 +5,9 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context';
|
||||||
|
|
||||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
|
||||||
describe('Painless Lab', () => {
|
describe('Painless Lab', () => {
|
||||||
loadTestFile(require.resolve('./painless_lab'));
|
loadTestFile(require.resolve('./painless_lab'));
|
||||||
});
|
});
|
|
@ -6,22 +6,34 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import expect from '@kbn/expect';
|
import expect from '@kbn/expect';
|
||||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
import { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services';
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context';
|
||||||
|
|
||||||
const API_BASE_PATH = '/api/painless_lab';
|
const API_BASE_PATH = '/api/painless_lab';
|
||||||
|
|
||||||
export default function ({ getService }: FtrProviderContext) {
|
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
const supertest = getService('supertest');
|
const samlAuth = getService('samlAuth');
|
||||||
|
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||||
|
let roleAuthc: RoleCredentials;
|
||||||
|
let internalHeaders: InternalRequestHeader;
|
||||||
|
|
||||||
describe('Painless Lab', function () {
|
describe('Painless Lab Routes', function () {
|
||||||
|
before(async () => {
|
||||||
|
roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin');
|
||||||
|
internalHeaders = samlAuth.getInternalRequestHeader();
|
||||||
|
});
|
||||||
|
after(async () => {
|
||||||
|
await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc);
|
||||||
|
});
|
||||||
describe('Execute', () => {
|
describe('Execute', () => {
|
||||||
it('should execute a valid painless script', async () => {
|
it('should execute a valid painless script', async () => {
|
||||||
const script =
|
const script =
|
||||||
'"{\\n \\"script\\": {\\n \\"source\\": \\"return true;\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"';
|
'"{\\n \\"script\\": {\\n \\"source\\": \\"return true;\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"';
|
||||||
|
|
||||||
const { body } = await supertest
|
const { body } = await supertestWithoutAuth
|
||||||
.post(`${API_BASE_PATH}/execute`)
|
.post(`${API_BASE_PATH}/execute`)
|
||||||
.set('kbn-xsrf', 'xxx')
|
.set(internalHeaders)
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
.set('Content-Type', 'application/json;charset=UTF-8')
|
.set('Content-Type', 'application/json;charset=UTF-8')
|
||||||
.send(script)
|
.send(script)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
@ -35,10 +47,11 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
const invalidScript =
|
const invalidScript =
|
||||||
'"{\\n \\"script\\": {\\n \\"source\\": \\"foobar\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"';
|
'"{\\n \\"script\\": {\\n \\"source\\": \\"foobar\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"';
|
||||||
|
|
||||||
const { body } = await supertest
|
const { body } = await supertestWithoutAuth
|
||||||
.post(`${API_BASE_PATH}/execute`)
|
.post(`${API_BASE_PATH}/execute`)
|
||||||
.set('kbn-xsrf', 'xxx')
|
.set(internalHeaders)
|
||||||
.set('Content-Type', 'application/json;charset=UTF-8')
|
.set('Content-Type', 'application/json;charset=UTF-8')
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
.send(invalidScript)
|
.send(invalidScript)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
import { FtrConfigProviderContext, Config } from '@kbn/test';
|
||||||
|
|
||||||
|
import { ServerlessProjectType } from '@kbn/es';
|
||||||
|
import { services } from '../services';
|
||||||
|
|
||||||
|
interface CreateTestConfigOptions {
|
||||||
|
serverlessProject: ServerlessProjectType;
|
||||||
|
esServerArgs?: string[];
|
||||||
|
kbnServerArgs?: string[];
|
||||||
|
testFiles: string[];
|
||||||
|
junit: { reportName: string };
|
||||||
|
suiteTags?: { include?: string[]; exclude?: string[] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// include settings from elasticsearch controller
|
||||||
|
// https://github.com/elastic/elasticsearch-controller/blob/main/helm/values.yaml
|
||||||
|
const esServerArgsFromController = {
|
||||||
|
es: [],
|
||||||
|
oblt: [
|
||||||
|
'xpack.apm_data.enabled=true',
|
||||||
|
// for ML, data frame analytics are not part of this project type
|
||||||
|
'xpack.ml.dfa.enabled=false',
|
||||||
|
],
|
||||||
|
security: [
|
||||||
|
'xpack.security.authc.api_key.cache.max_keys=70000',
|
||||||
|
'data_streams.lifecycle.retention.factory_default=365d',
|
||||||
|
'data_streams.lifecycle.retention.factory_max=365d',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// include settings from kibana controller
|
||||||
|
// https://github.com/elastic/kibana-controller/blob/main/internal/controllers/kibana/config/config_settings.go
|
||||||
|
const kbnServerArgsFromController = {
|
||||||
|
es: [
|
||||||
|
// useful for testing (also enabled in MKI QA)
|
||||||
|
'--coreApp.allowDynamicConfigOverrides=true',
|
||||||
|
],
|
||||||
|
oblt: [
|
||||||
|
'--coreApp.allowDynamicConfigOverrides=true',
|
||||||
|
// defined in MKI control plane
|
||||||
|
'--xpack.uptime.service.manifestUrl=mockDevUrl',
|
||||||
|
],
|
||||||
|
security: [
|
||||||
|
'--coreApp.allowDynamicConfigOverrides=true',
|
||||||
|
// disable fleet task that writes to metrics.fleet_server.* data streams, impacting functional tests
|
||||||
|
`--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify(['Fleet-Metrics-Task'])}`,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createServerlessTestConfig(options: CreateTestConfigOptions) {
|
||||||
|
return async ({ readConfigFile }: FtrConfigProviderContext): Promise<Config> => {
|
||||||
|
const svlSharedConfig = await readConfigFile(
|
||||||
|
require.resolve('@kbn/test-suites-serverless/shared/config.base')
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...svlSharedConfig.getAll(),
|
||||||
|
|
||||||
|
services: {
|
||||||
|
...services,
|
||||||
|
},
|
||||||
|
esTestCluster: {
|
||||||
|
...svlSharedConfig.get('esTestCluster'),
|
||||||
|
serverArgs: [
|
||||||
|
...svlSharedConfig.get('esTestCluster.serverArgs'),
|
||||||
|
...esServerArgsFromController[options.serverlessProject],
|
||||||
|
...(options.esServerArgs ?? []),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
kbnTestServer: {
|
||||||
|
...svlSharedConfig.get('kbnTestServer'),
|
||||||
|
serverArgs: [
|
||||||
|
...svlSharedConfig.get('kbnTestServer.serverArgs'),
|
||||||
|
...kbnServerArgsFromController[options.serverlessProject],
|
||||||
|
`--serverless=${options.serverlessProject}`,
|
||||||
|
...(options.kbnServerArgs || []),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
testFiles: options.testFiles,
|
||||||
|
junit: options.junit,
|
||||||
|
suiteTags: options.suiteTags,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
MOCK_IDP_REALM_NAME,
|
||||||
|
MOCK_IDP_ENTITY_ID,
|
||||||
|
MOCK_IDP_ATTRIBUTE_PRINCIPAL,
|
||||||
|
MOCK_IDP_ATTRIBUTE_ROLES,
|
||||||
|
MOCK_IDP_ATTRIBUTE_EMAIL,
|
||||||
|
MOCK_IDP_ATTRIBUTE_NAME,
|
||||||
|
} from '@kbn/mock-idp-utils';
|
||||||
|
import {
|
||||||
|
esTestConfig,
|
||||||
|
kbnTestConfig,
|
||||||
|
systemIndicesSuperuser,
|
||||||
|
FtrConfigProviderContext,
|
||||||
|
} from '@kbn/test';
|
||||||
|
import { services } from '../services';
|
||||||
|
|
||||||
|
interface CreateTestConfigOptions {
|
||||||
|
esServerArgs?: string[];
|
||||||
|
kbnServerArgs?: string[];
|
||||||
|
testFiles: string[];
|
||||||
|
junit: { reportName: string };
|
||||||
|
suiteTags?: { include?: string[]; exclude?: string[] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createStatefulTestConfig(options: CreateTestConfigOptions) {
|
||||||
|
return async ({ readConfigFile }: FtrConfigProviderContext) => {
|
||||||
|
const xPackAPITestsConfig = await readConfigFile(require.resolve('../../config.ts'));
|
||||||
|
|
||||||
|
// TODO: move to kbn-es because currently metadata file has hardcoded entityID and Location
|
||||||
|
const idpPath = require.resolve(
|
||||||
|
'@kbn/security-api-integration-helpers/saml/idp_metadata_mock_idp.xml'
|
||||||
|
);
|
||||||
|
|
||||||
|
const servers = {
|
||||||
|
kibana: {
|
||||||
|
...kbnTestConfig.getUrlParts(systemIndicesSuperuser),
|
||||||
|
protocol: process.env.TEST_CLOUD ? 'https' : 'http',
|
||||||
|
},
|
||||||
|
elasticsearch: {
|
||||||
|
...esTestConfig.getUrlParts(),
|
||||||
|
protocol: process.env.TEST_CLOUD ? 'https' : 'http',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const kbnUrl = `${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
servers,
|
||||||
|
testFiles: options.testFiles,
|
||||||
|
security: { disableTestUser: true },
|
||||||
|
services,
|
||||||
|
junit: options.junit,
|
||||||
|
suiteTags: options.suiteTags,
|
||||||
|
|
||||||
|
esTestCluster: {
|
||||||
|
...xPackAPITestsConfig.get('esTestCluster'),
|
||||||
|
serverArgs: [
|
||||||
|
...xPackAPITestsConfig.get('esTestCluster.serverArgs'),
|
||||||
|
...(options.esServerArgs ?? []),
|
||||||
|
'xpack.security.authc.token.enabled=true',
|
||||||
|
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.order=0`,
|
||||||
|
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.metadata.path=${idpPath}`,
|
||||||
|
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.entity_id=${MOCK_IDP_ENTITY_ID}`,
|
||||||
|
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.entity_id=${kbnUrl}`,
|
||||||
|
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.acs=${kbnUrl}/api/security/saml/callback`,
|
||||||
|
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.logout=${kbnUrl}/logout`,
|
||||||
|
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.principal=${MOCK_IDP_ATTRIBUTE_PRINCIPAL}`,
|
||||||
|
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.groups=${MOCK_IDP_ATTRIBUTE_ROLES}`,
|
||||||
|
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.name=${MOCK_IDP_ATTRIBUTE_NAME}`,
|
||||||
|
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.mail=${MOCK_IDP_ATTRIBUTE_EMAIL}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
kbnTestServer: {
|
||||||
|
...xPackAPITestsConfig.get('kbnTestServer'),
|
||||||
|
serverArgs: [
|
||||||
|
...xPackAPITestsConfig.get('kbnTestServer.serverArgs'),
|
||||||
|
...(options.kbnServerArgs || []),
|
||||||
|
'--xpack.security.authc.selector.enabled=false',
|
||||||
|
`--xpack.security.authc.providers=${JSON.stringify({
|
||||||
|
saml: { 'cloud-saml-kibana': { order: 0, realm: MOCK_IDP_REALM_NAME } },
|
||||||
|
basic: { 'cloud-basic': { order: 1 } },
|
||||||
|
})}`,
|
||||||
|
`--server.publicBaseUrl=${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
12
x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts
vendored
Normal file
12
x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GenericFtrProviderContext } from '@kbn/test';
|
||||||
|
|
||||||
|
import { services } from './services';
|
||||||
|
|
||||||
|
export type DeploymentAgnosticFtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
describe('Serverless Observability - Deployment-agnostic api integration tests', () => {
|
||||||
|
loadTestFile(require.resolve('./apis/console'));
|
||||||
|
loadTestFile(require.resolve('./apis/core'));
|
||||||
|
loadTestFile(require.resolve('./apis/painless_lab'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createServerlessTestConfig } from './default_configs/serverless.config.base';
|
||||||
|
|
||||||
|
export default createServerlessTestConfig({
|
||||||
|
serverlessProject: 'oblt',
|
||||||
|
testFiles: [require.resolve('./oblt.index.ts')],
|
||||||
|
junit: {
|
||||||
|
reportName: 'Serverless Observability - Deployment-agnostic API Integration Tests',
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
describe('Serverless Search - Deployment-agnostic api integration tests', () => {
|
||||||
|
loadTestFile(require.resolve('./apis/console'));
|
||||||
|
loadTestFile(require.resolve('./apis/core'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createServerlessTestConfig } from './default_configs/serverless.config.base';
|
||||||
|
|
||||||
|
export default createServerlessTestConfig({
|
||||||
|
serverlessProject: 'es',
|
||||||
|
testFiles: [require.resolve('./search.index.ts')],
|
||||||
|
junit: {
|
||||||
|
reportName: 'Serverless Search - Deployment-agnostic API Integration Tests',
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
describe('Security Search - Deployment-agnostic api integration tests', () => {
|
||||||
|
loadTestFile(require.resolve('./apis/console'));
|
||||||
|
loadTestFile(require.resolve('./apis/core'));
|
||||||
|
loadTestFile(require.resolve('./apis/painless_lab'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createServerlessTestConfig } from './default_configs/serverless.config.base';
|
||||||
|
|
||||||
|
export default createServerlessTestConfig({
|
||||||
|
serverlessProject: 'security',
|
||||||
|
testFiles: [require.resolve('./security.index.ts')],
|
||||||
|
junit: {
|
||||||
|
reportName: 'Serverless Security - Deployment-agnostic API Integration Tests',
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context';
|
||||||
|
|
||||||
|
export function DataViewApiProvider({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||||
|
const samlAuth = getService('samlAuth');
|
||||||
|
|
||||||
|
return {
|
||||||
|
async create({
|
||||||
|
roleAuthc,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
roleAuthc: RoleCredentials;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
}) {
|
||||||
|
const { body } = await supertestWithoutAuth
|
||||||
|
.post(`/api/content_management/rpc/create`)
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
|
.set(samlAuth.getInternalRequestHeader())
|
||||||
|
.set(samlAuth.getCommonRequestHeader())
|
||||||
|
.send({
|
||||||
|
contentTypeId: 'index-pattern',
|
||||||
|
data: {
|
||||||
|
fieldAttrs: '{}',
|
||||||
|
title,
|
||||||
|
timeFieldName: '@timestamp',
|
||||||
|
sourceFilters: '[]',
|
||||||
|
fields: '[]',
|
||||||
|
fieldFormatMap: '{}',
|
||||||
|
typeMeta: '{}',
|
||||||
|
runtimeFieldMap: '{}',
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
options: { id },
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
return body;
|
||||||
|
},
|
||||||
|
|
||||||
|
async delete({ roleAuthc, id }: { roleAuthc: RoleCredentials; id: string }) {
|
||||||
|
const { body } = await supertestWithoutAuth
|
||||||
|
.post(`/api/content_management/rpc/create`)
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
|
.set(samlAuth.getInternalRequestHeader())
|
||||||
|
.set(samlAuth.getCommonRequestHeader())
|
||||||
|
.send({
|
||||||
|
contentTypeId: 'index-pattern',
|
||||||
|
id,
|
||||||
|
options: { force: true },
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
return body;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { services as apiIntegrationServices } from '../../services';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load only services that support both stateful & serverless deployments (including Cloud/MKI),
|
||||||
|
* e.g. `randomness` or `retry` are deployment agnostic
|
||||||
|
*/
|
||||||
|
export const deploymentAgnosticServices = _.pick(apiIntegrationServices, [
|
||||||
|
'supertest', // TODO: review its behaviour
|
||||||
|
'es',
|
||||||
|
'esArchiver',
|
||||||
|
'esSupertest', // TODO: review its behaviour
|
||||||
|
'indexPatterns',
|
||||||
|
'ingestPipelines',
|
||||||
|
'kibanaServer',
|
||||||
|
// 'ml', depends on 'esDeleteAllIndices', can we make it deployment agnostic?
|
||||||
|
'randomness',
|
||||||
|
'retry',
|
||||||
|
'security',
|
||||||
|
'usageAPI',
|
||||||
|
]);
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
|
||||||
|
import { deploymentAgnosticServices } from './deployment_agnostic_services';
|
||||||
|
import { DataViewApiProvider } from './data_view_api';
|
||||||
|
import { SloApiProvider } from './slo_api';
|
||||||
|
|
||||||
|
export type {
|
||||||
|
InternalRequestHeader,
|
||||||
|
RoleCredentials,
|
||||||
|
SupertestWithoutAuthProviderType,
|
||||||
|
} from '@kbn/ftr-common-functional-services';
|
||||||
|
|
||||||
|
export const services = {
|
||||||
|
...deploymentAgnosticServices,
|
||||||
|
supertestWithoutAuth: commonFunctionalServices.supertestWithoutAuth,
|
||||||
|
samlAuth: commonFunctionalServices.samlAuth,
|
||||||
|
dataViewApi: DataViewApiProvider,
|
||||||
|
sloApi: SloApiProvider,
|
||||||
|
// create a new deployment-agnostic service and load here
|
||||||
|
};
|
|
@ -0,0 +1,203 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
fetchHistoricalSummaryParamsSchema,
|
||||||
|
FetchHistoricalSummaryResponse,
|
||||||
|
} from '@kbn/slo-schema';
|
||||||
|
import * as t from 'io-ts';
|
||||||
|
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context';
|
||||||
|
|
||||||
|
type DurationUnit = 'm' | 'h' | 'd' | 'w' | 'M';
|
||||||
|
|
||||||
|
interface Duration {
|
||||||
|
value: number;
|
||||||
|
unit: DurationUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WindowSchema {
|
||||||
|
id: string;
|
||||||
|
burnRateThreshold: number;
|
||||||
|
maxBurnRateThreshold: number;
|
||||||
|
longWindow: Duration;
|
||||||
|
shortWindow: Duration;
|
||||||
|
actionGroup: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Dependency {
|
||||||
|
ruleId: string;
|
||||||
|
actionGroupsToSuppressOn: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SloBurnRateRuleParams {
|
||||||
|
sloId: string;
|
||||||
|
windows: WindowSchema[];
|
||||||
|
dependencies?: Dependency[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SloParams {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
indicator: {
|
||||||
|
type: 'sli.kql.custom';
|
||||||
|
params: {
|
||||||
|
index: string;
|
||||||
|
good: string;
|
||||||
|
total: string;
|
||||||
|
timestampField: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
timeWindow: {
|
||||||
|
duration: string;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
budgetingMethod: string;
|
||||||
|
objective: {
|
||||||
|
target: number;
|
||||||
|
};
|
||||||
|
groupBy: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FetchHistoricalSummaryParams = t.OutputOf<
|
||||||
|
typeof fetchHistoricalSummaryParamsSchema.props.body
|
||||||
|
>;
|
||||||
|
|
||||||
|
interface SloRequestParams {
|
||||||
|
id: string;
|
||||||
|
roleAuthc: RoleCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SloApiProvider({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
const es = getService('es');
|
||||||
|
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||||
|
const samlAuth = getService('samlAuth');
|
||||||
|
const retry = getService('retry');
|
||||||
|
const config = getService('config');
|
||||||
|
const retryTimeout = config.get('timeouts.try');
|
||||||
|
const requestTimeout = 30 * 1000;
|
||||||
|
|
||||||
|
return {
|
||||||
|
async create(slo: SloParams, roleAuthc: RoleCredentials) {
|
||||||
|
const { body } = await supertestWithoutAuth
|
||||||
|
.post(`/api/observability/slos`)
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
|
.set(samlAuth.getInternalRequestHeader())
|
||||||
|
.send(slo);
|
||||||
|
return body;
|
||||||
|
},
|
||||||
|
|
||||||
|
async delete({ id, roleAuthc }: SloRequestParams) {
|
||||||
|
const response = await supertestWithoutAuth
|
||||||
|
.delete(`/api/observability/slos/${id}`)
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
|
.set(samlAuth.getInternalRequestHeader());
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchHistoricalSummary(
|
||||||
|
params: FetchHistoricalSummaryParams,
|
||||||
|
roleAuthc: RoleCredentials
|
||||||
|
): Promise<FetchHistoricalSummaryResponse> {
|
||||||
|
const { body } = await supertestWithoutAuth
|
||||||
|
.post(`/internal/observability/slos/_historical_summary`)
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
|
.set(samlAuth.getInternalRequestHeader())
|
||||||
|
.send(params);
|
||||||
|
return body;
|
||||||
|
},
|
||||||
|
|
||||||
|
async waitForSloToBeDeleted({ id, roleAuthc }: SloRequestParams) {
|
||||||
|
return await retry.tryForTime(retryTimeout, async () => {
|
||||||
|
const response = await supertestWithoutAuth
|
||||||
|
.delete(`/api/observability/slos/${id}`)
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
|
.set(samlAuth.getInternalRequestHeader())
|
||||||
|
.timeout(requestTimeout);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`SLO with id '${id}' was not deleted`);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async waitForSloCreated({ id, roleAuthc }: SloRequestParams) {
|
||||||
|
return await retry.tryForTime(retryTimeout, async () => {
|
||||||
|
const response = await supertestWithoutAuth
|
||||||
|
.get(`/api/observability/slos/${id}`)
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
|
.set(samlAuth.getInternalRequestHeader())
|
||||||
|
.timeout(requestTimeout);
|
||||||
|
if (response.body.id === undefined) {
|
||||||
|
throw new Error(`No SLO with id '${id}' found`);
|
||||||
|
}
|
||||||
|
return response.body;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async waitForSloSummaryTempIndexToExist(index: string) {
|
||||||
|
return await retry.tryForTime(retryTimeout, async () => {
|
||||||
|
const indexExists = await es.indices.exists({ index, allow_no_indices: false });
|
||||||
|
if (!indexExists) {
|
||||||
|
throw new Error(`SLO summary index '${index}' should exist`);
|
||||||
|
}
|
||||||
|
return indexExists;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async getSloData({ sloId, indexName }: { sloId: string; indexName: string }) {
|
||||||
|
const response = await es.search({
|
||||||
|
index: indexName,
|
||||||
|
body: {
|
||||||
|
query: {
|
||||||
|
bool: {
|
||||||
|
filter: [{ term: { 'slo.id': sloId } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
async waitForSloData({ id, indexName }: { id: string; indexName: string }) {
|
||||||
|
return await retry.tryForTime(retryTimeout, async () => {
|
||||||
|
const response = await es.search({
|
||||||
|
index: indexName,
|
||||||
|
body: {
|
||||||
|
query: {
|
||||||
|
bool: {
|
||||||
|
filter: [{ term: { 'slo.id': id } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (response.hits.hits.length === 0) {
|
||||||
|
throw new Error(`No hits found at index '${indexName}' for slo id='${id}'`);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async deleteAllSLOs(roleAuthc: RoleCredentials) {
|
||||||
|
const response = await supertestWithoutAuth
|
||||||
|
.get(`/api/observability/slos/_definitions`)
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
|
.set(samlAuth.getInternalRequestHeader())
|
||||||
|
.send()
|
||||||
|
.expect(200);
|
||||||
|
await Promise.all(
|
||||||
|
response.body.results.map(({ id }: { id: string }) => {
|
||||||
|
return supertestWithoutAuth
|
||||||
|
.delete(`/api/observability/slos/${id}`)
|
||||||
|
.set(roleAuthc.apiKeyHeader)
|
||||||
|
.set(samlAuth.getInternalRequestHeader())
|
||||||
|
.send()
|
||||||
|
.expect(204);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createStatefulTestConfig } from './default_configs/stateful.config.base';
|
||||||
|
|
||||||
|
export default createStatefulTestConfig({
|
||||||
|
testFiles: [require.resolve('./stateful.index.ts')],
|
||||||
|
junit: {
|
||||||
|
reportName: 'Stateful - Deployment-agnostic API Integration Tests',
|
||||||
|
},
|
||||||
|
// extra arguments
|
||||||
|
esServerArgs: [],
|
||||||
|
kbnServerArgs: [],
|
||||||
|
});
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
describe('apis', () => {
|
||||||
|
loadTestFile(require.resolve('./apis/console'));
|
||||||
|
loadTestFile(require.resolve('./apis/core'));
|
||||||
|
loadTestFile(require.resolve('./apis/painless_lab'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -11,8 +11,8 @@ import path from 'path';
|
||||||
// @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail
|
// @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail
|
||||||
import { REPO_ROOT } from '@kbn/repo-info';
|
import { REPO_ROOT } from '@kbn/repo-info';
|
||||||
import { createFlagError } from '@kbn/dev-cli-errors';
|
import { createFlagError } from '@kbn/dev-cli-errors';
|
||||||
import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
|
|
||||||
import { v4 as uuidV4 } from 'uuid';
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
|
import { services } from './services';
|
||||||
import { ScalabilityTestRunner } from './runner';
|
import { ScalabilityTestRunner } from './runner';
|
||||||
import { FtrProviderContext } from './ftr_provider_context';
|
import { FtrProviderContext } from './ftr_provider_context';
|
||||||
import { ScalabilityJourney } from './types';
|
import { ScalabilityJourney } from './types';
|
||||||
|
@ -49,7 +49,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||||
return {
|
return {
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
|
|
||||||
services: commonFunctionalServices,
|
services,
|
||||||
pageObjects: {},
|
pageObjects: {},
|
||||||
|
|
||||||
testRunner: (context: FtrProviderContext) =>
|
testRunner: (context: FtrProviderContext) =>
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
|
|
||||||
import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test';
|
import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test';
|
||||||
|
import { services } from './services';
|
||||||
|
|
||||||
export type FtrProviderContext = GenericFtrProviderContext<typeof commonFunctionalServices, {}>;
|
export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
|
||||||
export class FtrService extends GenericFtrService<FtrProviderContext> {}
|
export class FtrService extends GenericFtrService<FtrProviderContext> {}
|
||||||
|
|
17
x-pack/test/scalability/services.ts
Normal file
17
x-pack/test/scalability/services.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
|
||||||
|
|
||||||
|
const { es, esArchiver, kibanaServer, retry, supertestWithoutAuth } = commonFunctionalServices;
|
||||||
|
export const services = {
|
||||||
|
es,
|
||||||
|
esArchiver,
|
||||||
|
kibanaServer,
|
||||||
|
retry,
|
||||||
|
supertestWithoutAuth,
|
||||||
|
};
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
entityID="urn:mock-idp">
|
||||||
|
<md:IDPSSODescriptor WantAuthnRequestsSigned="false"
|
||||||
|
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
|
<md:KeyDescriptor use="signing">
|
||||||
|
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
||||||
|
<ds:X509Data>
|
||||||
|
<!-- This certificate is extracted from KBN_CERT_PATH in @kbn/dev-utils and should always be in sync with it -->
|
||||||
|
<ds:X509Certificate>MIIDYjCCAkqgAwIBAgIUZ2p8K7GMXGk6xwCS9S91BUl1JnAwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l
|
||||||
|
cmF0ZWQgQ0EwIBcNMjMwOTIzMTUyMDE0WhgPMjA3MzA5MTAxNTIwMTRaMBExDzAN
|
||||||
|
BgNVBAMTBmtpYmFuYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMOU
|
||||||
|
r52dbZ5dY0BoP2p7CEnOpG+qHTNrOAqZO/OJfniPMtpGmwAMl3WZDca6u2XkV2KE
|
||||||
|
qQyevQ2ADk6G3o8S2RU8mO/+UweuCDF7LHuSdxEGTpucidZErmVhEGUOFosL5UeB
|
||||||
|
AtIDWxvWwgK+W9Yzt5IEN2HzNCZ6h0dOSk2r9EjVMG5yF4Q6kuqOYxBT7jxoaOtO
|
||||||
|
OCrgBRummtUga4T13WZ/ZIyyHpXj2+JD4YEmrDyoTa7NLaphv0hnVhHXYoYBI/c6
|
||||||
|
2SwwAoBlmtDmlinwSACQ3o/8eLWk0tqkIP14rc3oFh3m7D2c3c2m2HXuyoSDMfGW
|
||||||
|
beG2IE1Q3idcGmeG3qsCAwEAAaOBjDCBiTAdBgNVHQ4EFgQUMOUM7w5jmIozDvnq
|
||||||
|
RpM779m5GigwHwYDVR0jBBgwFoAUMEwqwI5b0MYpNxwaHJ9Tw1Lp3p4wPAYDVR0R
|
||||||
|
BDUwM4IUaG9zdC5kb2NrZXIuaW50ZXJuYWyCCWxvY2FsaG9zdIIEZXMwM4IEZXMw
|
||||||
|
MoIEZXMwMTAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCxqvQYXSKqgpdl
|
||||||
|
SP4gXgwipAnYsoW9qkgWQODTvSBEzUdOWme0d3j7i2l6Ur/nVSv5YjkqAv1hf/yJ
|
||||||
|
Hrk9h+j29ZO/aQ/KDh5i/gTEUnPw3Bxbw47dfn23tjMWO7NCU1fr5HNztRsa/gQr
|
||||||
|
e9s07g25u/gTfTi9Fyu0lcRe3bXOLS/mFVcuC5oxuS65R9OlbIsiORkZ2EfwuNUf
|
||||||
|
wAAYOGPIjM2VlQCvBitefsd/SzRKHdxSPy6KSjkO6MGEGo87fr7B7Nx1qp1DVrK7
|
||||||
|
q9XeP1Cuygjg9WTcnsvWvNw8CssyuFM6X/3tGjpPasXwLvNUoG2AairK2AYTWhvS
|
||||||
|
foE31cFg</ds:X509Certificate>
|
||||||
|
</ds:X509Data>
|
||||||
|
</ds:KeyInfo>
|
||||||
|
</md:KeyDescriptor>
|
||||||
|
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
|
Location="http://localhost:5620/mock_idp/logout"/>
|
||||||
|
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||||
|
Location="http://localhost:5620/mock_idp/logout"/>
|
||||||
|
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
|
Location="http://localhost:5620/mock_idp/login"/>
|
||||||
|
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||||
|
Location="http://localhost:5620/mock_idp/login"/>
|
||||||
|
</md:IDPSSODescriptor>
|
||||||
|
</md:EntityDescriptor>
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SvlUserManagerProvider } from '@kbn/test-suites-serverless/shared/services/svl_user_manager';
|
import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
|
||||||
import { SvlCommonApiServiceProvider } from '@kbn/test-suites-serverless/shared/services/svl_common_api';
|
import { SvlCommonApiServiceProvider } from '@kbn/test-suites-serverless/shared/services/svl_common_api';
|
||||||
import { services as essServices } from '../ess/services_edr_workflows';
|
import { services as essServices } from '../ess/services_edr_workflows';
|
||||||
import { SecuritySolutionServerlessSuperTest } from '../services/security_solution_serverless_supertest';
|
import { SecuritySolutionServerlessSuperTest } from '../services/security_solution_serverless_supertest';
|
||||||
|
@ -15,6 +15,6 @@ export const svlServices = {
|
||||||
...essServices,
|
...essServices,
|
||||||
supertest: SecuritySolutionServerlessSuperTest,
|
supertest: SecuritySolutionServerlessSuperTest,
|
||||||
securitySolutionUtils: SecuritySolutionServerlessUtils,
|
securitySolutionUtils: SecuritySolutionServerlessUtils,
|
||||||
svlUserManager: SvlUserManagerProvider,
|
svlUserManager: commonFunctionalServices.samlAuth,
|
||||||
svlCommonApi: SvlCommonApiServiceProvider,
|
svlCommonApi: SvlCommonApiServiceProvider,
|
||||||
};
|
};
|
||||||
|
|
|
@ -32,6 +32,8 @@ export function SecuritySolutionServerlessUtils({
|
||||||
});
|
});
|
||||||
|
|
||||||
async function invalidateApiKey(credentials: RoleCredentials) {
|
async function invalidateApiKey(credentials: RoleCredentials) {
|
||||||
|
// load service to call it outside mocha context
|
||||||
|
await svlUserManager.init();
|
||||||
await svlUserManager.invalidateM2mApiKeyWithRoleScope(credentials);
|
await svlUserManager.invalidateM2mApiKeyWithRoleScope(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +55,8 @@ export function SecuritySolutionServerlessUtils({
|
||||||
|
|
||||||
const createSuperTest = async (role = 'admin') => {
|
const createSuperTest = async (role = 'admin') => {
|
||||||
cleanCredentials(role);
|
cleanCredentials(role);
|
||||||
|
// load service to call it outside mocha context
|
||||||
|
await svlUserManager.init();
|
||||||
const credentials = await svlUserManager.createM2mApiKeyWithRoleScope(role);
|
const credentials = await svlUserManager.createM2mApiKeyWithRoleScope(role);
|
||||||
rolesCredentials.set(role, credentials);
|
rolesCredentials.set(role, credentials);
|
||||||
|
|
||||||
|
@ -62,6 +66,8 @@ export function SecuritySolutionServerlessUtils({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getUsername: async (role = 'admin') => {
|
getUsername: async (role = 'admin') => {
|
||||||
|
// load service to call it outside mocha context
|
||||||
|
await svlUserManager.init();
|
||||||
const { username } = await svlUserManager.getUserData(role);
|
const { username } = await svlUserManager.getUserData(role);
|
||||||
|
|
||||||
return username;
|
return username;
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { ToolingLog } from '@kbn/tooling-log';
|
||||||
|
|
||||||
import { SecurityRoleName } from '@kbn/security-solution-plugin/common/test';
|
import { SecurityRoleName } from '@kbn/security-solution-plugin/common/test';
|
||||||
import { HostOptions, SamlSessionManager } from '@kbn/test';
|
import { HostOptions, SamlSessionManager } from '@kbn/test';
|
||||||
|
import { REPO_ROOT } from '@kbn/repo-info';
|
||||||
|
import { resolve } from 'path';
|
||||||
import { DEFAULT_SERVERLESS_ROLE } from '../env_var_names_constants';
|
import { DEFAULT_SERVERLESS_ROLE } from '../env_var_names_constants';
|
||||||
|
|
||||||
export const samlAuthentication = async (
|
export const samlAuthentication = async (
|
||||||
|
@ -31,30 +33,27 @@ export const samlAuthentication = async (
|
||||||
|
|
||||||
// If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles.
|
// If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles.
|
||||||
const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined;
|
const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined;
|
||||||
|
const cloudUsersFilePath = resolve(REPO_ROOT, '.ftr', rolesFilename ?? 'role_users.json');
|
||||||
|
|
||||||
on('task', {
|
on('task', {
|
||||||
getSessionCookie: async (role: string | SecurityRoleName): Promise<string> => {
|
getSessionCookie: async (role: string | SecurityRoleName): Promise<string> => {
|
||||||
const sessionManager = new SamlSessionManager(
|
const sessionManager = new SamlSessionManager({
|
||||||
{
|
hostOptions,
|
||||||
hostOptions,
|
log,
|
||||||
log,
|
isCloud: config.env.CLOUD_SERVERLESS,
|
||||||
isCloud: config.env.CLOUD_SERVERLESS,
|
cloudUsersFilePath,
|
||||||
},
|
});
|
||||||
rolesFilename
|
|
||||||
);
|
|
||||||
return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role);
|
return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role);
|
||||||
},
|
},
|
||||||
getFullname: async (
|
getFullname: async (
|
||||||
role: string | SecurityRoleName = DEFAULT_SERVERLESS_ROLE
|
role: string | SecurityRoleName = DEFAULT_SERVERLESS_ROLE
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const sessionManager = new SamlSessionManager(
|
const sessionManager = new SamlSessionManager({
|
||||||
{
|
hostOptions,
|
||||||
hostOptions,
|
log,
|
||||||
log,
|
isCloud: config.env.CLOUD_SERVERLESS,
|
||||||
isCloud: config.env.CLOUD_SERVERLESS,
|
cloudUsersFilePath,
|
||||||
},
|
});
|
||||||
rolesFilename
|
|
||||||
);
|
|
||||||
const { full_name: fullName } = await sessionManager.getUserData(role);
|
const { full_name: fullName } = await sessionManager.getUserData(role);
|
||||||
return fullName;
|
return fullName;
|
||||||
},
|
},
|
||||||
|
|
|
@ -42,5 +42,6 @@
|
||||||
"@kbn/actions-plugin",
|
"@kbn/actions-plugin",
|
||||||
"@kbn/alerts-ui-shared",
|
"@kbn/alerts-ui-shared",
|
||||||
"@kbn/securitysolution-endpoint-exceptions-common",
|
"@kbn/securitysolution-endpoint-exceptions-common",
|
||||||
|
"@kbn/repo-info",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SvlUserManagerProvider } from '@kbn/test-suites-serverless/shared/services/svl_user_manager';
|
import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
|
||||||
import { SvlCommonApiServiceProvider } from '@kbn/test-suites-serverless/shared/services/svl_common_api';
|
import { SvlCommonApiServiceProvider } from '@kbn/test-suites-serverless/shared/services/svl_common_api';
|
||||||
import { services as xPackFunctionalServices } from '../../functional/services';
|
import { services as xPackFunctionalServices } from '../../functional/services';
|
||||||
import { IngestManagerProvider } from '../../common/services/ingest_manager';
|
import { IngestManagerProvider } from '../../common/services/ingest_manager';
|
||||||
|
@ -43,7 +43,7 @@ export const svlServices = {
|
||||||
supertestWithoutAuth: KibanaSupertestWithCertWithoutAuthProvider,
|
supertestWithoutAuth: KibanaSupertestWithCertWithoutAuthProvider,
|
||||||
|
|
||||||
svlCommonApi: SvlCommonApiServiceProvider,
|
svlCommonApi: SvlCommonApiServiceProvider,
|
||||||
svlUserManager: SvlUserManagerProvider,
|
svlUserManager: commonFunctionalServices.samlAuth,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Services = typeof services | typeof svlServices;
|
export type Services = typeof services | typeof svlServices;
|
||||||
|
|
|
@ -27,5 +27,6 @@
|
||||||
"@kbn/ftr-common-functional-ui-services",
|
"@kbn/ftr-common-functional-ui-services",
|
||||||
"@kbn/test",
|
"@kbn/test",
|
||||||
"@kbn/test-subj-selector",
|
"@kbn/test-subj-selector",
|
||||||
|
"@kbn/ftr-common-functional-services",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,6 +176,7 @@
|
||||||
"@kbn/osquery-plugin",
|
"@kbn/osquery-plugin",
|
||||||
"@kbn/entities-schema",
|
"@kbn/entities-schema",
|
||||||
"@kbn/actions-simulators-plugin",
|
"@kbn/actions-simulators-plugin",
|
||||||
"@kbn/cases-api-integration-test-plugin"
|
"@kbn/cases-api-integration-test-plugin",
|
||||||
|
"@kbn/mock-idp-utils"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
|
|
||||||
import expect from '@kbn/expect';
|
import expect from '@kbn/expect';
|
||||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||||
import { RoleCredentials } from '../../../../shared/services';
|
import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services';
|
||||||
import { InternalRequestHeader } from '../../../../shared/services/svl_common_api';
|
|
||||||
|
|
||||||
export default ({ getService }: FtrProviderContext) => {
|
export default ({ getService }: FtrProviderContext) => {
|
||||||
const svlCommonApi = getService('svlCommonApi');
|
const svlCommonApi = getService('svlCommonApi');
|
||||||
|
|
|
@ -42,7 +42,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows to mutate the objects during an export', async () => {
|
it('allows to mutate the objects during an export', async () => {
|
||||||
await supertest
|
const resp = await supertest
|
||||||
.post('/api/saved_objects/_export')
|
.post('/api/saved_objects/_export')
|
||||||
.set(svlCommonApi.getCommonRequestHeader())
|
.set(svlCommonApi.getCommonRequestHeader())
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(svlCommonApi.getInternalRequestHeader())
|
||||||
|
@ -50,24 +50,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
type: ['test-export-transform'],
|
type: ['test-export-transform'],
|
||||||
excludeExportDetails: true,
|
excludeExportDetails: true,
|
||||||
})
|
})
|
||||||
.expect(200)
|
.expect(200);
|
||||||
.then((resp) => {
|
|
||||||
const objects = parseNdJson(resp.text);
|
const objects = parseNdJson(resp.text);
|
||||||
expect(objects.map((obj) => ({ id: obj.id, enabled: obj.attributes.enabled }))).to.eql([
|
expect(objects.map((obj) => ({ id: obj.id, enabled: obj.attributes.enabled }))).to.eql([
|
||||||
{
|
{
|
||||||
id: 'type_1-obj_1',
|
id: 'type_1-obj_1',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'type_1-obj_2',
|
id: 'type_1-obj_2',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows to add additional objects to an export', async () => {
|
it('allows to add additional objects to an export', async () => {
|
||||||
await supertest
|
const resp = await supertest
|
||||||
.post('/api/saved_objects/_export')
|
.post('/api/saved_objects/_export')
|
||||||
.set(svlCommonApi.getCommonRequestHeader())
|
.set(svlCommonApi.getCommonRequestHeader())
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(svlCommonApi.getInternalRequestHeader())
|
||||||
|
@ -80,15 +79,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
],
|
],
|
||||||
excludeExportDetails: true,
|
excludeExportDetails: true,
|
||||||
})
|
})
|
||||||
.expect(200)
|
.expect(200);
|
||||||
.then((resp) => {
|
const objects = parseNdJson(resp.text);
|
||||||
const objects = parseNdJson(resp.text);
|
expect(objects.map((obj) => obj.id)).to.eql(['type_2-obj_1', 'type_dep-obj_1']);
|
||||||
expect(objects.map((obj) => obj.id)).to.eql(['type_2-obj_1', 'type_dep-obj_1']);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows to add additional objects to an export when exporting by type', async () => {
|
it('allows to add additional objects to an export when exporting by type', async () => {
|
||||||
await supertest
|
const resp = await supertest
|
||||||
.post('/api/saved_objects/_export')
|
.post('/api/saved_objects/_export')
|
||||||
.set(svlCommonApi.getCommonRequestHeader())
|
.set(svlCommonApi.getCommonRequestHeader())
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(svlCommonApi.getInternalRequestHeader())
|
||||||
|
@ -96,20 +93,18 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
type: ['test-export-add'],
|
type: ['test-export-add'],
|
||||||
excludeExportDetails: true,
|
excludeExportDetails: true,
|
||||||
})
|
})
|
||||||
.expect(200)
|
.expect(200);
|
||||||
.then((resp) => {
|
const objects = parseNdJson(resp.text);
|
||||||
const objects = parseNdJson(resp.text);
|
expect(objects.map((obj) => obj.id)).to.eql([
|
||||||
expect(objects.map((obj) => obj.id)).to.eql([
|
'type_2-obj_1',
|
||||||
'type_2-obj_1',
|
'type_2-obj_2',
|
||||||
'type_2-obj_2',
|
'type_dep-obj_1',
|
||||||
'type_dep-obj_1',
|
'type_dep-obj_2',
|
||||||
'type_dep-obj_2',
|
]);
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a 400 when the type causes a transform error', async () => {
|
it('returns a 400 when the type causes a transform error', async () => {
|
||||||
await supertest
|
const resp = await supertest
|
||||||
.post('/api/saved_objects/_export')
|
.post('/api/saved_objects/_export')
|
||||||
.set(svlCommonApi.getCommonRequestHeader())
|
.set(svlCommonApi.getCommonRequestHeader())
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(svlCommonApi.getInternalRequestHeader())
|
||||||
|
@ -117,21 +112,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
type: ['test-export-transform-error'],
|
type: ['test-export-transform-error'],
|
||||||
excludeExportDetails: true,
|
excludeExportDetails: true,
|
||||||
})
|
})
|
||||||
.expect(400)
|
.expect(400);
|
||||||
.then((resp) => {
|
const { attributes, ...error } = resp.body;
|
||||||
const { attributes, ...error } = resp.body;
|
expect(error).to.eql({
|
||||||
expect(error).to.eql({
|
error: 'Bad Request',
|
||||||
error: 'Bad Request',
|
message: 'Error transforming objects to export',
|
||||||
message: 'Error transforming objects to export',
|
statusCode: 400,
|
||||||
statusCode: 400,
|
});
|
||||||
});
|
expect(attributes.cause).to.eql('Error during transform');
|
||||||
expect(attributes.cause).to.eql('Error during transform');
|
expect(attributes.objects.map((obj: any) => obj.id)).to.eql(['type_4-obj_1']);
|
||||||
expect(attributes.objects.map((obj: any) => obj.id)).to.eql(['type_4-obj_1']);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a 400 when the type causes an invalid transform', async () => {
|
it('returns a 400 when the type causes an invalid transform', async () => {
|
||||||
await supertest
|
const resp = await supertest
|
||||||
.post('/api/saved_objects/_export')
|
.post('/api/saved_objects/_export')
|
||||||
.set(svlCommonApi.getCommonRequestHeader())
|
.set(svlCommonApi.getCommonRequestHeader())
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(svlCommonApi.getInternalRequestHeader())
|
||||||
|
@ -139,17 +132,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
type: ['test-export-invalid-transform'],
|
type: ['test-export-invalid-transform'],
|
||||||
excludeExportDetails: true,
|
excludeExportDetails: true,
|
||||||
})
|
})
|
||||||
.expect(400)
|
.expect(400);
|
||||||
.then((resp) => {
|
expect(resp.body).to.eql({
|
||||||
expect(resp.body).to.eql({
|
error: 'Bad Request',
|
||||||
error: 'Bad Request',
|
message: 'Invalid transform performed on objects to export',
|
||||||
message: 'Invalid transform performed on objects to export',
|
statusCode: 400,
|
||||||
statusCode: 400,
|
attributes: {
|
||||||
attributes: {
|
objectKeys: ['test-export-invalid-transform|type_3-obj_1'],
|
||||||
objectKeys: ['test-export-invalid-transform|type_3-obj_1'],
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -172,7 +163,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('execute export transforms for reference objects', async () => {
|
it('execute export transforms for reference objects', async () => {
|
||||||
await supertest
|
const resp = await supertest
|
||||||
.post('/api/saved_objects/_export')
|
.post('/api/saved_objects/_export')
|
||||||
.set(svlCommonApi.getCommonRequestHeader())
|
.set(svlCommonApi.getCommonRequestHeader())
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(svlCommonApi.getInternalRequestHeader())
|
||||||
|
@ -186,21 +177,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
includeReferencesDeep: true,
|
includeReferencesDeep: true,
|
||||||
excludeExportDetails: true,
|
excludeExportDetails: true,
|
||||||
})
|
})
|
||||||
.expect(200)
|
.expect(200);
|
||||||
.then((resp) => {
|
const objects = parseNdJson(resp.text).sort((obj1, obj2) => obj1.id.localeCompare(obj2.id));
|
||||||
const objects = parseNdJson(resp.text).sort((obj1, obj2) =>
|
expect(objects.map((obj) => obj.id)).to.eql([
|
||||||
obj1.id.localeCompare(obj2.id)
|
'type_1-obj_1',
|
||||||
);
|
'type_1-obj_2',
|
||||||
expect(objects.map((obj) => obj.id)).to.eql([
|
'type_2-obj_1',
|
||||||
'type_1-obj_1',
|
'type_dep-obj_1',
|
||||||
'type_1-obj_2',
|
]);
|
||||||
'type_2-obj_1',
|
|
||||||
'type_dep-obj_1',
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(objects[0].attributes.enabled).to.eql(false);
|
expect(objects[0].attributes.enabled).to.eql(false);
|
||||||
expect(objects[1].attributes.enabled).to.eql(false);
|
expect(objects[1].attributes.enabled).to.eql(false);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -223,7 +210,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only export objects returning `true` for `isExportable`', async () => {
|
it('should only export objects returning `true` for `isExportable`', async () => {
|
||||||
await supertest
|
const resp = await supertest
|
||||||
.post('/api/saved_objects/_export')
|
.post('/api/saved_objects/_export')
|
||||||
.set(svlCommonApi.getCommonRequestHeader())
|
.set(svlCommonApi.getCommonRequestHeader())
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(svlCommonApi.getInternalRequestHeader())
|
||||||
|
@ -237,21 +224,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
includeReferencesDeep: true,
|
includeReferencesDeep: true,
|
||||||
excludeExportDetails: true,
|
excludeExportDetails: true,
|
||||||
})
|
})
|
||||||
.expect(200)
|
.expect(200);
|
||||||
.then((resp) => {
|
const objects = parseNdJson(resp.text).sort((obj1, obj2) => obj1.id.localeCompare(obj2.id));
|
||||||
const objects = parseNdJson(resp.text).sort((obj1, obj2) =>
|
expect(objects.map((obj) => `${obj.type}:${obj.id}`)).to.eql([
|
||||||
obj1.id.localeCompare(obj2.id)
|
'test-is-exportable:1',
|
||||||
);
|
'test-is-exportable:3',
|
||||||
expect(objects.map((obj) => `${obj.type}:${obj.id}`)).to.eql([
|
'test-is-exportable:5',
|
||||||
'test-is-exportable:1',
|
]);
|
||||||
'test-is-exportable:3',
|
|
||||||
'test-is-exportable:5',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('lists objects that got filtered', async () => {
|
it('lists objects that got filtered', async () => {
|
||||||
await supertest
|
const resp = await supertest
|
||||||
.post('/api/saved_objects/_export')
|
.post('/api/saved_objects/_export')
|
||||||
.set(svlCommonApi.getCommonRequestHeader())
|
.set(svlCommonApi.getCommonRequestHeader())
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(svlCommonApi.getInternalRequestHeader())
|
||||||
|
@ -265,31 +248,29 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
includeReferencesDeep: true,
|
includeReferencesDeep: true,
|
||||||
excludeExportDetails: false,
|
excludeExportDetails: false,
|
||||||
})
|
})
|
||||||
.expect(200)
|
.expect(200);
|
||||||
.then((resp) => {
|
const objects = parseNdJson(resp.text);
|
||||||
const objects = parseNdJson(resp.text);
|
const exportDetails = objects[
|
||||||
const exportDetails = objects[
|
objects.length - 1
|
||||||
objects.length - 1
|
] as unknown as SavedObjectsExportResultDetails;
|
||||||
] as unknown as SavedObjectsExportResultDetails;
|
|
||||||
|
|
||||||
expect(exportDetails.excludedObjectsCount).to.eql(2);
|
expect(exportDetails.excludedObjectsCount).to.eql(2);
|
||||||
expect(exportDetails.excludedObjects).to.eql([
|
expect(exportDetails.excludedObjects).to.eql([
|
||||||
{
|
{
|
||||||
type: 'test-is-exportable',
|
type: 'test-is-exportable',
|
||||||
id: '2',
|
id: '2',
|
||||||
reason: 'excluded',
|
reason: 'excluded',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'test-is-exportable',
|
type: 'test-is-exportable',
|
||||||
id: '4',
|
id: '4',
|
||||||
reason: 'excluded',
|
reason: 'excluded',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('excludes objects if `isExportable` throws', async () => {
|
it('excludes objects if `isExportable` throws', async () => {
|
||||||
await supertest
|
const resp = await supertest
|
||||||
.post('/api/saved_objects/_export')
|
.post('/api/saved_objects/_export')
|
||||||
.set(svlCommonApi.getCommonRequestHeader())
|
.set(svlCommonApi.getCommonRequestHeader())
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(svlCommonApi.getInternalRequestHeader())
|
||||||
|
@ -307,24 +288,20 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
includeReferencesDeep: true,
|
includeReferencesDeep: true,
|
||||||
excludeExportDetails: false,
|
excludeExportDetails: false,
|
||||||
})
|
})
|
||||||
.expect(200)
|
.expect(200);
|
||||||
.then((resp) => {
|
const objects = parseNdJson(resp.text);
|
||||||
const objects = parseNdJson(resp.text);
|
expect(objects.length).to.eql(2);
|
||||||
expect(objects.length).to.eql(2);
|
expect([objects[0]].map((obj) => `${obj.type}:${obj.id}`)).to.eql(['test-is-exportable:5']);
|
||||||
expect([objects[0]].map((obj) => `${obj.type}:${obj.id}`)).to.eql([
|
const exportDetails = objects[
|
||||||
'test-is-exportable:5',
|
objects.length - 1
|
||||||
]);
|
] as unknown as SavedObjectsExportResultDetails;
|
||||||
const exportDetails = objects[
|
expect(exportDetails.excludedObjects).to.eql([
|
||||||
objects.length - 1
|
{
|
||||||
] as unknown as SavedObjectsExportResultDetails;
|
type: 'test-is-exportable',
|
||||||
expect(exportDetails.excludedObjects).to.eql([
|
id: 'error',
|
||||||
{
|
reason: 'predicate_error',
|
||||||
type: 'test-is-exportable',
|
},
|
||||||
id: 'error',
|
]);
|
||||||
reason: 'predicate_error',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,37 +44,35 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
await kibanaServer.savedObjects.cleanStandardList();
|
await kibanaServer.savedObjects.cleanStandardList();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns saved objects with importableAndExportable types', async () =>
|
it('returns saved objects with importableAndExportable types', async () => {
|
||||||
await supertest
|
const resp = await supertest
|
||||||
.get('/api/kibana/management/saved_objects/_find?type=test-hidden-importable-exportable')
|
.get('/api/kibana/management/saved_objects/_find?type=test-hidden-importable-exportable')
|
||||||
.set(svlCommonApi.getCommonRequestHeader())
|
.set(svlCommonApi.getCommonRequestHeader())
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(svlCommonApi.getInternalRequestHeader())
|
||||||
.expect(200)
|
.expect(200);
|
||||||
.then((resp) => {
|
expect(
|
||||||
expect(
|
resp.body.saved_objects.map((so: { id: string; type: string }) => ({
|
||||||
resp.body.saved_objects.map((so: { id: string; type: string }) => ({
|
id: so.id,
|
||||||
id: so.id,
|
type: so.type,
|
||||||
type: so.type,
|
}))
|
||||||
}))
|
).to.eql([
|
||||||
).to.eql([
|
{
|
||||||
{
|
type: 'test-hidden-importable-exportable',
|
||||||
type: 'test-hidden-importable-exportable',
|
id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab',
|
||||||
id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab',
|
},
|
||||||
},
|
]);
|
||||||
]);
|
});
|
||||||
}));
|
|
||||||
|
|
||||||
it('returns empty response for non importableAndExportable types', async () =>
|
it('returns empty response for non importableAndExportable types', async () => {
|
||||||
await supertest
|
const resp = await supertest
|
||||||
.get(
|
.get(
|
||||||
'/api/kibana/management/saved_objects/_find?type=test-hidden-non-importable-exportable'
|
'/api/kibana/management/saved_objects/_find?type=test-hidden-non-importable-exportable'
|
||||||
)
|
)
|
||||||
.set(svlCommonApi.getCommonRequestHeader())
|
.set(svlCommonApi.getCommonRequestHeader())
|
||||||
.set(svlCommonApi.getInternalRequestHeader())
|
.set(svlCommonApi.getInternalRequestHeader())
|
||||||
.expect(200)
|
.expect(200);
|
||||||
.then((resp) => {
|
expect(resp.body.saved_objects).to.eql([]);
|
||||||
expect(resp.body.saved_objects).to.eql([]);
|
});
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,13 @@ import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
|
||||||
import { SupertestProvider } from './supertest';
|
import { SupertestProvider } from './supertest';
|
||||||
import { SvlCommonApiServiceProvider } from './svl_common_api';
|
import { SvlCommonApiServiceProvider } from './svl_common_api';
|
||||||
import { SvlReportingServiceProvider } from './svl_reporting';
|
import { SvlReportingServiceProvider } from './svl_reporting';
|
||||||
import { SvlUserManagerProvider } from './svl_user_manager';
|
|
||||||
import { DataViewApiProvider } from './data_view_api';
|
import { DataViewApiProvider } from './data_view_api';
|
||||||
|
|
||||||
export type { RoleCredentials } from './svl_user_manager';
|
export type {
|
||||||
export type { InternalRequestHeader } from './svl_common_api';
|
InternalRequestHeader,
|
||||||
export type { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services';
|
RoleCredentials,
|
||||||
|
SupertestWithoutAuthProviderType,
|
||||||
|
} from '@kbn/ftr-common-functional-services';
|
||||||
|
|
||||||
const SupertestWithoutAuthProvider = commonFunctionalServices.supertestWithoutAuth;
|
const SupertestWithoutAuthProvider = commonFunctionalServices.supertestWithoutAuth;
|
||||||
|
|
||||||
|
@ -23,6 +24,6 @@ export const services = {
|
||||||
supertestWithoutAuth: SupertestWithoutAuthProvider,
|
supertestWithoutAuth: SupertestWithoutAuthProvider,
|
||||||
svlCommonApi: SvlCommonApiServiceProvider,
|
svlCommonApi: SvlCommonApiServiceProvider,
|
||||||
svlReportingApi: SvlReportingServiceProvider,
|
svlReportingApi: SvlReportingServiceProvider,
|
||||||
svlUserManager: SvlUserManagerProvider,
|
svlUserManager: commonFunctionalServices.samlAuth,
|
||||||
dataViewApi: DataViewApiProvider,
|
dataViewApi: DataViewApiProvider,
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,10 +22,11 @@ export type InternalRequestHeader = typeof INTERNAL_REQUEST_HEADERS;
|
||||||
|
|
||||||
export function SvlCommonApiServiceProvider({}: FtrProviderContext) {
|
export function SvlCommonApiServiceProvider({}: FtrProviderContext) {
|
||||||
return {
|
return {
|
||||||
|
// call it from 'samlAuth' service when tests are migrated to deployment-agnostic
|
||||||
getCommonRequestHeader() {
|
getCommonRequestHeader() {
|
||||||
return COMMON_REQUEST_HEADERS;
|
return COMMON_REQUEST_HEADERS;
|
||||||
},
|
},
|
||||||
|
// call it from 'samlAuth' service when tests are migrated to deployment-agnostic
|
||||||
getInternalRequestHeader(): InternalRequestHeader {
|
getInternalRequestHeader(): InternalRequestHeader {
|
||||||
return INTERNAL_REQUEST_HEADERS;
|
return INTERNAL_REQUEST_HEADERS;
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,8 +11,8 @@ import type { ReportingJobResponse } from '@kbn/reporting-plugin/server/types';
|
||||||
import { REPORTING_DATA_STREAM_WILDCARD_WITH_LEGACY } from '@kbn/reporting-server';
|
import { REPORTING_DATA_STREAM_WILDCARD_WITH_LEGACY } from '@kbn/reporting-server';
|
||||||
import rison from '@kbn/rison';
|
import rison from '@kbn/rison';
|
||||||
import { FtrProviderContext } from '../../functional/ftr_provider_context';
|
import { FtrProviderContext } from '../../functional/ftr_provider_context';
|
||||||
import { RoleCredentials } from './svl_user_manager';
|
import { RoleCredentials } from '.';
|
||||||
import { InternalRequestHeader } from './svl_common_api';
|
import { InternalRequestHeader } from '.';
|
||||||
|
|
||||||
const API_HEADER: [string, string] = ['kbn-xsrf', 'reporting'];
|
const API_HEADER: [string, string] = ['kbn-xsrf', 'reporting'];
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue