From a2b973c6a5a6dc9ed1fdb5f02e0d00ee33856552 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Fri, 18 Oct 2024 14:17:13 +0200 Subject: [PATCH] [FTR] enable roles management testing for Observability project (#196514) ## Summary This PR makes changes in FTR `saml_auth` service to allow creating custom role for Oblt serverless project, when roles management is explicitly enabled with `--xpack.security.roleManagementEnabled=true` in Kibana server arguments. I also added [role_management/custom_role_access.ts ](x-pack/test_serverless/functional/test_suites/observability/role_management/custom_role_access.ts) as a test example. Currently roles management is enabled in `x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts` and after this PR is merged, more tests with custom roles can be added for Oblt project. How to run tests: ``` node scripts/functional_tests --config x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts ``` (cherry picked from commit 16c965f853f17565e2da996b1f2ab21e9e33a003) --- .../services/saml_auth/saml_auth_provider.ts | 26 ++++- .../saml_auth/serverless/auth_provider.ts | 18 +++- x-pack/test_serverless/README.md | 8 +- .../observability/index.feature_flags.ts | 1 + .../role_management/custom_role_access.ts | 100 ++++++++++++++++++ .../observability/role_management/index.ts | 14 +++ .../test_suites/search/custom_role_access.ts | 2 + 7 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 x-pack/test_serverless/functional/test_suites/observability/role_management/custom_role_access.ts create mode 100644 x-pack/test_serverless/functional/test_suites/observability/role_management/index.ts diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts index 5723dca7b339b..efc86f85213c0 100644 --- a/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts @@ -33,7 +33,7 @@ export interface KibanaRoleDescriptors { } const throwIfRoleNotSet = (role: string, customRole: string, roleDescriptors: Map) => { - if (role === customRole && !roleDescriptors.has(customRole)) { + if (role === customRole && !roleDescriptors.get(customRole)) { throw new Error( `Set privileges for '${customRole}' using 'samlAuth.setCustomRole' before authentication.` ); @@ -179,7 +179,7 @@ export function SamlAuthProvider({ getService }: FtrProviderContext) { if (!isCustomRoleEnabled) { throw new Error(`Custom roles are not supported for the current deployment`); } - log.debug(`Updating role ${CUSTOM_ROLE}`); + log.debug(`Updating role '${CUSTOM_ROLE}'`); const adminCookieHeader = await getAdminCredentials(); const customRoleDescriptors = { @@ -199,6 +199,28 @@ export function SamlAuthProvider({ getService }: FtrProviderContext) { supportedRoleDescriptors.set(CUSTOM_ROLE, customRoleDescriptors); }, + async deleteCustomRole() { + if (!isCustomRoleEnabled) { + throw new Error(`Custom roles are not supported for the current deployment`); + } + + if (supportedRoleDescriptors.get(CUSTOM_ROLE)) { + log.debug(`Deleting role '${CUSTOM_ROLE}'`); + const adminCookieHeader = await getAdminCredentials(); + + // Resetting descriptors for the custom role, even if role deletion fails + supportedRoleDescriptors.set(CUSTOM_ROLE, null); + log.debug(`'${CUSTOM_ROLE}' descriptors were reset`); + + const { status } = await supertestWithoutAuth + .delete(`/api/security/role/${CUSTOM_ROLE}`) + .set(INTERNAL_REQUEST_HEADERS) + .set(adminCookieHeader); + + expect(status).to.be(204); + } + }, + getCommonRequestHeader() { return COMMON_REQUEST_HEADERS; }, diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts index 25038a3cfa17b..16c2dc9cfa844 100644 --- a/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts @@ -34,8 +34,18 @@ const getDefaultServerlessRole = (projectType: string) => { } }; +const isRoleManagementExplicitlyEnabled = (args: string[]): boolean => { + const roleManagementArg = args.find((arg) => + arg.startsWith('--xpack.security.roleManagementEnabled=') + ); + + // Return true if the value is explicitly set to 'true', otherwise false + return roleManagementArg?.split('=')[1] === 'true' || false; +}; + export class ServerlessAuthProvider implements AuthProvider { private readonly projectType: string; + private readonly roleManagementEnabled: boolean; private readonly rolesDefinitionPath: string; constructor(config: Config) { @@ -45,6 +55,10 @@ export class ServerlessAuthProvider implements AuthProvider { return acc + (match ? match[1] : ''); }, '') as ServerlessProjectType; + // Indicates whether role management was explicitly enabled using + // the `--xpack.security.roleManagementEnabled=true` flag. + this.roleManagementEnabled = isRoleManagementExplicitlyEnabled(kbnServerArgs); + if (!isServerlessProjectType(this.projectType)) { throw new Error(`Unsupported serverless projectType: ${this.projectType}`); } @@ -70,7 +84,9 @@ export class ServerlessAuthProvider implements AuthProvider { } isCustomRoleEnabled() { - return projectTypesWithCustomRolesEnabled.includes(this.projectType); + return ( + projectTypesWithCustomRolesEnabled.includes(this.projectType) || this.roleManagementEnabled + ); } getCustomRole() { diff --git a/x-pack/test_serverless/README.md b/x-pack/test_serverless/README.md index 3f8b4fe692de0..44f871273aca2 100644 --- a/x-pack/test_serverless/README.md +++ b/x-pack/test_serverless/README.md @@ -207,6 +207,8 @@ describe("my internal APIs test suite", async function() { With custom native roles now enabled for the Security and Search projects on MKI, the FTR supports defining and authenticating with custom roles in both UI functional tests and API integration tests. +To test role management within the Observability project, you can execute the tests using the existing [config.feature_flags.ts](x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts), where this functionality is explicitly enabled. Though the config is not run on MKI, it provides the ability to test custom roles in Kibana CI before the functionality is enabled in MKI. When roles management is enabled on MKI, these tests can be migrated to the regular FTR config and will be run on MKI. + For compatibility with MKI, the role name `customRole` is reserved for use in tests. The test user is automatically assigned to this role, but before logging in via the browser, generating a cookie header, or creating an API key in each test suite, the role’s privileges must be updated. Note: We are still working on a solution to run these tests against MKI. In the meantime, please tag the suite with `skipMKI`. @@ -229,6 +231,9 @@ await samlAuth.setCustomRole({ }); // Then, log in via the browser as a user with the newly defined privileges await pageObjects.svlCommonPage.loginWithCustomRole(); + +// Make sure to delete the custom role in the 'after' hook +await samlAuth.deleteCustomRole(); ``` FTR api_integration test example: @@ -251,8 +256,9 @@ await samlAuth.setCustomRole({ // Then, generate an API key with the newly defined privileges const roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('customRole'); -// Remember to invalidate the API key after use +// Remember to invalidate the API key after use and delete the custom role await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); +await samlAuth.deleteCustomRole(); ``` ### Testing with feature flags diff --git a/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts index 955d839a38d26..1f087233b52e9 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('serverless observability UI - feature flags', function () { // add tests that require feature flags, defined in config.feature_flags.ts + loadTestFile(require.resolve('./role_management')); loadTestFile(require.resolve('./infra')); loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts')); loadTestFile(require.resolve('../common/platform_security/roles.ts')); diff --git a/x-pack/test_serverless/functional/test_suites/observability/role_management/custom_role_access.ts b/x-pack/test_serverless/functional/test_suites/observability/role_management/custom_role_access.ts new file mode 100644 index 0000000000000..2db9e4c5d3b16 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/role_management/custom_role_access.ts @@ -0,0 +1,100 @@ +/* + * 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 { FtrProviderContext } from '../../../ftr_provider_context'; +import { RoleCredentials } from '../../../../shared/services'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects(['svlCommonPage', 'timePicker', 'common', 'header']); + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const testSubjects = getService('testSubjects'); + let roleAuthc: RoleCredentials; + + describe('With custom role', function () { + // skipping on MKI while we are working on a solution + this.tags(['skipMKI']); + before(async () => { + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await kibanaServer.uiSettings.update({ + defaultIndex: 'logstash-*', + }); + await samlAuth.setCustomRole({ + elasticsearch: { + indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + feature: { + discover: ['read'], + }, + spaces: ['*'], + }, + ], + }); + // login with custom role + await pageObjects.svlCommonPage.loginWithCustomRole(); + await pageObjects.svlCommonPage.assertUserAvatarExists(); + }); + + after(async () => { + await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await kibanaServer.uiSettings.replace({}); + await kibanaServer.savedObjects.cleanStandardList(); + if (roleAuthc) { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + } + // delete custom role + await samlAuth.deleteCustomRole(); + }); + + it('should have limited navigation menu', async () => { + await pageObjects.svlCommonPage.assertUserAvatarExists(); + // discover navigation link is present + await testSubjects.existOrFail('~nav-item-id-last-used-logs-viewer'); + + // all other links in navigation menu are hidden + await testSubjects.missingOrFail('~nav-item-id-dashboards'); + await testSubjects.missingOrFail('~nav-item-id-observability-overview:alerts'); + await testSubjects.missingOrFail('~nav-item-id-observability-overview:cases'); + await testSubjects.missingOrFail('~nav-item-id-slo'); + await testSubjects.missingOrFail('~nav-item-id-aiops'); + await testSubjects.missingOrFail('~nav-item-id-inventory'); + await testSubjects.missingOrFail('~nav-item-id-apm'); + await testSubjects.missingOrFail('~nav-item-id-metrics'); + await testSubjects.missingOrFail('~nav-item-id-synthetics'); + + // TODO: 'Add data' and 'Project Settings' should be hidden + // await testSubjects.missingOrFail('~nav-item-id-observabilityOnboarding'); + // await testSubjects.missingOrFail('~nav-item-id-project_settings_project_nav'); + }); + + it('should access Discover app', async () => { + await pageObjects.common.navigateToApp('discover'); + await pageObjects.timePicker.setDefaultAbsoluteRange(); + await pageObjects.header.waitUntilLoadingHasFinished(); + expect(await testSubjects.exists('unifiedHistogramChart')).to.be(true); + expect(await testSubjects.exists('discoverQueryHits')).to.be(true); + }); + + it('should access console with API key', async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('customRole'); + const { body } = await supertestWithoutAuth + .get('/api/console/api_server') + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .set({ 'kbn-xsrf': 'true' }) + .expect(200); + expect(body.es).to.be.ok(); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/role_management/index.ts b/x-pack/test_serverless/functional/test_suites/observability/role_management/index.ts new file mode 100644 index 0000000000000..063f1c8c8cc2c --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/role_management/index.ts @@ -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 { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Role Management', function () { + loadTestFile(require.resolve('./custom_role_access')); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/search/custom_role_access.ts b/x-pack/test_serverless/functional/test_suites/search/custom_role_access.ts index 6e28289d4fb00..7a7931d5f6eea 100644 --- a/x-pack/test_serverless/functional/test_suites/search/custom_role_access.ts +++ b/x-pack/test_serverless/functional/test_suites/search/custom_role_access.ts @@ -53,6 +53,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { if (roleAuthc) { await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); } + // delete custom role + await samlAuth.deleteCustomRole(); }); it('should have limited navigation menu', async () => {