[7.x] Register privileges in Kibana Platform Security plugin and remove legacy getUser
API. (#68344)
# Conflicts: # x-pack/README.md # x-pack/plugins/uptime/README.md # x-pack/scripts/functional_tests.js
This commit is contained in:
parent
1cf77f7ca9
commit
2890d5b1fe
58 changed files with 721 additions and 413 deletions
|
@ -4,7 +4,6 @@ files:
|
|||
- 'src/legacy/core_plugins/timelion/**/*.s+(a|c)ss'
|
||||
- 'src/plugins/vis_type_vislib/**/*.s+(a|c)ss'
|
||||
- 'src/plugins/vis_type_xy/**/*.s+(a|c)ss'
|
||||
- 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss'
|
||||
- 'x-pack/plugins/canvas/**/*.s+(a|c)ss'
|
||||
- 'x-pack/plugins/triggers_actions_ui/**/*.s+(a|c)ss'
|
||||
- 'x-pack/plugins/lens/**/*.s+(a|c)ss'
|
||||
|
@ -12,6 +11,7 @@ files:
|
|||
- 'x-pack/legacy/plugins/maps/**/*.s+(a|c)ss'
|
||||
- 'x-pack/plugins/maps/**/*.s+(a|c)ss'
|
||||
- 'x-pack/plugins/spaces/**/*.s+(a|c)ss'
|
||||
- 'x-pack/plugins/security/**/*.s+(a|c)ss'
|
||||
ignore:
|
||||
- 'x-pack/plugins/canvas/shareable_runtime/**/*.s+(a|c)ss'
|
||||
rules:
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"xpack.reporting": ["plugins/reporting"],
|
||||
"xpack.rollupJobs": ["legacy/plugins/rollup", "plugins/rollup"],
|
||||
"xpack.searchProfiler": "plugins/searchprofiler",
|
||||
"xpack.security": ["legacy/plugins/security", "plugins/security"],
|
||||
"xpack.security": "plugins/security",
|
||||
"xpack.server": "legacy/server",
|
||||
"xpack.securitySolution": "plugins/security_solution",
|
||||
"xpack.snapshotRestore": "plugins/snapshot_restore",
|
||||
|
|
|
@ -25,8 +25,8 @@ Examples:
|
|||
- Run the jest test case whose description matches 'filtering should skip values of null':
|
||||
`cd x-pack && yarn test:jest -t 'filtering should skip values of null' plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js`
|
||||
- Run the x-pack api integration test case whose description matches the given string:
|
||||
`node scripts/functional_tests_server --config x-pack/test/api_integration/config.js`
|
||||
`node scripts/functional_test_runner --config x-pack/test/api_integration/config.js --grep='apis Monitoring Beats list with restarted beat instance should load multiple clusters'`
|
||||
`node scripts/functional_tests_server --config x-pack/test/api_integration/config.ts`
|
||||
`node scripts/functional_test_runner --config x-pack/test/api_integration/config.ts --grep='apis Monitoring Beats list with restarted beat instance should load multiple clusters'`
|
||||
|
||||
In addition to to providing a regular expression argument, specific tests can also be run by appeding `.only` to an `it` or `describe` function block. E.g. `describe(` to `describe.only(`.
|
||||
|
||||
|
@ -61,7 +61,7 @@ yarn test:mocha
|
|||
|
||||
#### Running functional tests
|
||||
|
||||
The functional UI tests, the API integration tests, and the SAML API integration tests are all run against a live browser, Kibana, and Elasticsearch install. Each set of tests is specified with a unique config that describes how to start the Elasticsearch server, the Kibana server, and what tests to run against them. The sets of tests that exist today are *functional UI tests* ([specified by this config](test/functional/config.js)), *API integration tests* ([specified by this config](test/api_integration/config.js)), and *SAML API integration tests* ([specified by this config](test/saml_api_integration/config.js)).
|
||||
The functional UI tests, the API integration tests, and the SAML API integration tests are all run against a live browser, Kibana, and Elasticsearch install. Each set of tests is specified with a unique config that describes how to start the Elasticsearch server, the Kibana server, and what tests to run against them. The sets of tests that exist today are *functional UI tests* ([specified by this config](test/functional/config.js)), *API integration tests* ([specified by this config](test/api_integration/config.ts)), and *SAML API integration tests* ([specified by this config](test/saml_api_integration/config.ts)).
|
||||
|
||||
The script runs all sets of tests sequentially like so:
|
||||
* builds Elasticsearch and X-Pack
|
||||
|
|
|
@ -15,7 +15,7 @@ In one shell, from **~/kibana/x-pack**:
|
|||
`node scripts/functional_tests-server.js`
|
||||
|
||||
In another shell, from **~kibana/x-pack**:
|
||||
`node ../scripts/functional_test_runner.js --config test/api_integration/config.js`.
|
||||
`node ../scripts/functional_test_runner.js --config test/api_integration/config.ts`.
|
||||
|
||||
### Manual e2e testing
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { Lifecycle, ResponseToolkit } from 'hapi';
|
||||
import * as t from 'io-ts';
|
||||
import { SecurityPluginSetup } from '../../../../../../../plugins/security/server';
|
||||
import { LicenseType } from '../../../../common/constants/security';
|
||||
|
||||
export const internalAuthData = Symbol('internalAuthData');
|
||||
|
@ -39,6 +40,11 @@ export interface BackendFrameworkAdapter {
|
|||
}
|
||||
|
||||
export interface KibanaLegacyServer {
|
||||
newPlatform: {
|
||||
setup: {
|
||||
plugins: { security: SecurityPluginSetup };
|
||||
};
|
||||
};
|
||||
plugins: {
|
||||
xpack_main: {
|
||||
status: {
|
||||
|
@ -53,9 +59,6 @@ export interface KibanaLegacyServer {
|
|||
};
|
||||
};
|
||||
};
|
||||
security: {
|
||||
getUser: (request: KibanaServerRequest) => any;
|
||||
};
|
||||
elasticsearch: {
|
||||
status: {
|
||||
on: (status: 'green' | 'yellow' | 'red', callback: () => void) => void;
|
||||
|
|
|
@ -8,6 +8,7 @@ import { ResponseToolkit } from 'hapi';
|
|||
import { PathReporter } from 'io-ts/lib/PathReporter';
|
||||
import { get } from 'lodash';
|
||||
import { isLeft } from 'fp-ts/lib/Either';
|
||||
import { KibanaRequest, LegacyRequest } from '../../../../../../../../src/core/server';
|
||||
// @ts-ignore
|
||||
import { mirrorPluginStatus } from '../../../../../../server/lib/mirror_plugin_status';
|
||||
import {
|
||||
|
@ -128,13 +129,10 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter {
|
|||
}
|
||||
|
||||
private async getUser(request: KibanaServerRequest): Promise<KibanaUser | null> {
|
||||
let user;
|
||||
try {
|
||||
user = await this.server.plugins.security.getUser(request);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
if (user === null) {
|
||||
const user = this.server.newPlatform.setup.plugins.security?.authc.getCurrentUser(
|
||||
KibanaRequest.from((request as unknown) as LegacyRequest)
|
||||
);
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
const assertKibanaUser = RuntimeKibanaUser.decode(user);
|
||||
|
|
|
@ -6,64 +6,17 @@
|
|||
|
||||
import { Root } from 'joi';
|
||||
import { resolve } from 'path';
|
||||
import { Server } from 'src/legacy/server/kbn_server';
|
||||
import { KibanaRequest, LegacyRequest } from '../../../../src/core/server';
|
||||
// @ts-ignore
|
||||
import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize';
|
||||
import { AuthenticatedUser, SecurityPluginSetup } from '../../../plugins/security/server';
|
||||
|
||||
/**
|
||||
* Public interface of the security plugin.
|
||||
*/
|
||||
export interface SecurityPlugin {
|
||||
getUser: (request: LegacyRequest) => Promise<AuthenticatedUser>;
|
||||
}
|
||||
|
||||
function getSecurityPluginSetup(server: Server) {
|
||||
const securityPlugin = server.newPlatform.setup.plugins.security as SecurityPluginSetup;
|
||||
if (!securityPlugin) {
|
||||
throw new Error('Kibana Platform Security plugin is not available.');
|
||||
}
|
||||
|
||||
return securityPlugin;
|
||||
}
|
||||
|
||||
export const security = (kibana: Record<string, any>) =>
|
||||
new kibana.Plugin({
|
||||
id: 'security',
|
||||
publicDir: resolve(__dirname, 'public'),
|
||||
require: ['kibana', 'xpack_main'],
|
||||
require: ['kibana'],
|
||||
configPrefix: 'xpack.security',
|
||||
uiExports: {
|
||||
hacks: ['plugins/security/hacks/legacy'],
|
||||
injectDefaultVars: (server: Server) => {
|
||||
return { enableSpaceAwarePrivileges: server.config().get('xpack.spaces.enabled') };
|
||||
},
|
||||
},
|
||||
|
||||
config(Joi: Root) {
|
||||
return Joi.object({
|
||||
enabled: Joi.boolean().default(true),
|
||||
})
|
||||
uiExports: { hacks: ['plugins/security/hacks/legacy'] },
|
||||
config: (Joi: Root) =>
|
||||
Joi.object({ enabled: Joi.boolean().default(true) })
|
||||
.unknown()
|
||||
.default();
|
||||
},
|
||||
|
||||
async postInit(server: Server) {
|
||||
watchStatusAndLicenseToInitialize(server.plugins.xpack_main, this, async () => {
|
||||
const xpackInfo = server.plugins.xpack_main.info;
|
||||
if (xpackInfo.isAvailable() && xpackInfo.feature('security').isEnabled()) {
|
||||
await getSecurityPluginSetup(server).__legacyCompat.registerPrivilegesWithCluster();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async init(server: Server) {
|
||||
const securityPlugin = getSecurityPluginSetup(server);
|
||||
|
||||
server.expose({
|
||||
getUser: async (request: LegacyRequest) =>
|
||||
securityPlugin.authc.getCurrentUser(KibanaRequest.from(request)),
|
||||
});
|
||||
},
|
||||
.default(),
|
||||
init() {},
|
||||
});
|
||||
|
|
|
@ -83,13 +83,13 @@ For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
|
|||
**Start server**
|
||||
|
||||
```
|
||||
node scripts/functional_tests_server --config x-pack/test/api_integration/config.js
|
||||
node scripts/functional_tests_server --config x-pack/test/api_integration/config.ts
|
||||
```
|
||||
|
||||
**Run tests**
|
||||
|
||||
```
|
||||
node scripts/functional_test_runner --config x-pack/test/api_integration/config.js --grep='APM specs'
|
||||
node scripts/functional_test_runner --config x-pack/test/api_integration/config.ts --grep='APM specs'
|
||||
```
|
||||
|
||||
APM tests are located in `x-pack/test/api_integration/apis/apm`.
|
||||
|
|
|
@ -52,12 +52,12 @@ This plugin follows the `common`, `server`, `public` structure from the [Archite
|
|||
1. In one terminal, change to the `x-pack` directory and start the test server with
|
||||
|
||||
```
|
||||
node scripts/functional_tests_server.js --config test/api_integration/config.js
|
||||
node scripts/functional_tests_server.js --config test/api_integration/config.ts
|
||||
```
|
||||
|
||||
1. in a second terminal, run the tests from the Kibana root directory with
|
||||
```
|
||||
node scripts/functional_test_runner.js --config x-pack/test/api_integration/config.js
|
||||
node scripts/functional_test_runner.js --config x-pack/test/api_integration/config.ts
|
||||
```
|
||||
|
||||
#### EPM
|
||||
|
|
|
@ -11,4 +11,4 @@ Run all tests from the `x-pack` root directory
|
|||
- You may want to comment out all imports except for Lens in the config file.
|
||||
- API Functional tests:
|
||||
- Run `node scripts/functional_tests_server`
|
||||
- Run `node ../scripts/functional_test_runner.js --config ./test/api_integration/config.js --grep=Lens`
|
||||
- Run `node ../scripts/functional_test_runner.js --config ./test/api_integration/config.ts --grep=Lens`
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { StartServicesAccessor, ApplicationSetup, AppMountParameters } from 'src/core/public';
|
||||
import {
|
||||
ApplicationSetup,
|
||||
AppMountParameters,
|
||||
AppNavLinkStatus,
|
||||
StartServicesAccessor,
|
||||
} from '../../../../../src/core/public';
|
||||
import { AuthenticationServiceSetup } from '../authentication';
|
||||
|
||||
interface CreateDeps {
|
||||
|
@ -23,8 +28,7 @@ export const accountManagementApp = Object.freeze({
|
|||
application.register({
|
||||
id: this.id,
|
||||
title,
|
||||
// TODO: switch to proper enum once https://github.com/elastic/kibana/issues/58327 is resolved.
|
||||
navLinkStatus: 3,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
appRoute: '/security/account',
|
||||
async mount({ element }: AppMountParameters) {
|
||||
const [
|
||||
|
|
|
@ -23,9 +23,10 @@
|
|||
}
|
||||
|
||||
&:focus {
|
||||
@include euiFocusRing;
|
||||
|
||||
border-color: transparent;
|
||||
border-radius: $euiBorderRadius;
|
||||
@include euiFocusRing;
|
||||
|
||||
.secLoginCard__title {
|
||||
text-decoration: underline;
|
||||
|
|
|
@ -11,13 +11,15 @@ import { Feature } from '../../../../../features/public';
|
|||
import { KibanaPrivileges } from '../model';
|
||||
import { SecurityLicenseFeatures } from '../../..';
|
||||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { featuresPluginMock } from '../../../../../features/server/mocks';
|
||||
|
||||
export const createRawKibanaPrivileges = (
|
||||
features: Feature[],
|
||||
{ allowSubFeaturePrivileges = true } = {}
|
||||
) => {
|
||||
const featuresService = {
|
||||
getFeatures: () => features,
|
||||
};
|
||||
const featuresService = featuresPluginMock.createSetup();
|
||||
featuresService.getFeatures.mockReturnValue(features);
|
||||
|
||||
const licensingService = {
|
||||
getFeatures: () => ({ allowSubFeaturePrivileges } as SecurityLicenseFeatures),
|
||||
|
|
|
@ -163,7 +163,12 @@ function getProps({
|
|||
const { http, docLinks, notifications } = coreMock.createStart();
|
||||
http.get.mockImplementation(async (path: any) => {
|
||||
if (path === '/api/spaces/space') {
|
||||
return buildSpaces();
|
||||
if (spacesEnabled) {
|
||||
return buildSpaces();
|
||||
}
|
||||
|
||||
const notFoundError = { response: { status: 404 } };
|
||||
throw notFoundError;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -181,7 +186,6 @@ function getProps({
|
|||
notifications,
|
||||
docLinks: new DocumentationLinksService(docLinks),
|
||||
fatalErrors,
|
||||
spacesEnabled,
|
||||
uiCapabilities: buildUICapabilities(canManageSpaces),
|
||||
history: (scopedHistoryMock.create() as unknown) as ScopedHistory,
|
||||
};
|
||||
|
|
|
@ -80,7 +80,6 @@ interface Props {
|
|||
docLinks: DocumentationLinksService;
|
||||
http: HttpStart;
|
||||
license: SecurityLicense;
|
||||
spacesEnabled: boolean;
|
||||
uiCapabilities: Capabilities;
|
||||
notifications: NotificationsStart;
|
||||
fatalErrors: FatalErrorsSetup;
|
||||
|
@ -225,14 +224,21 @@ function useRole(
|
|||
return [role, setRole] as [Role | null, typeof setRole];
|
||||
}
|
||||
|
||||
function useSpaces(http: HttpStart, fatalErrors: FatalErrorsSetup, spacesEnabled: boolean) {
|
||||
const [spaces, setSpaces] = useState<Space[] | null>(null);
|
||||
function useSpaces(http: HttpStart, fatalErrors: FatalErrorsSetup) {
|
||||
const [spaces, setSpaces] = useState<{ enabled: boolean; list: Space[] } | null>(null);
|
||||
useEffect(() => {
|
||||
(spacesEnabled ? http.get('/api/spaces/space') : Promise.resolve([])).then(
|
||||
(fetchedSpaces) => setSpaces(fetchedSpaces),
|
||||
(err) => fatalErrors.add(err)
|
||||
http.get('/api/spaces/space').then(
|
||||
(fetchedSpaces) => setSpaces({ enabled: true, list: fetchedSpaces }),
|
||||
(err: IHttpFetchError) => {
|
||||
// Spaces plugin can be disabled and hence this endpoint can be unavailable.
|
||||
if (err.response?.status === 404) {
|
||||
setSpaces({ enabled: false, list: [] });
|
||||
} else {
|
||||
fatalErrors.add(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}, [http, fatalErrors, spacesEnabled]);
|
||||
}, [http, fatalErrors]);
|
||||
|
||||
return spaces;
|
||||
}
|
||||
|
@ -278,7 +284,6 @@ export const EditRolePage: FunctionComponent<Props> = ({
|
|||
roleName,
|
||||
action,
|
||||
fatalErrors,
|
||||
spacesEnabled,
|
||||
license,
|
||||
docLinks,
|
||||
uiCapabilities,
|
||||
|
@ -295,7 +300,7 @@ export const EditRolePage: FunctionComponent<Props> = ({
|
|||
const runAsUsers = useRunAsUsers(userAPIClient, fatalErrors);
|
||||
const indexPatternsTitles = useIndexPatternsTitles(indexPatterns, fatalErrors, notifications);
|
||||
const privileges = usePrivileges(privilegesAPIClient, fatalErrors);
|
||||
const spaces = useSpaces(http, fatalErrors, spacesEnabled);
|
||||
const spaces = useSpaces(http, fatalErrors);
|
||||
const features = useFeatures(getFeatures, fatalErrors);
|
||||
const [role, setRole] = useRole(
|
||||
rolesAPIClient,
|
||||
|
@ -434,8 +439,8 @@ export const EditRolePage: FunctionComponent<Props> = ({
|
|||
<EuiSpacer />
|
||||
<KibanaPrivilegesRegion
|
||||
kibanaPrivileges={new KibanaPrivileges(kibanaPrivileges, features)}
|
||||
spaces={spaces}
|
||||
spacesEnabled={spacesEnabled}
|
||||
spaces={spaces.list}
|
||||
spacesEnabled={spaces.enabled}
|
||||
uiCapabilities={uiCapabilities}
|
||||
canCustomizeSubFeaturePrivileges={license.getFeatures().allowSubFeaturePrivileges}
|
||||
editable={!isRoleReadOnly}
|
||||
|
@ -519,7 +524,7 @@ export const EditRolePage: FunctionComponent<Props> = ({
|
|||
setFormError(null);
|
||||
|
||||
try {
|
||||
await rolesAPIClient.saveRole({ role, spacesEnabled });
|
||||
await rolesAPIClient.saveRole({ role, spacesEnabled: spaces.enabled });
|
||||
} catch (error) {
|
||||
notifications.toasts.addDanger(get(error, 'data.message'));
|
||||
return;
|
||||
|
@ -554,7 +559,7 @@ export const EditRolePage: FunctionComponent<Props> = ({
|
|||
backToRoleList();
|
||||
};
|
||||
|
||||
const description = spacesEnabled ? (
|
||||
const description = spaces.enabled ? (
|
||||
<FormattedMessage
|
||||
id="xpack.security.management.editRole.setPrivilegesToKibanaSpacesDescription"
|
||||
defaultMessage="Set privileges on your Elasticsearch data and control access to your Kibana spaces."
|
||||
|
|
|
@ -36,10 +36,7 @@ export const rolesManagementApp = Object.freeze({
|
|||
];
|
||||
|
||||
const [
|
||||
[
|
||||
{ application, docLinks, http, i18n: i18nStart, injectedMetadata, notifications },
|
||||
{ data, features },
|
||||
],
|
||||
[{ application, docLinks, http, i18n: i18nStart, notifications }, { data, features }],
|
||||
{ RolesGridPage },
|
||||
{ EditRolePage },
|
||||
{ RolesAPIClient },
|
||||
|
@ -86,9 +83,6 @@ export const rolesManagementApp = Object.freeze({
|
|||
<EditRolePage
|
||||
action={action}
|
||||
roleName={roleName}
|
||||
spacesEnabled={
|
||||
injectedMetadata.getInjectedVar('enableSpaceAwarePrivileges') as boolean
|
||||
}
|
||||
rolesAPIClient={rolesAPIClient}
|
||||
userAPIClient={new UserAPIClient(http)}
|
||||
indicesAPIClient={new IndicesAPIClient(http)}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CoreSetup, Logger } from '../../../../../src/core/server';
|
||||
import { Authorization } from '.';
|
||||
import { HttpServiceSetup, Logger } from '../../../../../src/core/server';
|
||||
import { AuthorizationServiceSetup } from '.';
|
||||
|
||||
export function initAPIAuthorization(
|
||||
http: CoreSetup['http'],
|
||||
{ actions, checkPrivilegesDynamicallyWithRequest, mode }: Authorization,
|
||||
http: HttpServiceSetup,
|
||||
{ actions, checkPrivilegesDynamicallyWithRequest, mode }: AuthorizationServiceSetup,
|
||||
logger: Logger
|
||||
) {
|
||||
http.registerOnPostAuth(async (request, response, toolkit) => {
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CoreSetup, Logger } from '../../../../../src/core/server';
|
||||
import { FeaturesService } from '../plugin';
|
||||
import { Authorization } from '.';
|
||||
import { HttpServiceSetup, Logger } from '../../../../../src/core/server';
|
||||
import { PluginSetupContract as FeaturesPluginSetup } from '../../../features/server';
|
||||
import { AuthorizationServiceSetup } from '.';
|
||||
|
||||
class ProtectedApplications {
|
||||
private applications: Set<string> | null = null;
|
||||
constructor(private readonly featuresService: FeaturesService) {}
|
||||
constructor(private readonly featuresService: FeaturesPluginSetup) {}
|
||||
|
||||
public shouldProtect(appId: string) {
|
||||
// Currently, once we get the list of features we essentially "lock" additional
|
||||
|
@ -30,14 +30,14 @@ class ProtectedApplications {
|
|||
}
|
||||
|
||||
export function initAppAuthorization(
|
||||
http: CoreSetup['http'],
|
||||
http: HttpServiceSetup,
|
||||
{
|
||||
actions,
|
||||
checkPrivilegesDynamicallyWithRequest,
|
||||
mode,
|
||||
}: Pick<Authorization, 'actions' | 'checkPrivilegesDynamicallyWithRequest' | 'mode'>,
|
||||
}: Pick<AuthorizationServiceSetup, 'actions' | 'checkPrivilegesDynamicallyWithRequest' | 'mode'>,
|
||||
logger: Logger,
|
||||
featuresService: FeaturesService
|
||||
featuresService: FeaturesPluginSetup
|
||||
) {
|
||||
const protectedApplications = new ProtectedApplications(featuresService);
|
||||
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
mockAuthorizationModeFactory,
|
||||
mockCheckPrivilegesDynamicallyWithRequestFactory,
|
||||
mockCheckPrivilegesWithRequestFactory,
|
||||
mockCheckSavedObjectsPrivilegesWithRequestFactory,
|
||||
mockPrivilegesFactory,
|
||||
mockRegisterPrivilegesWithCluster,
|
||||
} from './service.test.mocks';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { CoreStatus, ServiceStatusLevels } from '../../../../../src/core/server';
|
||||
import { checkPrivilegesWithRequestFactory } from './check_privileges';
|
||||
import { checkPrivilegesDynamicallyWithRequestFactory } from './check_privileges_dynamically';
|
||||
import { checkSavedObjectsPrivilegesWithRequestFactory } from './check_saved_objects_privileges';
|
||||
import { authorizationModeFactory } from './mode';
|
||||
import { privilegesFactory } from './privileges';
|
||||
import { AuthorizationService } from '.';
|
||||
|
||||
import {
|
||||
coreMock,
|
||||
elasticsearchServiceMock,
|
||||
loggingServiceMock,
|
||||
} from '../../../../../src/core/server/mocks';
|
||||
import { featuresPluginMock } from '../../../features/server/mocks';
|
||||
import { licenseMock } from '../../common/licensing/index.mock';
|
||||
import { SecurityLicense, SecurityLicenseFeatures } from '../../common/licensing';
|
||||
import { nextTick } from 'test_utils/enzyme_helpers';
|
||||
|
||||
const kibanaIndexName = '.a-kibana-index';
|
||||
const application = `kibana-${kibanaIndexName}`;
|
||||
const mockCheckPrivilegesWithRequest = Symbol();
|
||||
const mockCheckPrivilegesDynamicallyWithRequest = Symbol();
|
||||
const mockCheckSavedObjectsPrivilegesWithRequest = Symbol();
|
||||
const mockPrivilegesService = Symbol();
|
||||
const mockAuthorizationMode = Symbol();
|
||||
beforeEach(() => {
|
||||
mockCheckPrivilegesWithRequestFactory.mockReturnValue(mockCheckPrivilegesWithRequest);
|
||||
mockCheckPrivilegesDynamicallyWithRequestFactory.mockReturnValue(
|
||||
mockCheckPrivilegesDynamicallyWithRequest
|
||||
);
|
||||
mockCheckSavedObjectsPrivilegesWithRequestFactory.mockReturnValue(
|
||||
mockCheckSavedObjectsPrivilegesWithRequest
|
||||
);
|
||||
mockPrivilegesFactory.mockReturnValue(mockPrivilegesService);
|
||||
mockAuthorizationModeFactory.mockReturnValue(mockAuthorizationMode);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockRegisterPrivilegesWithCluster.mockClear();
|
||||
});
|
||||
|
||||
it(`#setup returns exposed services`, () => {
|
||||
const mockClusterClient = elasticsearchServiceMock.createClusterClient();
|
||||
const mockGetSpacesService = jest
|
||||
.fn()
|
||||
.mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() });
|
||||
const mockFeaturesSetup = featuresPluginMock.createSetup();
|
||||
const mockLicense = licenseMock.create();
|
||||
const mockCoreSetup = coreMock.createSetup();
|
||||
|
||||
const authorizationService = new AuthorizationService();
|
||||
const authz = authorizationService.setup({
|
||||
http: mockCoreSetup.http,
|
||||
capabilities: mockCoreSetup.capabilities,
|
||||
status: mockCoreSetup.status,
|
||||
clusterClient: mockClusterClient,
|
||||
license: mockLicense,
|
||||
loggers: loggingServiceMock.create(),
|
||||
kibanaIndexName,
|
||||
packageVersion: 'some-version',
|
||||
features: mockFeaturesSetup,
|
||||
getSpacesService: mockGetSpacesService,
|
||||
});
|
||||
|
||||
expect(authz.actions.version).toBe('version:some-version');
|
||||
expect(authz.applicationName).toBe(application);
|
||||
|
||||
expect(authz.checkPrivilegesWithRequest).toBe(mockCheckPrivilegesWithRequest);
|
||||
expect(checkPrivilegesWithRequestFactory).toHaveBeenCalledWith(
|
||||
authz.actions,
|
||||
mockClusterClient,
|
||||
authz.applicationName
|
||||
);
|
||||
|
||||
expect(authz.checkPrivilegesDynamicallyWithRequest).toBe(
|
||||
mockCheckPrivilegesDynamicallyWithRequest
|
||||
);
|
||||
expect(checkPrivilegesDynamicallyWithRequestFactory).toHaveBeenCalledWith(
|
||||
mockCheckPrivilegesWithRequest,
|
||||
mockGetSpacesService
|
||||
);
|
||||
|
||||
expect(authz.checkSavedObjectsPrivilegesWithRequest).toBe(
|
||||
mockCheckSavedObjectsPrivilegesWithRequest
|
||||
);
|
||||
expect(checkSavedObjectsPrivilegesWithRequestFactory).toHaveBeenCalledWith(
|
||||
mockCheckPrivilegesWithRequest,
|
||||
mockGetSpacesService
|
||||
);
|
||||
|
||||
expect(authz.privileges).toBe(mockPrivilegesService);
|
||||
expect(privilegesFactory).toHaveBeenCalledWith(authz.actions, mockFeaturesSetup, mockLicense);
|
||||
|
||||
expect(authz.mode).toBe(mockAuthorizationMode);
|
||||
expect(authorizationModeFactory).toHaveBeenCalledWith(mockLicense);
|
||||
|
||||
expect(mockCoreSetup.capabilities.registerSwitcher).toHaveBeenCalledTimes(1);
|
||||
expect(mockCoreSetup.capabilities.registerSwitcher).toHaveBeenCalledWith(expect.any(Function));
|
||||
});
|
||||
|
||||
describe('#start', () => {
|
||||
let statusSubject: BehaviorSubject<CoreStatus>;
|
||||
let licenseSubject: BehaviorSubject<SecurityLicenseFeatures>;
|
||||
let mockLicense: jest.Mocked<SecurityLicense>;
|
||||
beforeEach(() => {
|
||||
const mockClusterClient = elasticsearchServiceMock.createClusterClient();
|
||||
|
||||
licenseSubject = new BehaviorSubject(({} as unknown) as SecurityLicenseFeatures);
|
||||
mockLicense = licenseMock.create();
|
||||
mockLicense.isEnabled.mockReturnValue(false);
|
||||
mockLicense.features$ = licenseSubject;
|
||||
|
||||
statusSubject = new BehaviorSubject<CoreStatus>({
|
||||
elasticsearch: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' },
|
||||
savedObjects: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' },
|
||||
});
|
||||
const mockCoreSetup = coreMock.createSetup();
|
||||
mockCoreSetup.status.core$ = statusSubject;
|
||||
|
||||
const authorizationService = new AuthorizationService();
|
||||
authorizationService.setup({
|
||||
http: mockCoreSetup.http,
|
||||
capabilities: mockCoreSetup.capabilities,
|
||||
status: mockCoreSetup.status,
|
||||
clusterClient: mockClusterClient,
|
||||
license: mockLicense,
|
||||
loggers: loggingServiceMock.create(),
|
||||
kibanaIndexName,
|
||||
packageVersion: 'some-version',
|
||||
features: featuresPluginMock.createSetup(),
|
||||
getSpacesService: jest
|
||||
.fn()
|
||||
.mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() }),
|
||||
});
|
||||
|
||||
const featuresStart = featuresPluginMock.createStart();
|
||||
featuresStart.getFeatures.mockReturnValue([]);
|
||||
|
||||
authorizationService.start({ clusterClient: mockClusterClient, features: featuresStart });
|
||||
|
||||
// ES and license aren't available yet.
|
||||
expect(mockRegisterPrivilegesWithCluster).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('registers cluster privileges', async () => {
|
||||
// ES is available now, but not license.
|
||||
statusSubject.next({
|
||||
elasticsearch: { level: ServiceStatusLevels.available, summary: 'Service is working' },
|
||||
savedObjects: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' },
|
||||
});
|
||||
expect(mockRegisterPrivilegesWithCluster).not.toHaveBeenCalled();
|
||||
|
||||
// Both ES and license are available now.
|
||||
mockLicense.isEnabled.mockReturnValue(true);
|
||||
licenseSubject.next(({} as unknown) as SecurityLicenseFeatures);
|
||||
expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(1);
|
||||
|
||||
await nextTick();
|
||||
|
||||
// New changes still trigger privileges re-registration.
|
||||
licenseSubject.next(({} as unknown) as SecurityLicenseFeatures);
|
||||
expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('schedules retries if fails to register cluster privileges', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
mockRegisterPrivilegesWithCluster.mockRejectedValue(new Error('Some error'));
|
||||
|
||||
// Both ES and license are available.
|
||||
mockLicense.isEnabled.mockReturnValue(true);
|
||||
statusSubject.next({
|
||||
elasticsearch: { level: ServiceStatusLevels.available, summary: 'Service is working' },
|
||||
savedObjects: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' },
|
||||
});
|
||||
expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Next retry isn't performed immediately, retry happens only after a timeout.
|
||||
await nextTick();
|
||||
expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(1);
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Delay between consequent retries is increasing.
|
||||
await nextTick();
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(2);
|
||||
await nextTick();
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(3);
|
||||
|
||||
// When call finally succeeds retries aren't scheduled anymore.
|
||||
mockRegisterPrivilegesWithCluster.mockResolvedValue(undefined);
|
||||
await nextTick();
|
||||
jest.runAllTimers();
|
||||
expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(4);
|
||||
await nextTick();
|
||||
jest.runAllTimers();
|
||||
expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(4);
|
||||
|
||||
// New changes still trigger privileges re-registration.
|
||||
licenseSubject.next(({} as unknown) as SecurityLicenseFeatures);
|
||||
expect(mockRegisterPrivilegesWithCluster).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
});
|
||||
|
||||
it('#stop unsubscribes from license and ES updates.', () => {
|
||||
const mockClusterClient = elasticsearchServiceMock.createClusterClient();
|
||||
|
||||
const licenseSubject = new BehaviorSubject(({} as unknown) as SecurityLicenseFeatures);
|
||||
const mockLicense = licenseMock.create();
|
||||
mockLicense.isEnabled.mockReturnValue(false);
|
||||
mockLicense.features$ = licenseSubject;
|
||||
|
||||
const mockCoreSetup = coreMock.createSetup();
|
||||
mockCoreSetup.status.core$ = new BehaviorSubject<CoreStatus>({
|
||||
elasticsearch: { level: ServiceStatusLevels.available, summary: 'Service is working' },
|
||||
savedObjects: { level: ServiceStatusLevels.available, summary: 'Service is working' },
|
||||
});
|
||||
|
||||
const authorizationService = new AuthorizationService();
|
||||
authorizationService.setup({
|
||||
http: mockCoreSetup.http,
|
||||
capabilities: mockCoreSetup.capabilities,
|
||||
status: mockCoreSetup.status,
|
||||
clusterClient: mockClusterClient,
|
||||
license: mockLicense,
|
||||
loggers: loggingServiceMock.create(),
|
||||
kibanaIndexName,
|
||||
packageVersion: 'some-version',
|
||||
features: featuresPluginMock.createSetup(),
|
||||
getSpacesService: jest
|
||||
.fn()
|
||||
.mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() }),
|
||||
});
|
||||
|
||||
const featuresStart = featuresPluginMock.createStart();
|
||||
featuresStart.getFeatures.mockReturnValue([]);
|
||||
authorizationService.start({ clusterClient: mockClusterClient, features: featuresStart });
|
||||
|
||||
authorizationService.stop();
|
||||
|
||||
// After stop we don't register privileges even if all requirements are met.
|
||||
mockLicense.isEnabled.mockReturnValue(true);
|
||||
licenseSubject.next(({} as unknown) as SecurityLicenseFeatures);
|
||||
expect(mockRegisterPrivilegesWithCluster).not.toHaveBeenCalled();
|
||||
});
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { combineLatest, BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { distinctUntilChanged, filter } from 'rxjs/operators';
|
||||
import { UICapabilities } from 'ui/capabilities';
|
||||
import {
|
||||
LoggerFactory,
|
||||
KibanaRequest,
|
||||
IClusterClient,
|
||||
ServiceStatusLevels,
|
||||
Logger,
|
||||
StatusServiceSetup,
|
||||
HttpServiceSetup,
|
||||
CapabilitiesSetup,
|
||||
} from '../../../../../src/core/server';
|
||||
|
||||
import {
|
||||
PluginSetupContract as FeaturesPluginSetup,
|
||||
PluginStartContract as FeaturesPluginStart,
|
||||
} from '../../../features/server';
|
||||
|
||||
import { SpacesService } from '../plugin';
|
||||
import { Actions } from './actions';
|
||||
import { CheckPrivilegesWithRequest, checkPrivilegesWithRequestFactory } from './check_privileges';
|
||||
import {
|
||||
CheckPrivilegesDynamicallyWithRequest,
|
||||
checkPrivilegesDynamicallyWithRequestFactory,
|
||||
} from './check_privileges_dynamically';
|
||||
import {
|
||||
CheckSavedObjectsPrivilegesWithRequest,
|
||||
checkSavedObjectsPrivilegesWithRequestFactory,
|
||||
} from './check_saved_objects_privileges';
|
||||
import { AuthorizationMode, authorizationModeFactory } from './mode';
|
||||
import { privilegesFactory, PrivilegesService } from './privileges';
|
||||
import { initAppAuthorization } from './app_authorization';
|
||||
import { initAPIAuthorization } from './api_authorization';
|
||||
import { disableUICapabilitiesFactory } from './disable_ui_capabilities';
|
||||
import { validateFeaturePrivileges } from './validate_feature_privileges';
|
||||
import { validateReservedPrivileges } from './validate_reserved_privileges';
|
||||
import { registerPrivilegesWithCluster } from './register_privileges_with_cluster';
|
||||
import { APPLICATION_PREFIX } from '../../common/constants';
|
||||
import { SecurityLicense } from '../../common/licensing';
|
||||
|
||||
export { Actions } from './actions';
|
||||
export { CheckSavedObjectsPrivileges } from './check_saved_objects_privileges';
|
||||
export { featurePrivilegeIterator } from './privileges';
|
||||
|
||||
interface AuthorizationServiceSetupParams {
|
||||
packageVersion: string;
|
||||
http: HttpServiceSetup;
|
||||
status: StatusServiceSetup;
|
||||
capabilities: CapabilitiesSetup;
|
||||
clusterClient: IClusterClient;
|
||||
license: SecurityLicense;
|
||||
loggers: LoggerFactory;
|
||||
features: FeaturesPluginSetup;
|
||||
kibanaIndexName: string;
|
||||
getSpacesService(): SpacesService | undefined;
|
||||
}
|
||||
|
||||
interface AuthorizationServiceStartParams {
|
||||
features: FeaturesPluginStart;
|
||||
clusterClient: IClusterClient;
|
||||
}
|
||||
|
||||
export interface AuthorizationServiceSetup {
|
||||
actions: Actions;
|
||||
checkPrivilegesWithRequest: CheckPrivilegesWithRequest;
|
||||
checkPrivilegesDynamicallyWithRequest: CheckPrivilegesDynamicallyWithRequest;
|
||||
checkSavedObjectsPrivilegesWithRequest: CheckSavedObjectsPrivilegesWithRequest;
|
||||
applicationName: string;
|
||||
mode: AuthorizationMode;
|
||||
privileges: PrivilegesService;
|
||||
}
|
||||
|
||||
export class AuthorizationService {
|
||||
private logger!: Logger;
|
||||
private license!: SecurityLicense;
|
||||
private status!: StatusServiceSetup;
|
||||
private applicationName!: string;
|
||||
private privileges!: PrivilegesService;
|
||||
|
||||
private statusSubscription?: Subscription;
|
||||
|
||||
setup({
|
||||
http,
|
||||
capabilities,
|
||||
status,
|
||||
packageVersion,
|
||||
clusterClient,
|
||||
license,
|
||||
loggers,
|
||||
features,
|
||||
kibanaIndexName,
|
||||
getSpacesService,
|
||||
}: AuthorizationServiceSetupParams): AuthorizationServiceSetup {
|
||||
this.logger = loggers.get('authorization');
|
||||
this.license = license;
|
||||
this.status = status;
|
||||
this.applicationName = `${APPLICATION_PREFIX}${kibanaIndexName}`;
|
||||
|
||||
const mode = authorizationModeFactory(license);
|
||||
const actions = new Actions(packageVersion);
|
||||
this.privileges = privilegesFactory(actions, features, license);
|
||||
|
||||
const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory(
|
||||
actions,
|
||||
clusterClient,
|
||||
this.applicationName
|
||||
);
|
||||
|
||||
const authz = {
|
||||
actions,
|
||||
applicationName: this.applicationName,
|
||||
mode,
|
||||
privileges: this.privileges,
|
||||
checkPrivilegesWithRequest,
|
||||
checkPrivilegesDynamicallyWithRequest: checkPrivilegesDynamicallyWithRequestFactory(
|
||||
checkPrivilegesWithRequest,
|
||||
getSpacesService
|
||||
),
|
||||
checkSavedObjectsPrivilegesWithRequest: checkSavedObjectsPrivilegesWithRequestFactory(
|
||||
checkPrivilegesWithRequest,
|
||||
getSpacesService
|
||||
),
|
||||
};
|
||||
|
||||
capabilities.registerSwitcher(
|
||||
async (request: KibanaRequest, uiCapabilities: UICapabilities) => {
|
||||
// If we have a license which doesn't enable security, or we're a legacy user we shouldn't
|
||||
// disable any ui capabilities
|
||||
if (!mode.useRbacForRequest(request)) {
|
||||
return uiCapabilities;
|
||||
}
|
||||
|
||||
const disableUICapabilities = disableUICapabilitiesFactory(
|
||||
request,
|
||||
features.getFeatures(),
|
||||
this.logger,
|
||||
authz
|
||||
);
|
||||
|
||||
if (!request.auth.isAuthenticated) {
|
||||
return disableUICapabilities.all(uiCapabilities);
|
||||
}
|
||||
|
||||
return await disableUICapabilities.usingPrivileges(uiCapabilities);
|
||||
}
|
||||
);
|
||||
|
||||
initAPIAuthorization(http, authz, loggers.get('api-authorization'));
|
||||
initAppAuthorization(http, authz, loggers.get('app-authorization'), features);
|
||||
|
||||
return authz;
|
||||
}
|
||||
|
||||
start({ clusterClient, features }: AuthorizationServiceStartParams) {
|
||||
const allFeatures = features.getFeatures();
|
||||
validateFeaturePrivileges(allFeatures);
|
||||
validateReservedPrivileges(allFeatures);
|
||||
|
||||
this.registerPrivileges(clusterClient);
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.statusSubscription !== undefined) {
|
||||
this.statusSubscription.unsubscribe();
|
||||
this.statusSubscription = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private registerPrivileges(clusterClient: IClusterClient) {
|
||||
const RETRY_SCALE_DURATION = 100;
|
||||
const RETRY_TIMEOUT_MAX = 10000;
|
||||
const retries$ = new BehaviorSubject(0);
|
||||
let retryTimeout: NodeJS.Timeout;
|
||||
|
||||
// Register cluster privileges once Elasticsearch is available and Security plugin is enabled.
|
||||
this.statusSubscription = combineLatest([
|
||||
this.status.core$,
|
||||
this.license.features$,
|
||||
retries$.asObservable().pipe(
|
||||
// We shouldn't emit new value if retry counter is reset. This comparator isn't called for
|
||||
// the initial value.
|
||||
distinctUntilChanged((prev, curr) => prev === curr || curr === 0)
|
||||
),
|
||||
])
|
||||
.pipe(
|
||||
filter(
|
||||
([status]) =>
|
||||
this.license.isEnabled() && status.elasticsearch.level === ServiceStatusLevels.available
|
||||
)
|
||||
)
|
||||
.subscribe(async () => {
|
||||
// If status or license change occurred before retry timeout we should cancel it.
|
||||
if (retryTimeout) {
|
||||
clearTimeout(retryTimeout);
|
||||
}
|
||||
|
||||
try {
|
||||
await registerPrivilegesWithCluster(
|
||||
this.logger,
|
||||
this.privileges,
|
||||
this.applicationName,
|
||||
clusterClient
|
||||
);
|
||||
retries$.next(0);
|
||||
} catch (err) {
|
||||
const retriesElapsed = retries$.getValue() + 1;
|
||||
retryTimeout = setTimeout(
|
||||
() => retries$.next(retriesElapsed),
|
||||
Math.min(retriesElapsed * RETRY_SCALE_DURATION, RETRY_TIMEOUT_MAX)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -10,13 +10,13 @@ import { KibanaRequest, Logger } from '../../../../../src/core/server';
|
|||
import { Feature } from '../../../features/server';
|
||||
|
||||
import { CheckPrivilegesResponse } from './check_privileges';
|
||||
import { Authorization } from './index';
|
||||
import { AuthorizationServiceSetup } from '.';
|
||||
|
||||
export function disableUICapabilitiesFactory(
|
||||
request: KibanaRequest,
|
||||
features: Feature[],
|
||||
logger: Logger,
|
||||
authz: Authorization
|
||||
authz: AuthorizationServiceSetup
|
||||
) {
|
||||
const featureNavLinkIds = features
|
||||
.map((feature) => feature.navLinkId)
|
||||
|
|
|
@ -1,100 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
mockAuthorizationModeFactory,
|
||||
mockCheckPrivilegesDynamicallyWithRequestFactory,
|
||||
mockCheckPrivilegesWithRequestFactory,
|
||||
mockCheckSavedObjectsPrivilegesWithRequestFactory,
|
||||
mockPrivilegesFactory,
|
||||
} from './service.test.mocks';
|
||||
|
||||
import { checkPrivilegesWithRequestFactory } from './check_privileges';
|
||||
import { checkPrivilegesDynamicallyWithRequestFactory } from './check_privileges_dynamically';
|
||||
import { checkSavedObjectsPrivilegesWithRequestFactory } from './check_saved_objects_privileges';
|
||||
import { authorizationModeFactory } from './mode';
|
||||
import { privilegesFactory } from './privileges';
|
||||
import { setupAuthorization } from '.';
|
||||
|
||||
import {
|
||||
coreMock,
|
||||
elasticsearchServiceMock,
|
||||
loggingServiceMock,
|
||||
} from '../../../../../src/core/server/mocks';
|
||||
import { licenseMock } from '../../common/licensing/index.mock';
|
||||
|
||||
test(`returns exposed services`, () => {
|
||||
const kibanaIndexName = '.a-kibana-index';
|
||||
const application = `kibana-${kibanaIndexName}`;
|
||||
|
||||
const mockCheckPrivilegesWithRequest = Symbol();
|
||||
mockCheckPrivilegesWithRequestFactory.mockReturnValue(mockCheckPrivilegesWithRequest);
|
||||
|
||||
const mockCheckPrivilegesDynamicallyWithRequest = Symbol();
|
||||
mockCheckPrivilegesDynamicallyWithRequestFactory.mockReturnValue(
|
||||
mockCheckPrivilegesDynamicallyWithRequest
|
||||
);
|
||||
|
||||
const mockCheckSavedObjectsPrivilegesWithRequest = Symbol();
|
||||
mockCheckSavedObjectsPrivilegesWithRequestFactory.mockReturnValue(
|
||||
mockCheckSavedObjectsPrivilegesWithRequest
|
||||
);
|
||||
|
||||
const mockPrivilegesService = Symbol();
|
||||
mockPrivilegesFactory.mockReturnValue(mockPrivilegesService);
|
||||
const mockAuthorizationMode = Symbol();
|
||||
mockAuthorizationModeFactory.mockReturnValue(mockAuthorizationMode);
|
||||
|
||||
const mockClusterClient = elasticsearchServiceMock.createClusterClient();
|
||||
const mockGetSpacesService = jest
|
||||
.fn()
|
||||
.mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() });
|
||||
const mockFeaturesService = { getFeatures: () => [] };
|
||||
const mockLicense = licenseMock.create();
|
||||
|
||||
const authz = setupAuthorization({
|
||||
http: coreMock.createSetup().http,
|
||||
clusterClient: mockClusterClient,
|
||||
license: mockLicense,
|
||||
loggers: loggingServiceMock.create(),
|
||||
kibanaIndexName,
|
||||
packageVersion: 'some-version',
|
||||
featuresService: mockFeaturesService,
|
||||
getSpacesService: mockGetSpacesService,
|
||||
});
|
||||
|
||||
expect(authz.actions.version).toBe('version:some-version');
|
||||
expect(authz.applicationName).toBe(application);
|
||||
|
||||
expect(authz.checkPrivilegesWithRequest).toBe(mockCheckPrivilegesWithRequest);
|
||||
expect(checkPrivilegesWithRequestFactory).toHaveBeenCalledWith(
|
||||
authz.actions,
|
||||
mockClusterClient,
|
||||
authz.applicationName
|
||||
);
|
||||
|
||||
expect(authz.checkPrivilegesDynamicallyWithRequest).toBe(
|
||||
mockCheckPrivilegesDynamicallyWithRequest
|
||||
);
|
||||
expect(checkPrivilegesDynamicallyWithRequestFactory).toHaveBeenCalledWith(
|
||||
mockCheckPrivilegesWithRequest,
|
||||
mockGetSpacesService
|
||||
);
|
||||
|
||||
expect(authz.checkSavedObjectsPrivilegesWithRequest).toBe(
|
||||
mockCheckSavedObjectsPrivilegesWithRequest
|
||||
);
|
||||
expect(checkSavedObjectsPrivilegesWithRequestFactory).toHaveBeenCalledWith(
|
||||
mockCheckPrivilegesWithRequest,
|
||||
mockGetSpacesService
|
||||
);
|
||||
|
||||
expect(authz.privileges).toBe(mockPrivilegesService);
|
||||
expect(privilegesFactory).toHaveBeenCalledWith(authz.actions, mockFeaturesService, mockLicense);
|
||||
|
||||
expect(authz.mode).toBe(mockAuthorizationMode);
|
||||
expect(authorizationModeFactory).toHaveBeenCalledWith(mockLicense);
|
||||
});
|
|
@ -4,134 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { UICapabilities } from 'ui/capabilities';
|
||||
import {
|
||||
CoreSetup,
|
||||
LoggerFactory,
|
||||
KibanaRequest,
|
||||
IClusterClient,
|
||||
} from '../../../../../src/core/server';
|
||||
|
||||
import { FeaturesService, SpacesService } from '../plugin';
|
||||
import { Actions } from './actions';
|
||||
import { CheckPrivilegesWithRequest, checkPrivilegesWithRequestFactory } from './check_privileges';
|
||||
import {
|
||||
CheckPrivilegesDynamicallyWithRequest,
|
||||
checkPrivilegesDynamicallyWithRequestFactory,
|
||||
} from './check_privileges_dynamically';
|
||||
import {
|
||||
CheckSavedObjectsPrivilegesWithRequest,
|
||||
checkSavedObjectsPrivilegesWithRequestFactory,
|
||||
} from './check_saved_objects_privileges';
|
||||
import { AuthorizationMode, authorizationModeFactory } from './mode';
|
||||
import { privilegesFactory, PrivilegesService } from './privileges';
|
||||
import { initAppAuthorization } from './app_authorization';
|
||||
import { initAPIAuthorization } from './api_authorization';
|
||||
import { disableUICapabilitiesFactory } from './disable_ui_capabilities';
|
||||
import { validateFeaturePrivileges } from './validate_feature_privileges';
|
||||
import { validateReservedPrivileges } from './validate_reserved_privileges';
|
||||
import { registerPrivilegesWithCluster } from './register_privileges_with_cluster';
|
||||
import { APPLICATION_PREFIX } from '../../common/constants';
|
||||
import { SecurityLicense } from '../../common/licensing';
|
||||
|
||||
export { Actions } from './actions';
|
||||
export { AuthorizationService, AuthorizationServiceSetup } from './authorization_service';
|
||||
export { CheckSavedObjectsPrivileges } from './check_saved_objects_privileges';
|
||||
export { featurePrivilegeIterator } from './privileges';
|
||||
|
||||
interface SetupAuthorizationParams {
|
||||
packageVersion: string;
|
||||
http: CoreSetup['http'];
|
||||
clusterClient: IClusterClient;
|
||||
license: SecurityLicense;
|
||||
loggers: LoggerFactory;
|
||||
featuresService: FeaturesService;
|
||||
kibanaIndexName: string;
|
||||
getSpacesService(): SpacesService | undefined;
|
||||
}
|
||||
|
||||
export interface Authorization {
|
||||
actions: Actions;
|
||||
checkPrivilegesWithRequest: CheckPrivilegesWithRequest;
|
||||
checkPrivilegesDynamicallyWithRequest: CheckPrivilegesDynamicallyWithRequest;
|
||||
checkSavedObjectsPrivilegesWithRequest: CheckSavedObjectsPrivilegesWithRequest;
|
||||
applicationName: string;
|
||||
mode: AuthorizationMode;
|
||||
privileges: PrivilegesService;
|
||||
disableUnauthorizedCapabilities: (
|
||||
request: KibanaRequest,
|
||||
capabilities: UICapabilities
|
||||
) => Promise<UICapabilities>;
|
||||
registerPrivilegesWithCluster: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function setupAuthorization({
|
||||
http,
|
||||
packageVersion,
|
||||
clusterClient,
|
||||
license,
|
||||
loggers,
|
||||
featuresService,
|
||||
kibanaIndexName,
|
||||
getSpacesService,
|
||||
}: SetupAuthorizationParams): Authorization {
|
||||
const actions = new Actions(packageVersion);
|
||||
const mode = authorizationModeFactory(license);
|
||||
const applicationName = `${APPLICATION_PREFIX}${kibanaIndexName}`;
|
||||
const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory(
|
||||
actions,
|
||||
clusterClient,
|
||||
applicationName
|
||||
);
|
||||
const privileges = privilegesFactory(actions, featuresService, license);
|
||||
const logger = loggers.get('authorization');
|
||||
|
||||
const authz = {
|
||||
actions,
|
||||
applicationName,
|
||||
checkPrivilegesWithRequest,
|
||||
checkPrivilegesDynamicallyWithRequest: checkPrivilegesDynamicallyWithRequestFactory(
|
||||
checkPrivilegesWithRequest,
|
||||
getSpacesService
|
||||
),
|
||||
checkSavedObjectsPrivilegesWithRequest: checkSavedObjectsPrivilegesWithRequestFactory(
|
||||
checkPrivilegesWithRequest,
|
||||
getSpacesService
|
||||
),
|
||||
mode,
|
||||
privileges,
|
||||
|
||||
async disableUnauthorizedCapabilities(request: KibanaRequest, capabilities: UICapabilities) {
|
||||
// If we have a license which doesn't enable security, or we're a legacy user we shouldn't
|
||||
// disable any ui capabilities
|
||||
if (!mode.useRbacForRequest(request)) {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
const disableUICapabilities = disableUICapabilitiesFactory(
|
||||
request,
|
||||
featuresService.getFeatures(),
|
||||
logger,
|
||||
authz
|
||||
);
|
||||
|
||||
if (!request.auth.isAuthenticated) {
|
||||
return disableUICapabilities.all(capabilities);
|
||||
}
|
||||
|
||||
return await disableUICapabilities.usingPrivileges(capabilities);
|
||||
},
|
||||
|
||||
registerPrivilegesWithCluster: async () => {
|
||||
const features = featuresService.getFeatures();
|
||||
validateFeaturePrivileges(features);
|
||||
validateReservedPrivileges(features);
|
||||
|
||||
await registerPrivilegesWithCluster(logger, privileges, applicationName, clusterClient);
|
||||
},
|
||||
};
|
||||
|
||||
initAPIAuthorization(http, authz, loggers.get('api-authorization'));
|
||||
initAppAuthorization(http, authz, loggers.get('app-authorization'), featuresService);
|
||||
|
||||
return authz;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import { Feature } from '../../../../features/server';
|
|||
import { Actions } from '../actions';
|
||||
import { privilegesFactory } from './privileges';
|
||||
|
||||
import { featuresPluginMock } from '../../../../features/server/mocks';
|
||||
|
||||
const actions = new Actions('1.0.0-zeta1');
|
||||
|
||||
describe('features', () => {
|
||||
|
@ -42,7 +44,9 @@ describe('features', () => {
|
|||
}),
|
||||
];
|
||||
|
||||
const mockFeaturesService = { getFeatures: jest.fn().mockReturnValue(features) };
|
||||
const mockFeaturesService = featuresPluginMock.createSetup();
|
||||
mockFeaturesService.getFeatures.mockReturnValue(features);
|
||||
|
||||
const mockLicenseService = {
|
||||
getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }),
|
||||
};
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
|
||||
import { uniq } from 'lodash';
|
||||
import { SecurityLicense } from '../../../common/licensing';
|
||||
import { Feature } from '../../../../features/server';
|
||||
import { Feature, PluginSetupContract as FeaturesPluginSetup } from '../../../../features/server';
|
||||
import { RawKibanaPrivileges } from '../../../common/model';
|
||||
import { Actions } from '../actions';
|
||||
import { featurePrivilegeBuilderFactory } from './feature_privilege_builder';
|
||||
import { FeaturesService } from '../../plugin';
|
||||
import {
|
||||
featurePrivilegeIterator,
|
||||
subFeaturePrivilegeIterator,
|
||||
|
@ -22,7 +21,7 @@ export interface PrivilegesService {
|
|||
|
||||
export function privilegesFactory(
|
||||
actions: Actions,
|
||||
featuresService: FeaturesService,
|
||||
featuresService: FeaturesPluginSetup,
|
||||
licenseService: Pick<SecurityLicense, 'getFeatures'>
|
||||
) {
|
||||
const featurePrivilegeBuilder = featurePrivilegeBuilderFactory(actions);
|
||||
|
|
|
@ -49,7 +49,7 @@ const registerPrivilegesWithClusterTest = (
|
|||
});
|
||||
for (const deletedPrivilege of deletedPrivileges) {
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
`Deleting Kibana Privilege ${deletedPrivilege} from Elasticearch for ${application}`
|
||||
`Deleting Kibana Privilege ${deletedPrivilege} from Elasticsearch for ${application}`
|
||||
);
|
||||
expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith(
|
||||
'shield.deletePrivilege',
|
||||
|
@ -82,7 +82,7 @@ const registerPrivilegesWithClusterTest = (
|
|||
`Registering Kibana Privileges with Elasticsearch for ${application}`
|
||||
);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
`Kibana Privileges already registered with Elasticearch for ${application}`
|
||||
`Kibana Privileges already registered with Elasticsearch for ${application}`
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -61,14 +61,14 @@ export async function registerPrivilegesWithCluster(
|
|||
privilege: application,
|
||||
});
|
||||
if (arePrivilegesEqual(existingPrivileges, expectedPrivileges)) {
|
||||
logger.debug(`Kibana Privileges already registered with Elasticearch for ${application}`);
|
||||
logger.debug(`Kibana Privileges already registered with Elasticsearch for ${application}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const privilegesToDelete = getPrivilegesToDelete(existingPrivileges, expectedPrivileges);
|
||||
for (const privilegeToDelete of privilegesToDelete) {
|
||||
logger.debug(
|
||||
`Deleting Kibana Privilege ${privilegeToDelete} from Elasticearch for ${application}`
|
||||
`Deleting Kibana Privilege ${privilegeToDelete} from Elasticsearch for ${application}`
|
||||
);
|
||||
try {
|
||||
await clusterClient.callAsInternalUser('shield.deletePrivilege', {
|
||||
|
|
|
@ -28,3 +28,8 @@ export const mockAuthorizationModeFactory = jest.fn();
|
|||
jest.mock('./mode', () => ({
|
||||
authorizationModeFactory: mockAuthorizationModeFactory,
|
||||
}));
|
||||
|
||||
export const mockRegisterPrivilegesWithCluster = jest.fn();
|
||||
jest.mock('./register_privileges_with_cluster', () => ({
|
||||
registerPrivilegesWithCluster: mockRegisterPrivilegesWithCluster,
|
||||
}));
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SecurityPluginSetup } from './plugin';
|
||||
|
||||
import { authenticationMock } from './authentication/index.mock';
|
||||
import { authorizationMock } from './authorization/index.mock';
|
||||
import { licenseMock } from '../common/licensing/index.mock';
|
||||
|
@ -23,7 +21,6 @@ function createSetupMock() {
|
|||
},
|
||||
registerSpacesService: jest.fn(),
|
||||
license: licenseMock.create(),
|
||||
__legacyCompat: {} as SecurityPluginSetup['__legacyCompat'],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -50,9 +50,6 @@ describe('Security Plugin', () => {
|
|||
it('exposes proper contract', async () => {
|
||||
await expect(plugin.setup(mockCoreSetup, mockDependencies)).resolves.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"__legacyCompat": Object {
|
||||
"registerPrivilegesWithCluster": [Function],
|
||||
},
|
||||
"audit": Object {
|
||||
"getLogger": [Function],
|
||||
},
|
||||
|
|
|
@ -8,22 +8,22 @@ import { combineLatest } from 'rxjs';
|
|||
import { first, map } from 'rxjs/operators';
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import {
|
||||
deepFreeze,
|
||||
ICustomClusterClient,
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Logger,
|
||||
PluginInitializerContext,
|
||||
CoreStart,
|
||||
} from '../../../../src/core/server';
|
||||
import { deepFreeze } from '../../../../src/core/server';
|
||||
import { SpacesPluginSetup } from '../../spaces/server';
|
||||
import {
|
||||
PluginSetupContract as FeaturesSetupContract,
|
||||
PluginStartContract as FeaturesStartContract,
|
||||
PluginSetupContract as FeaturesPluginSetup,
|
||||
PluginStartContract as FeaturesPluginStart,
|
||||
} from '../../features/server';
|
||||
import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/server';
|
||||
|
||||
import { Authentication, setupAuthentication } from './authentication';
|
||||
import { Authorization, setupAuthorization } from './authorization';
|
||||
import { AuthorizationService, AuthorizationServiceSetup } from './authorization';
|
||||
import { ConfigSchema, createConfig } from './config';
|
||||
import { defineRoutes } from './routes';
|
||||
import { SecurityLicenseService, SecurityLicense } from '../common/licensing';
|
||||
|
@ -37,8 +37,6 @@ export type SpacesService = Pick<
|
|||
'getSpaceId' | 'namespaceToSpaceId'
|
||||
>;
|
||||
|
||||
export type FeaturesService = Pick<FeaturesSetupContract, 'getFeatures'>;
|
||||
|
||||
/**
|
||||
* Describes public Security plugin contract returned at the `setup` stage.
|
||||
*/
|
||||
|
@ -53,7 +51,7 @@ export interface SecurityPluginSetup {
|
|||
| 'grantAPIKeyAsInternalUser'
|
||||
| 'invalidateAPIKeyAsInternalUser'
|
||||
>;
|
||||
authz: Pick<Authorization, 'actions' | 'checkPrivilegesWithRequest' | 'mode'>;
|
||||
authz: Pick<AuthorizationServiceSetup, 'actions' | 'checkPrivilegesWithRequest' | 'mode'>;
|
||||
license: SecurityLicense;
|
||||
audit: Pick<AuditServiceSetup, 'getLogger'>;
|
||||
|
||||
|
@ -66,19 +64,15 @@ export interface SecurityPluginSetup {
|
|||
* @param service Spaces service exposed by the Spaces plugin.
|
||||
*/
|
||||
registerSpacesService: (service: SpacesService) => void;
|
||||
|
||||
__legacyCompat: {
|
||||
registerPrivilegesWithCluster: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginSetupDependencies {
|
||||
features: FeaturesService;
|
||||
features: FeaturesPluginSetup;
|
||||
licensing: LicensingPluginSetup;
|
||||
}
|
||||
|
||||
export interface PluginStartDependencies {
|
||||
features: FeaturesStartContract;
|
||||
features: FeaturesPluginStart;
|
||||
licensing: LicensingPluginStart;
|
||||
}
|
||||
|
||||
|
@ -101,6 +95,7 @@ export class Plugin {
|
|||
};
|
||||
|
||||
private readonly auditService = new AuditService(this.initializerContext.logger.get('audit'));
|
||||
private readonly authorizationService = new AuthorizationService();
|
||||
|
||||
private readonly getSpacesService = () => {
|
||||
// Changing property value from Symbol to undefined denotes the fact that property was accessed.
|
||||
|
@ -156,15 +151,17 @@ export class Plugin {
|
|||
loggers: this.initializerContext.logger,
|
||||
});
|
||||
|
||||
const authz = await setupAuthorization({
|
||||
const authz = this.authorizationService.setup({
|
||||
http: core.http,
|
||||
capabilities: core.capabilities,
|
||||
status: core.status,
|
||||
clusterClient: this.clusterClient,
|
||||
license,
|
||||
loggers: this.initializerContext.logger,
|
||||
kibanaIndexName: legacyConfig.kibana.index,
|
||||
packageVersion: this.initializerContext.env.packageInfo.version,
|
||||
getSpacesService: this.getSpacesService,
|
||||
featuresService: features,
|
||||
features,
|
||||
});
|
||||
|
||||
setupSavedObjects({
|
||||
|
@ -174,8 +171,6 @@ export class Plugin {
|
|||
getSpacesService: this.getSpacesService,
|
||||
});
|
||||
|
||||
core.capabilities.registerSwitcher(authz.disableUnauthorizedCapabilities);
|
||||
|
||||
defineRoutes({
|
||||
router: core.http.createRouter(),
|
||||
basePath: core.http.basePath,
|
||||
|
@ -223,18 +218,15 @@ export class Plugin {
|
|||
|
||||
this.spacesService = service;
|
||||
},
|
||||
|
||||
__legacyCompat: {
|
||||
registerPrivilegesWithCluster: async () => await authz.registerPrivilegesWithCluster(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start(core: CoreStart, { licensing }: PluginStartDependencies) {
|
||||
public start(core: CoreStart, { features, licensing }: PluginStartDependencies) {
|
||||
this.logger.debug('Starting plugin');
|
||||
this.featureUsageServiceStart = this.featureUsageService.start({
|
||||
featureUsage: licensing.featureUsage,
|
||||
});
|
||||
this.authorizationService.start({ features, clusterClient: this.clusterClient! });
|
||||
}
|
||||
|
||||
public stop() {
|
||||
|
@ -254,6 +246,7 @@ export class Plugin {
|
|||
this.featureUsageServiceStart = undefined;
|
||||
}
|
||||
this.auditService.stop();
|
||||
this.authorizationService.stop();
|
||||
}
|
||||
|
||||
private wasSpacesServiceAccessed() {
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
} from '../../../../../src/core/server';
|
||||
import { SecurityLicense } from '../../common/licensing';
|
||||
import { Authentication } from '../authentication';
|
||||
import { Authorization } from '../authorization';
|
||||
import { AuthorizationServiceSetup } from '../authorization';
|
||||
import { ConfigType } from '../config';
|
||||
|
||||
import { defineAuthenticationRoutes } from './authentication';
|
||||
|
@ -37,7 +37,7 @@ export interface RouteDefinitionParams {
|
|||
clusterClient: IClusterClient;
|
||||
config: ConfigType;
|
||||
authc: Authentication;
|
||||
authz: Authorization;
|
||||
authz: AuthorizationServiceSetup;
|
||||
license: SecurityLicense;
|
||||
getFeatures: () => Promise<Feature[]>;
|
||||
getFeatureUsageService: () => SecurityFeatureUsageServiceStart;
|
||||
|
|
|
@ -11,13 +11,16 @@ import {
|
|||
SavedObjectsClient,
|
||||
} from '../../../../../src/core/server';
|
||||
import { SecureSavedObjectsClientWrapper } from './secure_saved_objects_client_wrapper';
|
||||
import { Authorization } from '../authorization';
|
||||
import { AuthorizationServiceSetup } from '../authorization';
|
||||
import { SecurityAuditLogger } from '../audit';
|
||||
import { SpacesService } from '../plugin';
|
||||
|
||||
interface SetupSavedObjectsParams {
|
||||
auditLogger: SecurityAuditLogger;
|
||||
authz: Pick<Authorization, 'mode' | 'actions' | 'checkSavedObjectsPrivilegesWithRequest'>;
|
||||
authz: Pick<
|
||||
AuthorizationServiceSetup,
|
||||
'mode' | 'actions' | 'checkSavedObjectsPrivilegesWithRequest'
|
||||
>;
|
||||
savedObjects: CoreSetup['savedObjects'];
|
||||
getSpacesService(): SpacesService | undefined;
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ In another shell, from **~kibana/x-pack**:
|
|||
|
||||
If instead you need to run API tests, start up the test server and then in another shell, from **~kibana/x-pack**:
|
||||
|
||||
`node ../scripts/functional_test_runner.js --config test/api_integration/config.js --grep="{TEST_NAME}"`.
|
||||
`node ../scripts/functional_test_runner.js --config test/api_integration/config.ts --grep="{TEST_NAME}"`.
|
||||
|
||||
You can update snapshots by prefixing the runner command with `env UPDATE_UPTIME_FIXTURES=1`
|
||||
|
||||
|
|
|
@ -11,8 +11,9 @@ require('@kbn/test').runTestsCli([
|
|||
require.resolve('../test/functional_with_es_ssl/config.ts'),
|
||||
require.resolve('../test/functional/config_security_basic.ts'),
|
||||
require.resolve('../test/functional/config_security_trial.ts'),
|
||||
require.resolve('../test/api_integration/config_security_basic.js'),
|
||||
require.resolve('../test/api_integration/config.js'),
|
||||
require.resolve('../test/api_integration/config_security_basic.ts'),
|
||||
require.resolve('../test/api_integration/config_security_trial.ts'),
|
||||
require.resolve('../test/api_integration/config.ts'),
|
||||
require.resolve('../test/alerting_api_integration/basic/config.ts'),
|
||||
require.resolve('../test/alerting_api_integration/spaces_only/config.ts'),
|
||||
require.resolve('../test/alerting_api_integration/security_and_spaces/config.ts'),
|
||||
|
|
|
@ -40,7 +40,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
|
||||
return async ({ readConfigFile }: FtrConfigProviderContext) => {
|
||||
const xPackApiIntegrationTestsConfig = await readConfigFile(
|
||||
require.resolve('../../api_integration/config.js')
|
||||
require.resolve('../../api_integration/config.ts')
|
||||
);
|
||||
const servers = {
|
||||
...xPackApiIntegrationTestsConfig.get('servers'),
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect/expect.js';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('Privileges registration', function () {
|
||||
this.tags(['skipCloud']);
|
||||
|
||||
it('privileges are re-registered on license downgrade', async () => {
|
||||
// Verify currently registered privileges for TRIAL license.
|
||||
// If you're adding a privilege to the following, that's great!
|
||||
// If you're removing a privilege, this breaks backwards compatibility
|
||||
// Roles are associated with these privileges, and we shouldn't be removing them in a minor version.
|
||||
const expectedTrialLicenseDiscoverPrivileges = [
|
||||
'all',
|
||||
'read',
|
||||
'minimal_all',
|
||||
'minimal_read',
|
||||
'url_create',
|
||||
];
|
||||
const trialPrivileges = await supertest
|
||||
.get('/api/security/privileges')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(trialPrivileges.body.features.discover).to.eql(expectedTrialLicenseDiscoverPrivileges);
|
||||
|
||||
// Revert license to basic.
|
||||
await supertest
|
||||
.post('/api/license/start_basic?acknowledge=true')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.expect(200, {
|
||||
basic_was_started: true,
|
||||
acknowledged: true,
|
||||
});
|
||||
|
||||
// Verify that privileges were re-registered.
|
||||
const expectedBasicLicenseDiscoverPrivileges = ['all', 'read'];
|
||||
const basicPrivileges = await supertest
|
||||
.get('/api/security/privileges')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(basicPrivileges.body.features.discover).to.eql(expectedBasicLicenseDiscoverPrivileges);
|
||||
});
|
||||
});
|
||||
}
|
16
x-pack/test/api_integration/apis/security/security_trial.ts
Normal file
16
x-pack/test/api_integration/apis/security/security_trial.ts
Normal file
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('security (trial license)', function () {
|
||||
this.tags('ciGroup6');
|
||||
|
||||
// THIS TEST NEEDS TO BE LAST. IT IS DESTRUCTIVE! IT REMOVES TRIAL LICENSE!!!
|
||||
loadTestFile(require.resolve('./license_downgrade'));
|
||||
});
|
||||
}
|
|
@ -4,9 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
||||
import { services } from './services';
|
||||
|
||||
export async function getApiIntegrationConfig({ readConfigFile }) {
|
||||
export async function getApiIntegrationConfig({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const xPackFunctionalTestsConfig = await readConfigFile(
|
||||
require.resolve('../functional/config.js')
|
||||
);
|
|
@ -4,11 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable import/no-default-export */
|
||||
|
||||
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
||||
import { default as createTestConfig } from './config';
|
||||
|
||||
export default async function ({ readConfigFile }) {
|
||||
//security APIs should function the same under a basic or trial license
|
||||
return createTestConfig({ readConfigFile }).then((config) => {
|
||||
export default async function (context: FtrConfigProviderContext) {
|
||||
// security APIs should function the same under a basic or trial license
|
||||
return createTestConfig(context).then((config) => {
|
||||
config.esTestCluster.license = 'basic';
|
||||
config.esTestCluster.serverArgs = [
|
||||
'xpack.license.self_generated.type=basic',
|
17
x-pack/test/api_integration/config_security_trial.ts
Normal file
17
x-pack/test/api_integration/config_security_trial.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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable import/no-default-export */
|
||||
|
||||
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
||||
import { default as createTestConfig } from './config';
|
||||
|
||||
export default async function (context: FtrConfigProviderContext) {
|
||||
return createTestConfig(context).then((config) => {
|
||||
config.testFiles = [require.resolve('./apis/security/security_trial')];
|
||||
return config;
|
||||
});
|
||||
}
|
|
@ -17,7 +17,7 @@ export function createTestConfig(settings: Settings) {
|
|||
|
||||
return async ({ readConfigFile }: FtrConfigProviderContext) => {
|
||||
const xPackAPITestsConfig = await readConfigFile(
|
||||
require.resolve('../../api_integration/config.js')
|
||||
require.resolve('../../api_integration/config.ts')
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -39,7 +39,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
|
||||
return async ({ readConfigFile }: FtrConfigProviderContext) => {
|
||||
const xPackApiIntegrationTestsConfig = await readConfigFile(
|
||||
require.resolve('../../api_integration/config.js')
|
||||
require.resolve('../../api_integration/config.ts')
|
||||
);
|
||||
|
||||
const servers = {
|
||||
|
|
|
@ -42,7 +42,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
|
||||
return async ({ readConfigFile }: FtrConfigProviderContext) => {
|
||||
const xPackApiIntegrationTestsConfig = await readConfigFile(
|
||||
require.resolve('../../api_integration/config.js')
|
||||
require.resolve('../../api_integration/config.ts')
|
||||
);
|
||||
const servers = {
|
||||
...xPackApiIntegrationTestsConfig.get('servers'),
|
||||
|
|
|
@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
|||
import { services } from './services';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js'));
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
|
||||
return {
|
||||
testFiles: [require.resolve('./tests')],
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js'));
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
|
||||
return {
|
||||
...xPackAPITestsConfig.getAll(),
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js'));
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
|
||||
return {
|
||||
testFiles: [require.resolve('./apis')],
|
||||
|
|
|
@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
|||
import { services } from './services';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js'));
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
|
||||
const kerberosKeytabPath = resolve(
|
||||
__dirname,
|
||||
|
|
|
@ -12,7 +12,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
const kibanaAPITestsConfig = await readConfigFile(
|
||||
require.resolve('../../../test/api_integration/config.js')
|
||||
);
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js'));
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port');
|
||||
|
||||
const kerberosKeytabPath = resolve(__dirname, '../kerberos_api_integration/fixtures/krb5.keytab');
|
||||
|
|
|
@ -17,7 +17,7 @@ export function createTestConfig(settings: Settings) {
|
|||
|
||||
return async ({ readConfigFile }: FtrConfigProviderContext) => {
|
||||
const xPackAPITestsConfig = await readConfigFile(
|
||||
require.resolve('../../api_integration/config.js')
|
||||
require.resolve('../../api_integration/config.ts')
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
|||
import { services } from './services';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js'));
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
const plugin = resolve(__dirname, './fixtures/oidc_provider');
|
||||
const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port');
|
||||
const jwksPath = resolve(__dirname, './fixtures/jwks.json');
|
||||
|
|
|
@ -10,7 +10,7 @@ import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
|
|||
import { services } from './services';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js'));
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
|
||||
const servers = {
|
||||
...xPackAPITestsConfig.get('servers'),
|
||||
|
|
|
@ -11,7 +11,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
const kibanaAPITestsConfig = await readConfigFile(
|
||||
require.resolve('../../../test/api_integration/config.js')
|
||||
);
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js'));
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
|
||||
const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port');
|
||||
const idpPath = resolve(__dirname, '../../test/saml_api_integration/fixtures/idp_metadata.xml');
|
||||
|
|
|
@ -24,7 +24,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
functional: await readConfigFile(require.resolve('../../../../test/functional/config.js')),
|
||||
},
|
||||
xpack: {
|
||||
api: await readConfigFile(require.resolve('../../api_integration/config.js')),
|
||||
api: await readConfigFile(require.resolve('../../api_integration/config.ts')),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
functional: await readConfigFile(require.resolve('../../../../test/functional/config.js')),
|
||||
},
|
||||
xpack: {
|
||||
api: await readConfigFile(require.resolve('../../api_integration/config.js')),
|
||||
api: await readConfigFile(require.resolve('../../api_integration/config.ts')),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
export default async function ({ readConfigFile }) {
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js'));
|
||||
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
|
||||
|
||||
return {
|
||||
testFiles: [require.resolve('./auth')],
|
||||
|
|
|
@ -27,9 +27,6 @@
|
|||
"plugins/xpack_main/*": [
|
||||
"x-pack/legacy/plugins/xpack_main/public/*"
|
||||
],
|
||||
"plugins/security/*": [
|
||||
"x-pack/legacy/plugins/security/public/*"
|
||||
],
|
||||
"plugins/spaces/*": [
|
||||
"x-pack/legacy/plugins/spaces/public/*"
|
||||
],
|
||||
|
|
2
x-pack/typings/hapi.d.ts
vendored
2
x-pack/typings/hapi.d.ts
vendored
|
@ -7,7 +7,6 @@
|
|||
import 'hapi';
|
||||
|
||||
import { XPackMainPlugin } from '../legacy/plugins/xpack_main/server/xpack_main';
|
||||
import { SecurityPlugin } from '../legacy/plugins/security';
|
||||
import { ActionsPlugin, ActionsClient } from '../plugins/actions/server';
|
||||
import { AlertingPlugin, AlertsClient } from '../plugins/alerts/server';
|
||||
import { TaskManager } from '../plugins/task_manager/server';
|
||||
|
@ -19,7 +18,6 @@ declare module 'hapi' {
|
|||
}
|
||||
interface PluginProperties {
|
||||
xpack_main: XPackMainPlugin;
|
||||
security?: SecurityPlugin;
|
||||
actions?: ActionsPlugin;
|
||||
alerts?: AlertingPlugin;
|
||||
task_manager?: TaskManager;
|
||||
|
|
Loading…
Reference in a new issue