Config to toggle Custom Roles (#176200)

## Summary

Closes https://github.com/elastic/kibana/issues/174771

While the security team works on Custom Roles for serverless, we want to
hide the content behind a feature flag.

An existing config option that was used to hide the Roles UI during the
initial phases of serverless has been repurposed, and will now toggle
both the Roles UI and the Roles Routes

`xpack.security.confg.ui.roleManagementEnabled` has been changed to
`xpack.security.confg.roleManagementEnabled` and will have to be set to
`true` in a config file while in serverless mode to show the Roles card
on the management screen and enable the UI/routes.

## Reviewers

Ive included a `viewer`:`changeme` user for testing (It will be removed
after approval).

## Testing

### xpack.security.config.roleManagementEnabled

1. In your `kibana.yml`, add
`xpack.security.confg.roleManagementEnabled: true`
2. Start up in serverless mode locally, login in with
`elastic_serverless`:`changeme`
3. Click `Project Settings` > `Management`
4. `Roles` card should display under `Other`
5. Navigate to `Roles`, it displays, but the `Edit Roles` page does not
work yet.

### Test as Viewer

1. In your `kibana.yml`, add either above option as you prefer 
2. Start up in serverless mode locally, login in with
`viewer`:`changeme`
3. Click `Project Settings` > `Management`
4. `Roles` card should NOT display under `Other` and the `roles` URL
should not work.

## Screenshots

Roles card
<img width="1281" alt="Screenshot 2024-02-05 at 3 22 12 PM"
src="a1285ada-7ff7-495f-88a6-9847b3245518">

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Aleh Zasypkin <aleh.zasypkin@gmail.com>
This commit is contained in:
Kurt 2024-02-20 18:40:49 -05:00 committed by GitHub
parent f37d1dd050
commit 35656542eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 314 additions and 148 deletions

View file

@ -64,7 +64,6 @@ xpack.security.showInsecureClusterWarning: false
# Disable UI of security management plugins
xpack.security.ui.userManagementEnabled: false
xpack.security.ui.roleManagementEnabled: false
xpack.security.ui.roleMappingManagementEnabled: false
# Enforce restring access to internal APIs see https://github.com/elastic/kibana/issues/151940

View file

@ -1,6 +1,5 @@
superuser:elastic_serverless
system_indices_superuser:system_indices_superuser
t1_analyst:t1_analyst
t2_analyst:t2_analyst
t3_analyst:t3_analyst

View file

@ -134,4 +134,12 @@ export const appDefinitions: Record<AppId, AppDefinition> = {
}),
icon: 'gear',
},
[AppIds.ROLES]: {
category: appCategories.OTHER,
description: i18n.translate('management.landing.withCardNavigation.rolesDescription', {
defaultMessage: 'Allow custom roles to be created for users.',
}),
icon: 'usersRolesApp',
},
};

View file

@ -27,6 +27,7 @@ export enum AppIds {
RULES = 'triggersActions',
MAINTENANCE_WINDOWS = 'maintenanceWindows',
SERVERLESS_SETTINGS = 'settings',
ROLES = 'roles',
}
// Create new type that is a union of all the appId values

View file

@ -311,6 +311,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.security.showInsecureClusterWarning (boolean)',
'xpack.security.showNavLinks (boolean)',
'xpack.security.ui (any)',
'xpack.security.roleManagementEnabled (any)',
'xpack.spaces.maxSpaces (number)',
'xpack.spaces.allowFeatureVisibility (any)',
'xpack.securitySolution.enableExperimental (array)',
@ -374,6 +375,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.security.showInsecureClusterWarning (boolean)',
'xpack.security.showNavLinks (boolean)',
'xpack.security.ui (any)',
'xpack.security.roleManagementEnabled (any)',
'telemetry.allowChangingOptInStatus (boolean)',
'telemetry.appendServerlessChannelsSuffix (any)', // It's a boolean (any because schema.conditional)

View file

