Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Serverless] Disable UI of Users, Roles, and Role Mappings #158186

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.security.sameSiteCookies (alternatives)',
'xpack.security.showInsecureClusterWarning (boolean)',
'xpack.security.showNavLinks (boolean)',
'xpack.security.ui (any)',
'xpack.securitySolution.enableExperimental (array)',
'xpack.securitySolution.prebuiltRulesPackageVersion (string)',
'xpack.snapshot_restore.slm_ui.enabled (boolean)',
Expand Down Expand Up @@ -272,6 +273,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.security.sameSiteCookies (alternatives)',
'xpack.security.showInsecureClusterWarning (boolean)',
'xpack.security.showNavLinks (boolean)',
'xpack.security.ui (any)',
];
// We don't assert that actualExposedConfigKeys and expectedExposedConfigKeys are equal, because test failure messages with large
// arrays are hard to grok. Instead, we take the difference between the two arrays and assert them separately, that way it's
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/security/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ export interface ConfigType {
showInsecureClusterWarning: boolean;
sameSiteCookies: 'Strict' | 'Lax' | 'None' | undefined;
showNavLinks: boolean;
ui: {
userManagementEnabled: boolean;
roleManagementEnabled: boolean;
roleMappingManagementEnabled: boolean;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { licenseMock } from '../../common/licensing/index.mock';
import type { SecurityLicenseFeatures } from '../../common/licensing/license_features';
import { securityMock } from '../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';
Expand Down Expand Up @@ -78,6 +79,66 @@ describe('ManagementService', () => {
title: 'Role Mappings',
});
});

it('Users, Roles, and Role Mappings are not registered when their UI config settings are set to false', () => {
const mockSectionWithConfig = createManagementSectionMock();
const { fatalErrors, getStartServices } = coreMock.createSetup();
const { authc } = securityMock.createSetup();
const license = licenseMock.create();

const managementSetup: ManagementSetup = {
sections: {
register: jest.fn(() => mockSectionWithConfig),
section: {
security: mockSectionWithConfig,
} as DefinedSections,
},
locator: {} as any,
};

const uiConfig: ManagementAppConfigType = {
userManagementEnabled: false,
roleManagementEnabled: false,
roleMappingManagementEnabled: false,
};

const service = new ManagementService();
service.setup({
getStartServices: getStartServices as any,
license,
fatalErrors,
authc,
management: managementSetup,
uiConfig,
});

// Only API Keys app should be registered
expect(mockSectionWithConfig.registerApp).toHaveBeenCalledTimes(1);
expect(mockSectionWithConfig.registerApp).not.toHaveBeenCalledWith({
id: 'users',
mount: expect.any(Function),
order: 10,
title: 'Users',
});
expect(mockSectionWithConfig.registerApp).not.toHaveBeenCalledWith({
id: 'roles',
mount: expect.any(Function),
order: 20,
title: 'Roles',
});
expect(mockSectionWithConfig.registerApp).toHaveBeenCalledWith({
id: 'api_keys',
mount: expect.any(Function),
order: 30,
title: 'API keys',
});
expect(mockSectionWithConfig.registerApp).not.toHaveBeenCalledWith({
id: 'role_mappings',
mount: expect.any(Function),
order: 40,
title: 'Role Mappings',
});
});
});

