Skip to content

Commit

Permalink
Config to toggle Custom Roles (elastic#176200)
Browse files Browse the repository at this point in the history
## Summary

Closes elastic#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="https://github.com/elastic/kibana/assets/21210601/a1285ada-7ff7-495f-88a6-9847b3245518">

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Aleh Zasypkin <[email protected]>
  • Loading branch information
3 people authored Feb 20, 2024
1 parent f37d1dd commit 3565654
Show file tree
Hide file tree
Showing 32 changed files with 312 additions and 146 deletions.
1 change: 0 additions & 1 deletion config/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion packages/kbn-es/src/serverless_resources/users_roles
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 8 additions & 0 deletions packages/kbn-management/cards_navigation/src/consts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
};
1 change: 1 addition & 0 deletions packages/kbn-management/cards_navigation/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions test/plugin_functional/test_suites/core_plugins/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/security/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface ConfigType {
sameSiteCookies: 'Strict' | 'Lax' | 'None' | undefined;
ui: {
userManagementEnabled: boolean;
roleManagementEnabled: boolean;
roleMappingManagementEnabled: boolean;
};
roleManagementEnabled: boolean | undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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,
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down Expand Up @@ -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: {
Expand All @@ -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) => {
Expand Down Expand Up @@ -218,7 +220,6 @@ describe('ManagementService', () => {
navLinks: {},
catalogue: {},
},
uiConfig,
});

return {
Expand Down
37 changes: 24 additions & 13 deletions x-pack/plugins/security/public/management/management_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -34,60 +35,70 @@ 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;

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, uiConfig }: SetupParams) {
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!;

const securityManagementAppsStatuses: Array<[ManagementApp, boolean]> = [
[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,
Expand Down
8 changes: 4 additions & 4 deletions x-pack/plugins/security/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -137,7 +139,6 @@ export class SecurityPlugin
authc: this.authc,
fatalErrors: core.fatalErrors,
getStartServices: core.getStartServices,
uiConfig: this.config.ui,
});
}

Expand Down Expand Up @@ -188,7 +189,6 @@ export class SecurityPlugin
if (management) {
this.managementService.start({
capabilities: application.capabilities,
uiConfig: this.config.ui,
});
}

Expand Down
41 changes: 29 additions & 12 deletions x-pack/plugins/security/server/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ describe('config schema', () => {
"cookieName": "sid",
"loginAssistanceMessage": "",
"public": Object {},
"roleManagementEnabled": false,
"secureCookies": false,
"session": Object {
"cleanupInterval": "PT1H",
Expand All @@ -235,7 +236,6 @@ describe('config schema', () => {
"showInsecureClusterWarning": true,
"showNavLinks": true,
"ui": Object {
"roleManagementEnabled": true,
"roleMappingManagementEnabled": true,
"userManagementEnabled": true,
},
Expand Down Expand Up @@ -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);
});
});

Expand All @@ -1505,7 +1499,6 @@ describe('config schema', () => {
{
ui: {
userManagementEnabled: false,
roleManagementEnabled: false,
roleMappingManagementEnabled: false,
},
},
Expand All @@ -1520,22 +1513,46 @@ describe('config schema', () => {
{
ui: {
userManagementEnabled: false,
roleManagementEnabled: false,
roleMappingManagementEnabled: false,
},
},
{ serverless: true }
).ui
).toMatchInlineSnapshot(`
Object {
"roleManagementEnabled": false,
"roleMappingManagementEnabled": false,
"userManagementEnabled": false,
}
`);
});
});

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(
Expand Down
Loading

0 comments on commit 3565654

Please sign in to comment.