[7.x] Migrate Security and EncryptedSavedObjects test plugins to the Kibana Platform (#61864)

This commit is contained in:
Aleh Zasypkin 2020-03-30 21:35:41 +02:00 committed by GitHub
parent b7f1c29181
commit 07f5798625
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 249 additions and 220 deletions

View file

@ -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'),

View file

@ -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')}`,
],
},
};
}

View file

@ -0,0 +1,8 @@
{
"id": "eso",
"version": "8.0.0",
"kibanaVersion": "kibana",
"requiredPlugins": ["encryptedSavedObjects", "spaces"],
"server": true,
"ui": false
}

View file

@ -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() {},
});

View 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, {}>;

View file

@ -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';

View file

@ -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');

View file

@ -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() {

View file

@ -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',
]),
],
},
};

View file

@ -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',
]),
],
},
};

View file

@ -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 {};
},
});
}

View file

@ -0,0 +1,7 @@
{
"id": "oidc_provider_plugin",
"version": "8.0.0",
"kibanaVersion": "kibana",
"server": true,
"ui": false
}

View file

@ -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"
}
}

View file

@ -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: () => {},
});

View file

@ -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: {} });
}
);
}

View file

@ -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'),

View file

@ -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']),
});
},
});
}

View file

@ -1,15 +0,0 @@
{
"saved-object-with-secret": {
"properties": {
"publicProperty": {
"type": "keyword"
},
"publicPropertyExcludedFromAAD": {
"type": "keyword"
},
"privateProperty": {
"type": "binary"
}
}
}
}

View file

@ -1,4 +0,0 @@
{
"name": "eso",
"version": "kibana"
}

View file

@ -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',
],