describe('start()', () => {
Expand Down Expand Up @@ -105,12 +166,19 @@ 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 @@ -150,6 +218,7 @@ describe('ManagementService', () => {
navLinks: {},
catalogue: {},
},
uiConfig,
});

return {
Expand Down
53 changes: 41 additions & 12 deletions x-pack/plugins/security/public/management/management_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,48 +22,77 @@ import { roleMappingsManagementApp } from './role_mappings';
import { rolesManagementApp } from './roles';
import { usersManagementApp } from './users';

export interface ManagementAppConfigType {
userManagementEnabled: boolean;
roleManagementEnabled: boolean;
roleMappingManagementEnabled: boolean;
}

interface SetupParams {
management: ManagementSetup;
license: SecurityLicense;
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;

setup({ getStartServices, management, authc, license, fatalErrors }: SetupParams) {
setup({ getStartServices, management, authc, license, fatalErrors, uiConfig }: SetupParams) {
this.license = license;
this.securitySection = management.sections.section.security;

this.securitySection.registerApp(usersManagementApp.create({ authc, getStartServices }));
this.securitySection.registerApp(
rolesManagementApp.create({ fatalErrors, license, getStartServices })
);
if (!uiConfig || uiConfig.userManagementEnabled) {
this.securitySection.registerApp(usersManagementApp.create({ authc, getStartServices }));
}
if (!uiConfig || uiConfig.roleManagementEnabled) {
this.securitySection.registerApp(
rolesManagementApp.create({ fatalErrors, license, getStartServices })
);
}
this.securitySection.registerApp(apiKeysManagementApp.create({ authc, getStartServices }));
this.securitySection.registerApp(roleMappingsManagementApp.create({ getStartServices }));
if (!uiConfig || uiConfig.roleMappingManagementEnabled) {
this.securitySection.registerApp(roleMappingsManagementApp.create({ getStartServices }));
}
}

start({ capabilities }: StartParams) {
start({ capabilities, uiConfig }: StartParams) {
this.licenseFeaturesSubscription = this.license.features$.subscribe(async (features) => {
const securitySection = this.securitySection!;

const securityManagementAppsStatuses: Array<[ManagementApp, boolean]> = [
[securitySection.getApp(usersManagementApp.id)!, features.showLinks],
[securitySection.getApp(rolesManagementApp.id)!, features.showLinks],
[securitySection.getApp(apiKeysManagementApp.id)!, features.showLinks],
[
];

if (!uiConfig || uiConfig.userManagementEnabled) {
securityManagementAppsStatuses.push([
securitySection.getApp(usersManagementApp.id)!,
features.showLinks,
]);
}

if (!uiConfig || uiConfig.roleManagementEnabled) {
securityManagementAppsStatuses.push([
securitySection.getApp(rolesManagementApp.id)!,
features.showLinks,
]);
}

if (!uiConfig || uiConfig.roleMappingManagementEnabled) {
securityManagementAppsStatuses.push([
securitySection.getApp(roleMappingsManagementApp.id)!,
features.showLinks && features.showRoleMappingsManagement,
],
];
]);
}

// Iterate over all registered apps and update their enable status depending on the available
// license features.
Expand Down
6 changes: 5 additions & 1 deletion x-pack/plugins/security/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export class SecurityPlugin
authc: this.authc,
fatalErrors: core.fatalErrors,
getStartServices: core.getStartServices,
uiConfig: this.config.ui,
});
}

Expand Down Expand Up @@ -180,7 +181,10 @@ export class SecurityPlugin
this.securityCheckupService.start({ http, notifications, docLinks });

if (management) {
this.managementService.start({ capabilities: application.capabilities });
this.managementService.start({
capabilities: application.capabilities,
uiConfig: this.config.ui,
});
}

if (share) {
Expand Down
38 changes: 38 additions & 0 deletions x-pack/plugins/security/server/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,44 @@ describe('config schema', () => {
});
});

describe('ui', () => {
it('should not allow xpack.security.ui.* to be configured outside of the serverless context', () => {
expect(() =>
ConfigSchema.validate(
{
ui: {
userManagementEnabled: false,
roleManagementEnabled: false,
roleMappingManagementEnabled: false,
},
},
{ serverless: false }
)
).toThrowErrorMatchingInlineSnapshot(`"[ui]: a value wasn't expected to be present"`);
});

it('should allow xpack.security.ui.* to be configured inside of the serverless context', () => {
expect(
ConfigSchema.validate(
{
ui: {
userManagementEnabled: false,
roleManagementEnabled: false,
roleMappingManagementEnabled: false,
},
},
{ serverless: true }
).ui
).toMatchInlineSnapshot(`
Object {
"roleManagementEnabled": false,
"roleMappingManagementEnabled": false,
"userManagementEnabled": 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
12 changes: 12 additions & 0 deletions x-pack/plugins/security/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,18 @@ export const ConfigSchema = schema.object({
),
}),
enabled: schema.boolean({ defaultValue: true }),

// Setting only allowed in the Serverless offering
ui: schema.conditional(
schema.contextRef('serverless'),
true,
schema.object({
userManagementEnabled: schema.boolean({ defaultValue: false }),
roleManagementEnabled: schema.boolean({ defaultValue: false }),
roleMappingManagementEnabled: schema.boolean({ defaultValue: false }),
}),
schema.never()
),
ElenaStoeva marked this conversation as resolved.
Show resolved Hide resolved
});

export function createConfig(
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/security/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const config: PluginConfigDescriptor<TypeOf<typeof ConfigSchema>> = {
showInsecureClusterWarning: true,
sameSiteCookies: true,
showNavLinks: true,
ui: true,
},
};
export const plugin: PluginInitializer<
Expand Down