@ -11,7 +11,7 @@ export interface ConfigType {
sameSiteCookies: 'Strict' | 'Lax' | 'None' | undefined;
ui: {
userManagementEnabled: boolean;
roleManagementEnabled: boolean;
roleMappingManagementEnabled: boolean;
};
roleManagementEnabled: boolean | undefined;
}

View file

@ -16,13 +16,13 @@ import type {
import { createManagementSectionMock } from '@kbn/management-plugin/public/mocks';
import { apiKeysManagementApp } from './api_keys';
import type { ManagementAppConfigType } from './management_service';
import { ManagementService } from './management_service';
import { roleMappingsManagementApp } from './role_mappings';
import { rolesManagementApp } from './roles';
import { usersManagementApp } from './users';
import type { SecurityLicenseFeatures } from '../../common';
import { licenseMock } from '../../common/licensing/index.mock';
import type { ConfigType } from '../config';
import { securityMock } from '../mocks';
const mockSection = createManagementSectionMock();
@ -44,7 +44,7 @@ describe('ManagementService', () => {
locator: {} as any,
};
const service = new ManagementService();
const service = new ManagementService({} as unknown as ConfigType);
service.setup({
getStartServices: getStartServices as any,
license,
@ -80,7 +80,7 @@ describe('ManagementService', () => {
});
});
it('Users, Roles, and Role Mappings are not registered when their UI config settings are set to false', () => {
it('Users, Roles, and Role Mappings are not registered when their config settings are set to false', () => {
const mockSectionWithConfig = createManagementSectionMock();
const { fatalErrors, getStartServices } = coreMock.createSetup();
const { authc } = securityMock.createSetup();
@ -96,20 +96,21 @@ describe('ManagementService', () => {
locator: {} as any,
};
const uiConfig: ManagementAppConfigType = {
userManagementEnabled: false,
const config = {
ui: {
userManagementEnabled: false,
roleMappingManagementEnabled: false,
},
roleManagementEnabled: false,
roleMappingManagementEnabled: false,
};
} as unknown as ConfigType;
const service = new ManagementService();
const service = new ManagementService(config);
service.setup({
getStartServices: getStartServices as any,
license,
fatalErrors,
authc,
management: managementSetup,
uiConfig,
});
// Only API Keys app should be registered
@ -154,7 +155,15 @@ describe('ManagementService', () => {
const license = licenseMock.create();
license.features$ = licenseSubject;
const service = new ManagementService();
const config = {
ui: {
userManagementEnabled: true,
roleMappingManagementEnabled: true,
},
roleManagementEnabled: true,
} as unknown as ConfigType;
const service = new ManagementService(config);
const managementSetup: ManagementSetup = {
sections: {
@ -166,19 +175,12 @@ describe('ManagementService', () => {
locator: {} as any,
};
const uiConfig: ManagementAppConfigType = {
userManagementEnabled: true,
roleManagementEnabled: true,
roleMappingManagementEnabled: true,
};
service.setup({
getStartServices: getStartServices as any,
license,
fatalErrors,
authc: securityMock.createSetup().authc,
management: managementSetup,
uiConfig,
});
const getMockedApp = (id: string) => {
@ -218,7 +220,6 @@ describe('ManagementService', () => {
navLinks: {},
catalogue: {},
},
uiConfig,
});
return {

View file

@ -20,12 +20,13 @@ import { roleMappingsManagementApp } from './role_mappings';
import { rolesManagementApp } from './roles';
import { usersManagementApp } from './users';
import type { SecurityLicense } from '../../common';
import type { ConfigType } from '../config';
import type { PluginStartDependencies } from '../plugin';
export interface ManagementAppConfigType {
userManagementEnabled: boolean;
roleManagementEnabled: boolean;
roleMappingManagementEnabled: boolean;
userManagementEnabled?: boolean;
roleManagementEnabled?: boolean;
roleMappingManagementEnabled?: boolean;
}
interface SetupParams {
@ -34,38 +35,48 @@ interface SetupParams {
authc: AuthenticationServiceSetup;
fatalErrors: FatalErrorsSetup;
getStartServices: StartServicesAccessor<PluginStartDependencies>;
uiConfig?: ManagementAppConfigType;
}
interface StartParams {
capabilities: Capabilities;
uiConfig?: ManagementAppConfigType;
}
export class ManagementService {
private license!: SecurityLicense;
private licenseFeaturesSubscription?: Subscription;
private securitySection?: ManagementSection;
private readonly userManagementEnabled: boolean;
private readonly roleManagementEnabled: boolean;
private readonly roleMappingManagementEnabled: boolean;
setup({ getStartServices, management, authc, license, fatalErrors, uiConfig }: SetupParams) {
constructor(config: ConfigType) {
this.userManagementEnabled = config.ui?.userManagementEnabled !== false;
this.roleManagementEnabled = config.roleManagementEnabled !== false;
this.roleMappingManagementEnabled = config.ui?.roleMappingManagementEnabled !== false;
}
setup({ getStartServices, management, authc, license, fatalErrors }: SetupParams) {
this.license = license;
this.securitySection = management.sections.section.security;
if (!uiConfig || uiConfig.userManagementEnabled) {
if (this.userManagementEnabled) {
this.securitySection.registerApp(usersManagementApp.create({ authc, getStartServices }));
}
if (!uiConfig || uiConfig.roleManagementEnabled) {
if (this.roleManagementEnabled) {
this.securitySection.registerApp(
rolesManagementApp.create({ fatalErrors, license, getStartServices })
);
}
this.securitySection.registerApp(apiKeysManagementApp.create({ authc, getStartServices }));
if (!uiConfig || uiConfig.roleMappingManagementEnabled) {
if (this.roleMappingManagementEnabled) {
this.securitySection.registerApp(roleMappingsManagementApp.create({ getStartServices }));
}
}
start({ capabilities, uiConfig }: StartParams) {
start({ capabilities }: StartParams) {
this.licenseFeaturesSubscription = this.license.features$.subscribe((features) => {
const securitySection = this.securitySection!;
@ -73,21 +84,21 @@ export class ManagementService {
[securitySection.getApp(apiKeysManagementApp.id)!, features.showLinks],
];
if (!uiConfig || uiConfig.userManagementEnabled) {
if (this.userManagementEnabled) {
securityManagementAppsStatuses.push([
securitySection.getApp(usersManagementApp.id)!,
features.showLinks,
]);
}
if (!uiConfig || uiConfig.roleManagementEnabled) {
if (this.roleManagementEnabled) {
securityManagementAppsStatuses.push([
securitySection.getApp(rolesManagementApp.id)!,
features.showLinks,
]);
}
if (!uiConfig || uiConfig.roleMappingManagementEnabled) {
if (this.roleMappingManagementEnabled) {
securityManagementAppsStatuses.push([
securitySection.getApp(roleMappingsManagementApp.id)!,
features.showLinks && features.showRoleMappingsManagement,

View file

@ -73,19 +73,21 @@ export class SecurityPlugin
private readonly authenticationService = new AuthenticationService();
private readonly navControlService;
private readonly securityLicenseService = new SecurityLicenseService();
private readonly managementService = new ManagementService();
private readonly managementService: ManagementService;
private readonly securityCheckupService: SecurityCheckupService;
private readonly anonymousAccessService = new AnonymousAccessService();
private readonly analyticsService = new AnalyticsService();
private authc!: AuthenticationServiceSetup;
private securityApiClients!: SecurityApiClients;
constructor(private readonly initializerContext: PluginInitializerContext) {
this.config = this.initializerContext.config.get<ConfigType>();
this.securityCheckupService = new SecurityCheckupService(this.config, localStorage);
this.navControlService = new SecurityNavControlService(
initializerContext.env.packageInfo.buildFlavor
);
this.managementService = new ManagementService(
this.initializerContext.config.get<ConfigType>()
);
}
public setup(
@ -137,7 +139,6 @@ export class SecurityPlugin
authc: this.authc,
fatalErrors: core.fatalErrors,
getStartServices: core.getStartServices,
uiConfig: this.config.ui,
});
}
@ -188,7 +189,6 @@ export class SecurityPlugin
if (management) {
this.managementService.start({
capabilities: application.capabilities,
uiConfig: this.config.ui,
});
}

View file

@ -226,6 +226,7 @@ describe('config schema', () => {
"cookieName": "sid",
"loginAssistanceMessage": "",
"public": Object {},
"roleManagementEnabled": false,
"secureCookies": false,
"session": Object {
"cleanupInterval": "PT1H",
@ -235,7 +236,6 @@ describe('config schema', () => {
"showInsecureClusterWarning": true,
"showNavLinks": true,
"ui": Object {
"roleManagementEnabled": true,
"roleMappingManagementEnabled": true,
"userManagementEnabled": true,
},
@ -1487,14 +1487,8 @@ describe('config schema', () => {
ConfigSchema.validate(
{ authc: { http: { jwt: { taggedRoutesOnly: false } } } },
{ serverless: true }
).ui
).toMatchInlineSnapshot(`
Object {
"roleManagementEnabled": true,
"roleMappingManagementEnabled": true,
"userManagementEnabled": true,
}
`);
).authc.http.jwt.taggedRoutesOnly
).toEqual(false);
});
});
@ -1505,7 +1499,6 @@ describe('config schema', () => {
{
ui: {
userManagementEnabled: false,
roleManagementEnabled: false,
roleMappingManagementEnabled: false,
},
},
@ -1520,7 +1513,6 @@ describe('config schema', () => {
{
ui: {
userManagementEnabled: false,
roleManagementEnabled: false,
roleMappingManagementEnabled: false,
},
},
@ -1528,7 +1520,6 @@ describe('config schema', () => {
).ui
).toMatchInlineSnapshot(`
Object {
"roleManagementEnabled": false,
"roleMappingManagementEnabled": false,
"userManagementEnabled": false,
}
@ -1536,6 +1527,32 @@ describe('config schema', () => {
});
});
describe('roleManagementEnabled', () => {
it('should not allow xpack.security.roleManagementEnabled to be configured outside of the serverless context', () => {
expect(() =>
ConfigSchema.validate(
{
roleManagementEnabled: false,
},
{ serverless: false }
)
).toThrowErrorMatchingInlineSnapshot(
`"[roleManagementEnabled]: a value wasn't expected to be present"`
);
});
it('should allow xpack.security.roleManagementEnabled to be configured inside of the serverless context', () => {
expect(
ConfigSchema.validate(
{
roleManagementEnabled: false,
},
{ serverless: true }
).roleManagementEnabled
).toEqual(false);
});
});
describe('session', () => {
it('should throw error if xpack.security.session.cleanupInterval is less than 10 seconds', () => {
expect(() => ConfigSchema.validate({ session: { cleanupInterval: '9s' } })).toThrow(

View file

@ -302,11 +302,14 @@ export const ConfigSchema = schema.object({
),
}),
roleManagementEnabled: offeringBasedSchema({
serverless: schema.boolean({ defaultValue: false }),
}),
// Setting only allowed in the Serverless offering
ui: offeringBasedSchema({
serverless: schema.object({
userManagementEnabled: schema.boolean({ defaultValue: true }),
roleManagementEnabled: schema.boolean({ defaultValue: true }),
roleMappingManagementEnabled: schema.boolean({ defaultValue: true }),
}),
}),

View file

@ -88,6 +88,7 @@ export const config: PluginConfigDescriptor<TypeOf<typeof ConfigSchema>> = {
sameSiteCookies: true,
showNavLinks: true,
ui: true,
roleManagementEnabled: true,
},
};
export const plugin: PluginInitializer<

View file

@ -14,12 +14,17 @@ import type { RouteDefinitionParams } from '..';
export function defineAuthorizationRoutes(params: RouteDefinitionParams) {
// The reset session endpoint is registered with httpResources and should remain public in serverless
resetSessionPageRoutes(params);
defineRolesRoutes(params); // Temporarily allow role APIs (ToDo: move to non-serverless block below)
// In the serverless environment, roles, privileges, and permissions are managed internally and only
// exposed to users and administrators via control plane UI, eliminating the need for any public HTTP APIs.
if (params.buildFlavor !== 'serverless') {
// By default, in the serverless environment privileges and permissions are managed internally and only
// exposed to users and administrators via control plane UI, however, we will allow these routes to be registered
// behind a feature flag for development & design of Custom Roles
if (params.buildFlavor !== 'serverless' || params.config.roleManagementEnabled) {
definePrivilegesRoutes(params);
defineRolesRoutes(params);
}
// Shared object permission routes are not currently available in serverless.
if (params.buildFlavor !== 'serverless') {
defineShareSavedObjectPermissionRoutes(params);
}
}

View file

@ -14,86 +14,56 @@ export default function ({ getService }: FtrProviderContext) {
describe('security/authorization', function () {
describe('route access', () => {
describe('disabled', () => {
it('get all privileges', async () => {
const { body, status } = await supertest
.get('/api/security/privileges')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertApiNotFound(body, status);
});
it('get built-in elasticsearch privileges', async () => {
const { body, status } = await supertest
.get('/internal/security/esPrivileges/builtin')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertApiNotFound(body, status);
});
// ToDo: Uncomment when we disable role APIs
// it('create/update role', async () => {
// const { body, status } = await supertest
// .put('/api/security/role/test')
// .set(svlCommonApi.getInternalRequestHeader());
// svlCommonApi.assertApiNotFound(body, status);
// });
// it('get role', async () => {
// const { body, status } = await supertest
// .get('/api/security/role/superuser')
// .set(svlCommonApi.getInternalRequestHeader());
// svlCommonApi.assertApiNotFound(body, status);
// });
// it('get all roles', async () => {
// const { body, status } = await supertest
// .get('/api/security/role')
// .set(svlCommonApi.getInternalRequestHeader());
// svlCommonApi.assertApiNotFound(body, status);
// });
// it('delete role', async () => {
// const { body, status } = await supertest
// .delete('/api/security/role/superuser')
// .set(svlCommonApi.getInternalRequestHeader());
// svlCommonApi.assertApiNotFound(body, status);
// });
it('get shared saved object permissions', async () => {
const { body, status } = await supertest
.get('/internal/security/_share_saved_object_permissions')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertApiNotFound(body, status);
});
});
// ToDo: remove when we disable role APIs
describe('internal', () => {
it('create/update role', async () => {
const { status } = await supertest
.put('/api/security/role/test')
.set(svlCommonApi.getInternalRequestHeader());
expect(status).not.toBe(404);
});
describe('disabled', () => {
it('get all privileges', async () => {
const { body, status } = await supertest
.get('/api/security/privileges')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertApiNotFound(body, status);
});
it('get role', async () => {
const { status } = await supertest
.get('/api/security/role/superuser')
.set(svlCommonApi.getInternalRequestHeader());
expect(status).not.toBe(404);
});
it('get built-in elasticsearch privileges', async () => {
const { body, status } = await supertest
.get('/internal/security/esPrivileges/builtin')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertApiNotFound(body, status);
});
it('get all roles', async () => {
const { status } = await supertest
.get('/api/security/role')
.set(svlCommonApi.getInternalRequestHeader());
expect(status).not.toBe(404);
});
it('create/update role', async () => {
const { body, status } = await supertest
.put('/api/security/role/test')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertApiNotFound(body, status);
});
it('delete role', async () => {
const { status } = await supertest
.delete('/api/security/role/superuser')
.set(svlCommonApi.getInternalRequestHeader());
expect(status).not.toBe(404);
it('get role', async () => {
const { body, status } = await supertest
.get('/api/security/role/superuser')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertApiNotFound(body, status);
});
it('get all roles', async () => {
const { body, status } = await supertest
.get('/api/security/role')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertApiNotFound(body, status);
});
it('delete role', async () => {
const { body, status } = await supertest
.delete('/api/security/role/superuser')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertApiNotFound(body, status);
});
it('get shared saved object permissions', async () => {
const { body, status } = await supertest
.get('/internal/security/_share_saved_object_permissions')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertApiNotFound(body, status);
});
});
});

View file

@ -0,0 +1,63 @@
/*
* 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 ({ getService }: FtrProviderContext) {
const svlCommonApi = getService('svlCommonApi');
const supertest = getService('supertest');
describe('security', function () {
describe('route access', () => {
describe('roles', () => {
describe('enabled', () => {
it('get role', async () => {
const { body, status } = await supertest
.get('/api/security/role/superuser')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertResponseStatusCode(200, status, body);
});
it('get all roles', async () => {
const { body, status } = await supertest
.get('/api/security/role')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertResponseStatusCode(200, status, body);
});
});
describe('moved', () => {
it('delete role', async () => {
const { body, status } = await supertest
.delete('/api/security/role/superuser')
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertResponseStatusCode(410, status, body);
});
it('create/update role', async () => {
const role = {
elasticsearch: {
cluster: [],
indices: [{ names: ['test'], privileges: ['read'] }],
run_as: [],
},
kibana: [],
};
const { body, status } = await supertest
.put('/api/security/role/myRole')
.send(role)
.set(svlCommonApi.getInternalRequestHeader());
svlCommonApi.assertResponseStatusCode(410, status, body);
});
});
});
});
});
}

View file

@ -20,7 +20,7 @@ export default createTestConfig({
suiteTags: { exclude: ['skipSvlOblt'] },
services,
// add feature flags
kbnServerArgs: ['--xpack.infra.enabled=true'],
kbnServerArgs: ['--xpack.infra.enabled=true', '--xpack.security.roleManagementEnabled=true'],
// load tests in the index file
testFiles: [require.resolve('./index.feature_flags.ts')],

View file

@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('Serverless observability API - feature flags', function () {
loadTestFile(require.resolve('./custom_threshold_rule'));
loadTestFile(require.resolve('./infra'));
loadTestFile(require.resolve('../common/platform_security/roles_routes_feature_flag.ts'));
});
}

View file

@ -18,7 +18,7 @@ export default createTestConfig({
},
suiteTags: { exclude: ['skipSvlSearch'] },
// add feature flags
kbnServerArgs: [],
kbnServerArgs: ['--xpack.security.roleManagementEnabled=true'],
// load tests in the index file
testFiles: [require.resolve('./index.feature_flags.ts')],

View file

@ -5,9 +5,10 @@
* 2.0.
*/
export default function () {
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Serverless search API - feature flags', function () {
// add tests that require feature flags, defined in config.feature_flags.ts
// loadTestFile(require.resolve(<path_to_test_file>));
loadTestFile(require.resolve('../common/platform_security/roles_routes_feature_flag.ts'));
});
}

View file

@ -18,7 +18,7 @@ export default createTestConfig({
},
suiteTags: { exclude: ['skipSvlSec'] },
// add feature flags
kbnServerArgs: [],
kbnServerArgs: ['--xpack.security.roleManagementEnabled=true'],
// load tests in the index file
testFiles: [require.resolve('./index.feature_flags.ts')],

View file

@ -5,9 +5,10 @@
* 2.0.
*/
export default function () {
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Serverless security API - feature flags', function () {
// add tests that require feature flags, defined in config.feature_flags.ts
// loadTestFile(require.resolve(<path_to_test_file>));
loadTestFile(require.resolve('../common/platform_security/roles_routes_feature_flag.ts'));
});
}

View file

@ -19,6 +19,7 @@ import { SvlSecLandingPageProvider } from './svl_sec_landing_page';
import { SvlTriggersActionsPageProvider } from './svl_triggers_actions_ui_page';
import { SvlRuleDetailsPageProvider } from './svl_rule_details_ui_page';
import { SvlSearchConnectorsPageProvider } from './svl_search_connectors_page';
import { SvlManagementPageProvider } from './svl_management_page';
export const pageObjects = {
...xpackFunctionalPageObjects,
@ -34,4 +35,5 @@ export const pageObjects = {
svlSecLandingPage: SvlSecLandingPageProvider,
svlTriggersActionsUI: SvlTriggersActionsPageProvider,
svlRuleDetailsUI: SvlRuleDetailsPageProvider,
svlManagementPage: SvlManagementPageProvider,
};

View file

@ -229,7 +229,7 @@ export function SvlCommonPageProvider({ getService, getPageObjects }: FtrProvide
},
async clickUserAvatar() {
testSubjects.click('userMenuAvatar');
await testSubjects.click('userMenuAvatar');
},
async assertUserAvatarExists() {

View file

@ -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 { FtrProviderContext } from '../ftr_provider_context';
export function SvlManagementPageProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async assertRoleManagementCardExists() {
await testSubjects.existOrFail('app-card-roles');
},
async assertRoleManagementCardDoesNotExist() {
await testSubjects.missingOrFail('app-card-roles');
},
async clickRoleManagementCard() {
await testSubjects.click('app-card-roles');
},
};
}

View file

@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects(['svlCommonPage', 'common']);
const pageObjects = getPageObjects(['svlCommonPage', 'common', 'svlManagementPage']);
const browser = getService('browser');
const retry = getService('retry');
@ -36,5 +36,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
return await testSubjects.exists('indexManagementHeaderContent');
});
});
describe('Roles management card', () => {
it('should not be displayed by default', async () => {
await pageObjects.common.navigateToApp('management');
await pageObjects.svlManagementPage.assertRoleManagementCardDoesNotExist();
});
});
});
};

View file

@ -0,0 +1,41 @@
/*
* 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';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects(['svlCommonPage', 'common', 'svlManagementPage']);
const browser = getService('browser');
const retry = getService('retry');
describe('Roles management card', function () {
this.tags('smoke');
before(async () => {
// Navigate to the index management page
await pageObjects.svlCommonPage.loginAsAdmin();
await pageObjects.common.navigateToApp('management');
});
it('renders the page, displays the Roles card, and will navigate to the Roles UI', async () => {
await retry.waitFor('page to be visible', async () => {
return await testSubjects.exists('cards-navigation-page');
});
let url = await browser.getCurrentUrl();
expect(url).to.contain(`/management`);
await pageObjects.svlManagementPage.assertRoleManagementCardExists();
await pageObjects.svlManagementPage.clickRoleManagementCard();
url = await browser.getCurrentUrl();
expect(url).to.contain('/management/security/roles');
});
});
};

View file

@ -21,6 +21,7 @@ export default createTestConfig({
kbnServerArgs: [
'--xpack.infra.enabled=true',
'--xpack.infra.featureFlags.customThresholdAlertsEnabled=true',
'--xpack.security.roleManagementEnabled=true',
],
// load tests in the index file
testFiles: [require.resolve('./index.feature_flags.ts')],

View file

@ -11,5 +11,6 @@ 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('./infra'));
loadTestFile(require.resolve('../common/platform_security/roles_management_card.ts'));
});
}

View file

@ -18,7 +18,7 @@ export default createTestConfig({
},
suiteTags: { exclude: ['skipSvlSearch'] },
// add feature flags
kbnServerArgs: [],
kbnServerArgs: ['--xpack.security.roleManagementEnabled=true'],
// load tests in the index file
testFiles: [require.resolve('./index.feature_flags.ts')],

View file

@ -5,9 +5,11 @@
* 2.0.
*/
export default function () {
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless search UI - feature flags', function () {
// add tests that require feature flags, defined in config.feature_flags.ts
// loadTestFile(require.resolve(<path_to_test_file>));
loadTestFile(require.resolve('../common/platform_security/roles_management_card.ts'));
});
}

View file

@ -18,7 +18,7 @@ export default createTestConfig({
},
suiteTags: { exclude: ['skipSvlSec'] },
// add feature flags
kbnServerArgs: [],
kbnServerArgs: ['--xpack.security.roleManagementEnabled=true'],
// load tests in the index file
testFiles: [require.resolve('./index.feature_flags.ts')],

View file

@ -5,9 +5,11 @@
* 2.0.
*/
export default function () {
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless security UI - feature flags', function () {
// add tests that require feature flags, defined in config.feature_flags.ts
// loadTestFile(require.resolve(<path_to_test_file>));
loadTestFile(require.resolve('../common/platform_security/roles_management_card.ts'));
});
}