[7.x] Migrate Security and EncryptedSavedObjects test plugins to the Kibana Platform (#61864)
This commit is contained in:
parent
b7f1c29181
commit
07f5798625
20 changed files with 249 additions and 220 deletions
|
@ -28,6 +28,7 @@ require('@kbn/test').runTestsCli([
|
|||
require.resolve('../test/oidc_api_integration/implicit_flow.config'),
|
||||
require.resolve('../test/pki_api_integration/config'),
|
||||
require.resolve('../test/login_selector_api_integration/config'),
|
||||
require.resolve('../test/encrypted_saved_objects_api_integration/config'),
|
||||
require.resolve('../test/spaces_api_integration/spaces_only/config'),
|
||||
require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial'),
|
||||
require.resolve('../test/spaces_api_integration/security_and_spaces/config_basic'),
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { resolve } from 'path';
|
||||
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'));
|
||||
|
||||
return {
|
||||
testFiles: [require.resolve('./tests')],
|
||||
servers: xPackAPITestsConfig.get('servers'),
|
||||
services,
|
||||
junit: {
|
||||
reportName: 'X-Pack Encrypted Saved Objects API Integration Tests',
|
||||
},
|
||||
esTestCluster: xPackAPITestsConfig.get('esTestCluster'),
|
||||
kbnTestServer: {
|
||||
...xPackAPITestsConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...xPackAPITestsConfig.get('kbnTestServer.serverArgs'),
|
||||
`--plugin-path=${resolve(__dirname, './fixtures/api_consumer_plugin')}`,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"id": "eso",
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"requiredPlugins": ["encryptedSavedObjects", "spaces"],
|
||||
"server": true,
|
||||
"ui": false
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 { CoreSetup, PluginInitializer } from '../../../../../../src/core/server';
|
||||
import { deepFreeze } from '../../../../../../src/core/utils';
|
||||
import {
|
||||
EncryptedSavedObjectsPluginSetup,
|
||||
EncryptedSavedObjectsPluginStart,
|
||||
} from '../../../../../plugins/encrypted_saved_objects/server';
|
||||
import { SpacesPluginSetup } from '../../../../../plugins/spaces/server';
|
||||
|
||||
const SAVED_OBJECT_WITH_SECRET_TYPE = 'saved-object-with-secret';
|
||||
|
||||
interface PluginsSetup {
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
|
||||
spaces: SpacesPluginSetup;
|
||||
}
|
||||
|
||||
interface PluginsStart {
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
|
||||
spaces: never;
|
||||
}
|
||||
|
||||
export const plugin: PluginInitializer<void, void, PluginsSetup, PluginsStart> = () => ({
|
||||
setup(core: CoreSetup<PluginsStart>, deps) {
|
||||
core.savedObjects.registerType({
|
||||
name: SAVED_OBJECT_WITH_SECRET_TYPE,
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
mappings: deepFreeze({
|
||||
properties: {
|
||||
publicProperty: { type: 'keyword' },
|
||||
publicPropertyExcludedFromAAD: { type: 'keyword' },
|
||||
privateProperty: { type: 'binary' },
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
deps.encryptedSavedObjects.registerType({
|
||||
type: SAVED_OBJECT_WITH_SECRET_TYPE,
|
||||
attributesToEncrypt: new Set(['privateProperty']),
|
||||
attributesToExcludeFromAAD: new Set(['publicPropertyExcludedFromAAD']),
|
||||
});
|
||||
|
||||
core.http.createRouter().get(
|
||||
{
|
||||
path: '/api/saved_objects/get-decrypted-as-internal-user/{id}',
|
||||
validate: { params: value => ({ value }) },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const [, { encryptedSavedObjects }] = await core.getStartServices();
|
||||
const spaceId = deps.spaces.spacesService.getSpaceId(request);
|
||||
const namespace = deps.spaces.spacesService.spaceIdToNamespace(spaceId);
|
||||
|
||||
try {
|
||||
return response.ok({
|
||||
body: await encryptedSavedObjects.getDecryptedAsInternalUser(
|
||||
SAVED_OBJECT_WITH_SECRET_TYPE,
|
||||
request.params.id,
|
||||
{ namespace }
|
||||
),
|
||||
});
|
||||
} catch (err) {
|
||||
if (encryptedSavedObjects.isEncryptionError(err)) {
|
||||
return response.badRequest({ body: 'Failed to encrypt attributes' });
|
||||
}
|
||||
|
||||
return response.customError({ body: err, statusCode: 500 });
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
start() {},
|
||||
stop() {},
|
||||
});
|
11
x-pack/test/encrypted_saved_objects_api_integration/ftr_provider_context.d.ts
vendored
Normal file
11
x-pack/test/encrypted_saved_objects_api_integration/ftr_provider_context.d.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 { GenericFtrProviderContext } from '@kbn/test/types/ftr';
|
||||
|
||||
import { services } from './services';
|
||||
|
||||
export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { services } from '../api_integration/services';
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { SavedObject } from 'src/core/server';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
const es = getService('legacyEs');
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function({ loadTestFile }: FtrProviderContext) {
|
||||
describe('encryptedSavedObjects', function encryptedSavedObjectsSuite() {
|
|
@ -130,11 +130,6 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
|
|||
saml2: { order: 5, realm: 'saml2', maxRedirectURLSize: '100b' },
|
||||
},
|
||||
})}`,
|
||||
'--server.xsrf.whitelist',
|
||||
JSON.stringify([
|
||||
'/api/oidc_provider/token_endpoint',
|
||||
'/api/oidc_provider/userinfo_endpoint',
|
||||
]),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -51,12 +51,6 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
|
|||
`--plugin-path=${plugin}`,
|
||||
'--xpack.security.authc.providers=["oidc"]',
|
||||
'--xpack.security.authc.oidc.realm="oidc1"',
|
||||
'--server.xsrf.whitelist',
|
||||
JSON.stringify([
|
||||
'/api/security/oidc/initiate_login',
|
||||
'/api/oidc_provider/token_endpoint',
|
||||
'/api/oidc_provider/userinfo_endpoint',
|
||||
]),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,104 +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 Joi from 'joi';
|
||||
import { createTokens } from '../oidc_tools';
|
||||
|
||||
export function initRoutes(server) {
|
||||
let nonce = '';
|
||||
|
||||
server.route({
|
||||
path: '/api/oidc_provider/setup',
|
||||
method: 'POST',
|
||||
config: {
|
||||
auth: false,
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
nonce: Joi.string().required(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
handler: request => {
|
||||
nonce = request.payload.nonce;
|
||||
return {};
|
||||
},
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/api/oidc_provider/token_endpoint',
|
||||
method: 'POST',
|
||||
// Token endpoint needs authentication (with the client credentials) but we don't attempt to
|
||||
// validate this OIDC behavior here
|
||||
config: {
|
||||
auth: false,
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
grant_type: Joi.string().optional(),
|
||||
code: Joi.string().optional(),
|
||||
redirect_uri: Joi.string().optional(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request) {
|
||||
const userId = request.payload.code.substring(4);
|
||||
const { accessToken, idToken } = createTokens(userId, nonce);
|
||||
try {
|
||||
const userId = request.payload.code.substring(4);
|
||||
return {
|
||||
access_token: accessToken,
|
||||
token_type: 'Bearer',
|
||||
refresh_token: `valid-refresh-token${userId}`,
|
||||
expires_in: 3600,
|
||||
id_token: idToken,
|
||||
};
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/api/oidc_provider/userinfo_endpoint',
|
||||
method: 'GET',
|
||||
config: {
|
||||
auth: false,
|
||||
},
|
||||
handler: request => {
|
||||
const accessToken = request.headers.authorization.substring(7);
|
||||
if (accessToken === 'valid-access-token1') {
|
||||
return {
|
||||
sub: 'user1',
|
||||
name: 'Tony Stark',
|
||||
given_name: 'Tony',
|
||||
family_name: 'Stark',
|
||||
preferred_username: 'ironman',
|
||||
email: 'ironman@avengers.com',
|
||||
};
|
||||
}
|
||||
if (accessToken === 'valid-access-token2') {
|
||||
return {
|
||||
sub: 'user2',
|
||||
name: 'Peter Parker',
|
||||
given_name: 'Peter',
|
||||
family_name: 'Parker',
|
||||
preferred_username: 'spiderman',
|
||||
email: 'spiderman@avengers.com',
|
||||
};
|
||||
}
|
||||
if (accessToken === 'valid-access-token3') {
|
||||
return {
|
||||
sub: 'user3',
|
||||
name: 'Bruce Banner',
|
||||
given_name: 'Bruce',
|
||||
family_name: 'Banner',
|
||||
preferred_username: 'hulk',
|
||||
email: 'hulk@avengers.com',
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
});
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"id": "oidc_provider_plugin",
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"server": true,
|
||||
"ui": false
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"name": "oidc_provider_plugin",
|
||||
"version": "1.0.0",
|
||||
"kibana": {
|
||||
"version": "kibana",
|
||||
"templateVersion": "1.0.0"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"joi": "^13.5.2",
|
||||
"jsonwebtoken": "^8.3.0"
|
||||
}
|
||||
}
|
|
@ -4,16 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { PluginInitializer } from '../../../../../../src/core/server';
|
||||
import { initRoutes } from './init_routes';
|
||||
|
||||
export default function(kibana) {
|
||||
return new kibana.Plugin({
|
||||
name: 'oidcProvider',
|
||||
id: 'oidcProvider',
|
||||
require: ['elasticsearch'],
|
||||
|
||||
init(server) {
|
||||
initRoutes(server);
|
||||
},
|
||||
});
|
||||
}
|
||||
export const plugin: PluginInitializer<void, void> = () => ({
|
||||
setup: core => initRoutes(core.http.createRouter()),
|
||||
start: () => {},
|
||||
stop: () => {},
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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 { IRouter } from '../../../../../../src/core/server';
|
||||
import { createTokens } from '../../oidc_tools';
|
||||
|
||||
export function initRoutes(router: IRouter) {
|
||||
let nonce = '';
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/api/oidc_provider/setup',
|
||||
validate: { body: value => ({ value }) },
|
||||
options: { authRequired: false },
|
||||
},
|
||||
(context, request, response) => {
|
||||
nonce = request.body.nonce;
|
||||
return response.ok({ body: {} });
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/api/oidc_provider/token_endpoint',
|
||||
validate: { body: value => ({ value }) },
|
||||
// Token endpoint needs authentication (with the client credentials) but we don't attempt to
|
||||
// validate this OIDC behavior here
|
||||
options: { authRequired: false, xsrfRequired: false },
|
||||
},
|
||||
(context, request, response) => {
|
||||
const userId = request.body.code.substring(4);
|
||||
const { accessToken, idToken } = createTokens(userId, nonce);
|
||||
return response.ok({
|
||||
body: {
|
||||
access_token: accessToken,
|
||||
token_type: 'Bearer',
|
||||
refresh_token: `valid-refresh-token${userId}`,
|
||||
expires_in: 3600,
|
||||
id_token: idToken,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/api/oidc_provider/userinfo_endpoint',
|
||||
validate: false,
|
||||
options: { authRequired: false },
|
||||
},
|
||||
(context, request, response) => {
|
||||
const accessToken = (request.headers.authorization as string).substring(7);
|
||||
if (accessToken === 'valid-access-token1') {
|
||||
return response.ok({
|
||||
body: {
|
||||
sub: 'user1',
|
||||
name: 'Tony Stark',
|
||||
given_name: 'Tony',
|
||||
family_name: 'Stark',
|
||||
preferred_username: 'ironman',
|
||||
email: 'ironman@avengers.com',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (accessToken === 'valid-access-token2') {
|
||||
return response.ok({
|
||||
body: {
|
||||
sub: 'user2',
|
||||
name: 'Peter Parker',
|
||||
given_name: 'Peter',
|
||||
family_name: 'Parker',
|
||||
preferred_username: 'spiderman',
|
||||
email: 'spiderman@avengers.com',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (accessToken === 'valid-access-token3') {
|
||||
return response.ok({
|
||||
body: {
|
||||
sub: 'user3',
|
||||
name: 'Bruce Banner',
|
||||
given_name: 'Bruce',
|
||||
family_name: 'Banner',
|
||||
preferred_username: 'hulk',
|
||||
email: 'hulk@avengers.com',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return response.ok({ body: {} });
|
||||
}
|
||||
);
|
||||
}
|
|
@ -18,10 +18,7 @@ export default async function({ readConfigFile }) {
|
|||
);
|
||||
|
||||
return {
|
||||
testFiles: [
|
||||
require.resolve('./test_suites/task_manager'),
|
||||
require.resolve('./test_suites/encrypted_saved_objects'),
|
||||
],
|
||||
testFiles: [require.resolve('./test_suites/task_manager')],
|
||||
services,
|
||||
servers: integrationConfig.get('servers'),
|
||||
esTestCluster: integrationConfig.get('esTestCluster'),
|
||||
|
|
|
@ -1,55 +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 { Request } from 'hapi';
|
||||
import { boomify, badRequest } from 'boom';
|
||||
import { Legacy } from 'kibana';
|
||||
import {
|
||||
EncryptedSavedObjectsPluginSetup,
|
||||
EncryptedSavedObjectsPluginStart,
|
||||
} from '../../../../plugins/encrypted_saved_objects/server';
|
||||
|
||||
const SAVED_OBJECT_WITH_SECRET_TYPE = 'saved-object-with-secret';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function esoPlugin(kibana: any) {
|
||||
return new kibana.Plugin({
|
||||
id: 'eso',
|
||||
require: ['encryptedSavedObjects'],
|
||||
uiExports: { mappings: require('./mappings.json') },
|
||||
init(server: Legacy.Server) {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/api/saved_objects/get-decrypted-as-internal-user/{id}',
|
||||
async handler(request: Request) {
|
||||
const encryptedSavedObjectsStart = server.newPlatform.start.plugins
|
||||
.encryptedSavedObjects as EncryptedSavedObjectsPluginStart;
|
||||
const namespace = server.plugins.spaces && server.plugins.spaces.getSpaceId(request);
|
||||
try {
|
||||
return await encryptedSavedObjectsStart.getDecryptedAsInternalUser(
|
||||
SAVED_OBJECT_WITH_SECRET_TYPE,
|
||||
request.params.id,
|
||||
{ namespace: namespace === 'default' ? undefined : namespace }
|
||||
);
|
||||
} catch (err) {
|
||||
if (encryptedSavedObjectsStart.isEncryptionError(err)) {
|
||||
return badRequest('Failed to encrypt attributes');
|
||||
}
|
||||
|
||||
return boomify(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
(server.newPlatform.setup.plugins
|
||||
.encryptedSavedObjects as EncryptedSavedObjectsPluginSetup).registerType({
|
||||
type: SAVED_OBJECT_WITH_SECRET_TYPE,
|
||||
attributesToEncrypt: new Set(['privateProperty']),
|
||||
attributesToExcludeFromAAD: new Set(['publicPropertyExcludedFromAAD']),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"saved-object-with-secret": {
|
||||
"properties": {
|
||||
"publicProperty": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"publicPropertyExcludedFromAAD": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"privateProperty": {
|
||||
"type": "binary"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"name": "eso",
|
||||
"version": "kibana"
|
||||
}
|
|
@ -50,7 +50,6 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
|
|||
serverArgs: [
|
||||
...xPackAPITestsConfig.get('kbnTestServer.serverArgs'),
|
||||
'--optimize.enabled=false',
|
||||
'--server.xsrf.whitelist=["/api/security/v1/saml"]',
|
||||
`--xpack.security.authc.providers=${JSON.stringify(['saml', 'basic'])}`,
|
||||
'--xpack.security.authc.saml.maxRedirectURLSize=100b',
|
||||
],
|
||||
|
|
Loading…
Reference in a new issue