mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
Revert "Add mock identity provider for serverless (#170852)"
This reverts commit 1fb0313a52
.
This commit is contained in:
parent
65ebb69a9f
commit
d09e47abbc
20 changed files with 41 additions and 710 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -534,7 +534,6 @@ x-pack/packages/ml/runtime_field_utils @elastic/ml-ui
|
||||||
x-pack/packages/ml/string_hash @elastic/ml-ui
|
x-pack/packages/ml/string_hash @elastic/ml-ui
|
||||||
x-pack/packages/ml/trained_models_utils @elastic/ml-ui
|
x-pack/packages/ml/trained_models_utils @elastic/ml-ui
|
||||||
x-pack/packages/ml/url_state @elastic/ml-ui
|
x-pack/packages/ml/url_state @elastic/ml-ui
|
||||||
packages/kbn-mock-idp-plugin @elastic/kibana-security
|
|
||||||
packages/kbn-monaco @elastic/appex-sharedux
|
packages/kbn-monaco @elastic/appex-sharedux
|
||||||
x-pack/plugins/monitoring_collection @elastic/obs-ux-infra_services-team
|
x-pack/plugins/monitoring_collection @elastic/obs-ux-infra_services-team
|
||||||
x-pack/plugins/monitoring @elastic/obs-ux-infra_services-team
|
x-pack/plugins/monitoring @elastic/obs-ux-infra_services-team
|
||||||
|
|
|
@ -1232,7 +1232,6 @@
|
||||||
"@kbn/managed-vscode-config": "link:packages/kbn-managed-vscode-config",
|
"@kbn/managed-vscode-config": "link:packages/kbn-managed-vscode-config",
|
||||||
"@kbn/managed-vscode-config-cli": "link:packages/kbn-managed-vscode-config-cli",
|
"@kbn/managed-vscode-config-cli": "link:packages/kbn-managed-vscode-config-cli",
|
||||||
"@kbn/management-storybook-config": "link:packages/kbn-management/storybook/config",
|
"@kbn/management-storybook-config": "link:packages/kbn-management/storybook/config",
|
||||||
"@kbn/mock-idp-plugin": "link:packages/kbn-mock-idp-plugin",
|
|
||||||
"@kbn/openapi-generator": "link:packages/kbn-openapi-generator",
|
"@kbn/openapi-generator": "link:packages/kbn-openapi-generator",
|
||||||
"@kbn/optimizer": "link:packages/kbn-optimizer",
|
"@kbn/optimizer": "link:packages/kbn-optimizer",
|
||||||
"@kbn/optimizer-webpack-helpers": "link:packages/kbn-optimizer-webpack-helpers",
|
"@kbn/optimizer-webpack-helpers": "link:packages/kbn-optimizer-webpack-helpers",
|
||||||
|
|
|
@ -38,7 +38,6 @@ export const serverless: Command = {
|
||||||
--host Publish ES docker container on additional host IP
|
--host Publish ES docker container on additional host IP
|
||||||
--port The port to bind to on 127.0.0.1 [default: ${DEFAULT_PORT}]
|
--port The port to bind to on 127.0.0.1 [default: ${DEFAULT_PORT}]
|
||||||
--ssl Enable HTTP SSL on the ES cluster
|
--ssl Enable HTTP SSL on the ES cluster
|
||||||
--kibanaUrl Fully qualified URL where Kibana is hosted (including base path). [default: https://localhost:5601/]
|
|
||||||
--skipTeardown If this process exits, leave the ES cluster running in the background
|
--skipTeardown If this process exits, leave the ES cluster running in the background
|
||||||
--waitForReady Wait for the ES cluster to be ready to serve requests
|
--waitForReady Wait for the ES cluster to be ready to serve requests
|
||||||
--resources Overrides resources under ES 'config/' directory, which are by default
|
--resources Overrides resources under ES 'config/' directory, which are by default
|
||||||
|
@ -74,7 +73,7 @@ export const serverless: Command = {
|
||||||
files: 'F',
|
files: 'F',
|
||||||
},
|
},
|
||||||
|
|
||||||
string: ['tag', 'image', 'basePath', 'resources', 'host', 'kibanaUrl'],
|
string: ['tag', 'image', 'basePath', 'resources', 'host'],
|
||||||
boolean: ['clean', 'ssl', 'kill', 'background', 'skipTeardown', 'waitForReady'],
|
boolean: ['clean', 'ssl', 'kill', 'background', 'skipTeardown', 'waitForReady'],
|
||||||
|
|
||||||
default: defaults,
|
default: defaults,
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
import Os from 'os';
|
import Os from 'os';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { REPO_ROOT } from '@kbn/repo-info';
|
|
||||||
|
|
||||||
function maybeUseBat(bin: string) {
|
function maybeUseBat(bin: string) {
|
||||||
return Os.platform().startsWith('win') ? `${bin}.bat` : bin;
|
return Os.platform().startsWith('win') ? `${bin}.bat` : bin;
|
||||||
|
@ -52,8 +51,6 @@ export const SERVERLESS_SECRETS_SSL_PATH = resolve(
|
||||||
|
|
||||||
export const SERVERLESS_JWKS_PATH = resolve(__dirname, './serverless_resources/jwks.json');
|
export const SERVERLESS_JWKS_PATH = resolve(__dirname, './serverless_resources/jwks.json');
|
||||||
|
|
||||||
export const SERVERLESS_IDP_METADATA_PATH = resolve(REPO_ROOT, '.es', 'idp_metadata.xml');
|
|
||||||
|
|
||||||
export const SERVERLESS_RESOURCES_PATHS = [
|
export const SERVERLESS_RESOURCES_PATHS = [
|
||||||
SERVERLESS_OPERATOR_USERS_PATH,
|
SERVERLESS_OPERATOR_USERS_PATH,
|
||||||
SERVERLESS_ROLE_MAPPING_PATH,
|
SERVERLESS_ROLE_MAPPING_PATH,
|
||||||
|
|
|
@ -32,17 +32,15 @@ import {
|
||||||
ServerlessOptions,
|
ServerlessOptions,
|
||||||
} from './docker';
|
} from './docker';
|
||||||
import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log';
|
import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log';
|
||||||
import { CA_CERT_PATH, ES_P12_PATH } from '@kbn/dev-utils';
|
import { ES_P12_PATH } from '@kbn/dev-utils';
|
||||||
import {
|
import {
|
||||||
SERVERLESS_CONFIG_PATH,
|
SERVERLESS_CONFIG_PATH,
|
||||||
SERVERLESS_RESOURCES_PATHS,
|
SERVERLESS_RESOURCES_PATHS,
|
||||||
SERVERLESS_SECRETS_PATH,
|
SERVERLESS_SECRETS_PATH,
|
||||||
SERVERLESS_JWKS_PATH,
|
SERVERLESS_JWKS_PATH,
|
||||||
SERVERLESS_IDP_METADATA_PATH,
|
|
||||||
} from '../paths';
|
} from '../paths';
|
||||||
import * as waitClusterUtil from './wait_until_cluster_ready';
|
import * as waitClusterUtil from './wait_until_cluster_ready';
|
||||||
import * as waitForSecurityIndexUtil from './wait_for_security_index';
|
import * as waitForSecurityIndexUtil from './wait_for_security_index';
|
||||||
import * as mockIdpPluginUtil from '@kbn/mock-idp-plugin/common';
|
|
||||||
|
|
||||||
jest.mock('execa');
|
jest.mock('execa');
|
||||||
const execa = jest.requireMock('execa');
|
const execa = jest.requireMock('execa');
|
||||||
|
@ -60,8 +58,6 @@ jest.mock('./wait_for_security_index', () => ({
|
||||||
waitForSecurityIndex: jest.fn(),
|
waitForSecurityIndex: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@kbn/mock-idp-plugin/common');
|
|
||||||
|
|
||||||
const log = new ToolingLog();
|
const log = new ToolingLog();
|
||||||
const logWriter = new ToolingLogCollectingWriter();
|
const logWriter = new ToolingLogCollectingWriter();
|
||||||
log.setWriters([logWriter]);
|
log.setWriters([logWriter]);
|
||||||
|
@ -73,8 +69,6 @@ const serverlessObjectStorePath = `${baseEsPath}/${serverlessDir}`;
|
||||||
|
|
||||||
const waitUntilClusterReadyMock = jest.spyOn(waitClusterUtil, 'waitUntilClusterReady');
|
const waitUntilClusterReadyMock = jest.spyOn(waitClusterUtil, 'waitUntilClusterReady');
|
||||||
const waitForSecurityIndexMock = jest.spyOn(waitForSecurityIndexUtil, 'waitForSecurityIndex');
|
const waitForSecurityIndexMock = jest.spyOn(waitForSecurityIndexUtil, 'waitForSecurityIndex');
|
||||||
const ensureSAMLRoleMappingMock = jest.spyOn(mockIdpPluginUtil, 'ensureSAMLRoleMapping');
|
|
||||||
const createMockIdpMetadataMock = jest.spyOn(mockIdpPluginUtil, 'createMockIdpMetadata');
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
|
@ -429,66 +423,6 @@ describe('resolveEsArgs()', () => {
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should add SAML realm args when kibanaUrl and SSL are passed', () => {
|
|
||||||
const esArgs = resolveEsArgs([], {
|
|
||||||
ssl: true,
|
|
||||||
kibanaUrl: 'https://localhost:5601/',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(esArgs).toHaveLength(26);
|
|
||||||
expect(esArgs).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
"--env",
|
|
||||||
"xpack.security.http.ssl.enabled=true",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.http.ssl.keystore.path=/usr/share/elasticsearch/config/certs/elasticsearch.p12",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.http.ssl.verification_mode=certificate",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.authc.realms.saml.mock-idp.order=0",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.authc.realms.saml.mock-idp.idp.metadata.path=/usr/share/elasticsearch/config/secrets/idp_metadata.xml",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.authc.realms.saml.mock-idp.idp.entity_id=urn:mock-idp",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.authc.realms.saml.mock-idp.sp.entity_id=https://localhost:5601",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.authc.realms.saml.mock-idp.sp.acs=https://localhost:5601/api/security/saml/callback",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.authc.realms.saml.mock-idp.sp.logout=https://localhost:5601/logout",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.authc.realms.saml.mock-idp.attributes.principal=http://saml.elastic-cloud.com/attributes/principal",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.authc.realms.saml.mock-idp.attributes.groups=http://saml.elastic-cloud.com/attributes/roles",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.authc.realms.saml.mock-idp.attributes.name=http://saml.elastic-cloud.com/attributes/email",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.authc.realms.saml.mock-idp.attributes.mail=http://saml.elastic-cloud.com/attributes/name",
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not add SAML realm args when security is disabled', () => {
|
|
||||||
const esArgs = resolveEsArgs([['xpack.security.enabled', 'false']], {
|
|
||||||
ssl: true,
|
|
||||||
kibanaUrl: 'https://localhost:5601/',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(esArgs).toHaveLength(8);
|
|
||||||
expect(esArgs).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
"--env",
|
|
||||||
"xpack.security.enabled=false",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.http.ssl.enabled=true",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.http.ssl.keystore.path=/usr/share/elasticsearch/config/certs/elasticsearch.p12",
|
|
||||||
"--env",
|
|
||||||
"xpack.security.http.ssl.verification_mode=certificate",
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setupServerlessVolumes()', () => {
|
describe('setupServerlessVolumes()', () => {
|
||||||
|
@ -529,29 +463,21 @@ describe('setupServerlessVolumes()', () => {
|
||||||
expect(existsSync(`${serverlessObjectStorePath}/cluster_state/lease`)).toBe(false);
|
expect(existsSync(`${serverlessObjectStorePath}/cluster_state/lease`)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should add SSL and IDP metadata volumes when ssl and kibanaUrl are passed', async () => {
|
test('should add SSL volumes when ssl is passed', async () => {
|
||||||
mockFs(existingObjectStore);
|
mockFs(existingObjectStore);
|
||||||
createMockIdpMetadataMock.mockResolvedValue('<xml/>');
|
|
||||||
|
|
||||||
const volumeCmd = await setupServerlessVolumes(log, {
|
const volumeCmd = await setupServerlessVolumes(log, { basePath: baseEsPath, ssl: true });
|
||||||
basePath: baseEsPath,
|
|
||||||
ssl: true,
|
|
||||||
kibanaUrl: 'https://localhost:5603/',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(createMockIdpMetadataMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(createMockIdpMetadataMock).toHaveBeenCalledWith('https://localhost:5603/');
|
|
||||||
|
|
||||||
const requiredPaths = [
|
const requiredPaths = [
|
||||||
`${baseEsPath}:/objectstore:z`,
|
`${baseEsPath}:/objectstore:z`,
|
||||||
SERVERLESS_IDP_METADATA_PATH,
|
|
||||||
ES_P12_PATH,
|
ES_P12_PATH,
|
||||||
...SERVERLESS_RESOURCES_PATHS,
|
...SERVERLESS_RESOURCES_PATHS,
|
||||||
];
|
];
|
||||||
const pathsNotIncludedInCmd = requiredPaths.filter(
|
const pathsNotIncludedInCmd = requiredPaths.filter(
|
||||||
(path) => !volumeCmd.some((cmd) => cmd.includes(path))
|
(path) => !volumeCmd.some((cmd) => cmd.includes(path))
|
||||||
);
|
);
|
||||||
expect(volumeCmd).toHaveLength(22);
|
|
||||||
|
expect(volumeCmd).toHaveLength(20);
|
||||||
expect(pathsNotIncludedInCmd).toEqual([]);
|
expect(pathsNotIncludedInCmd).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -617,7 +543,6 @@ describe('runServerlessEsNode()', () => {
|
||||||
|
|
||||||
describe('runServerlessCluster()', () => {
|
describe('runServerlessCluster()', () => {
|
||||||
test('should start 3 serverless nodes', async () => {
|
test('should start 3 serverless nodes', async () => {
|
||||||
waitUntilClusterReadyMock.mockResolvedValue();
|
|
||||||
mockFs({
|
mockFs({
|
||||||
[baseEsPath]: {},
|
[baseEsPath]: {},
|
||||||
});
|
});
|
||||||
|
@ -642,27 +567,7 @@ describe('runServerlessCluster()', () => {
|
||||||
expect(waitUntilClusterReadyMock.mock.calls[0][0].readyTimeout).toEqual(undefined);
|
expect(waitUntilClusterReadyMock.mock.calls[0][0].readyTimeout).toEqual(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`should create SAML role mapping when ssl and kibanaUrl are passed`, async () => {
|
|
||||||
waitUntilClusterReadyMock.mockResolvedValue();
|
|
||||||
mockFs({
|
|
||||||
[CA_CERT_PATH]: '',
|
|
||||||
[baseEsPath]: {},
|
|
||||||
});
|
|
||||||
execa.mockImplementation(() => Promise.resolve({ stdout: '' }));
|
|
||||||
createMockIdpMetadataMock.mockResolvedValue('<xml/>');
|
|
||||||
|
|
||||||
await runServerlessCluster(log, {
|
|
||||||
basePath: baseEsPath,
|
|
||||||
waitForReady: true,
|
|
||||||
ssl: true,
|
|
||||||
kibanaUrl: 'https://localhost:5601/',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(ensureSAMLRoleMappingMock).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test(`should wait for the security index`, async () => {
|
test(`should wait for the security index`, async () => {
|
||||||
waitUntilClusterReadyMock.mockResolvedValue();
|
|
||||||
waitForSecurityIndexMock.mockResolvedValue();
|
waitForSecurityIndexMock.mockResolvedValue();
|
||||||
mockFs({
|
mockFs({
|
||||||
[baseEsPath]: {},
|
[baseEsPath]: {},
|
||||||
|
@ -675,7 +580,6 @@ describe('runServerlessCluster()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`should not wait for the security index when security is disabled`, async () => {
|
test(`should not wait for the security index when security is disabled`, async () => {
|
||||||
waitUntilClusterReadyMock.mockResolvedValue();
|
|
||||||
mockFs({
|
mockFs({
|
||||||
[baseEsPath]: {},
|
[baseEsPath]: {},
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,17 +14,12 @@ import { Client, ClientOptions, HttpConnection } from '@elastic/elasticsearch';
|
||||||
|
|
||||||
import { ToolingLog } from '@kbn/tooling-log';
|
import { ToolingLog } from '@kbn/tooling-log';
|
||||||
import { kibanaPackageJson as pkg, REPO_ROOT } from '@kbn/repo-info';
|
import { kibanaPackageJson as pkg, REPO_ROOT } from '@kbn/repo-info';
|
||||||
import { CA_CERT_PATH, ES_P12_PASSWORD, ES_P12_PATH } from '@kbn/dev-utils';
|
|
||||||
import {
|
import {
|
||||||
MOCK_IDP_REALM_NAME,
|
CA_CERT_PATH,
|
||||||
MOCK_IDP_ENTITY_ID,
|
ES_P12_PASSWORD,
|
||||||
MOCK_IDP_ATTRIBUTE_PRINCIPAL,
|
ES_P12_PATH,
|
||||||
MOCK_IDP_ATTRIBUTE_ROLES,
|
kibanaDevServiceAccount,
|
||||||
MOCK_IDP_ATTRIBUTE_EMAIL,
|
} from '@kbn/dev-utils';
|
||||||
MOCK_IDP_ATTRIBUTE_NAME,
|
|
||||||
ensureSAMLRoleMapping,
|
|
||||||
createMockIdpMetadata,
|
|
||||||
} from '@kbn/mock-idp-plugin/common';
|
|
||||||
|
|
||||||
import { waitForSecurityIndex } from './wait_for_security_index';
|
import { waitForSecurityIndex } from './wait_for_security_index';
|
||||||
import { createCliError } from '../errors';
|
import { createCliError } from '../errors';
|
||||||
|
@ -33,7 +28,6 @@ import {
|
||||||
SERVERLESS_RESOURCES_PATHS,
|
SERVERLESS_RESOURCES_PATHS,
|
||||||
SERVERLESS_SECRETS_PATH,
|
SERVERLESS_SECRETS_PATH,
|
||||||
SERVERLESS_JWKS_PATH,
|
SERVERLESS_JWKS_PATH,
|
||||||
SERVERLESS_IDP_METADATA_PATH,
|
|
||||||
SERVERLESS_CONFIG_PATH,
|
SERVERLESS_CONFIG_PATH,
|
||||||
SERVERLESS_FILES_PATH,
|
SERVERLESS_FILES_PATH,
|
||||||
SERVERLESS_SECRETS_SSL_PATH,
|
SERVERLESS_SECRETS_SSL_PATH,
|
||||||
|
@ -75,8 +69,6 @@ export interface ServerlessOptions extends EsClusterExecOptions, BaseOptions {
|
||||||
background?: boolean;
|
background?: boolean;
|
||||||
/** Wait for the ES cluster to be ready to serve requests */
|
/** Wait for the ES cluster to be ready to serve requests */
|
||||||
waitForReady?: boolean;
|
waitForReady?: boolean;
|
||||||
/** Fully qualified URL where Kibana is hosted (including base path) */
|
|
||||||
kibanaUrl?: string;
|
|
||||||
/**
|
/**
|
||||||
* Resource file(s) to overwrite
|
* Resource file(s) to overwrite
|
||||||
* (see list of files that can be overwritten under `packages/kbn-es/src/serverless_resources/users`)
|
* (see list of files that can be overwritten under `packages/kbn-es/src/serverless_resources/users`)
|
||||||
|
@ -468,54 +460,6 @@ export function resolveEsArgs(
|
||||||
esArgs.set('ELASTIC_PASSWORD', password);
|
esArgs.set('ELASTIC_PASSWORD', password);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure mock identify provider (ES only supports SAML when running in SSL mode)
|
|
||||||
if (
|
|
||||||
ssl &&
|
|
||||||
'kibanaUrl' in options &&
|
|
||||||
options.kibanaUrl &&
|
|
||||||
esArgs.get('xpack.security.enabled') !== 'false'
|
|
||||||
) {
|
|
||||||
const trimTrailingSlash = (url: string) => (url.endsWith('/') ? url.slice(0, -1) : url);
|
|
||||||
|
|
||||||
esArgs.set(`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.order`, '0');
|
|
||||||
esArgs.set(
|
|
||||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.metadata.path`,
|
|
||||||
`${SERVERLESS_CONFIG_PATH}secrets/idp_metadata.xml`
|
|
||||||
);
|
|
||||||
esArgs.set(
|
|
||||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.entity_id`,
|
|
||||||
MOCK_IDP_ENTITY_ID
|
|
||||||
);
|
|
||||||
esArgs.set(
|
|
||||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.entity_id`,
|
|
||||||
trimTrailingSlash(options.kibanaUrl)
|
|
||||||
);
|
|
||||||
esArgs.set(
|
|
||||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.acs`,
|
|
||||||
`${trimTrailingSlash(options.kibanaUrl)}/api/security/saml/callback`
|
|
||||||
);
|
|
||||||
esArgs.set(
|
|
||||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.logout`,
|
|
||||||
`${trimTrailingSlash(options.kibanaUrl)}/logout`
|
|
||||||
);
|
|
||||||
esArgs.set(
|
|
||||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.principal`,
|
|
||||||
MOCK_IDP_ATTRIBUTE_PRINCIPAL
|
|
||||||
);
|
|
||||||
esArgs.set(
|
|
||||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.groups`,
|
|
||||||
MOCK_IDP_ATTRIBUTE_ROLES
|
|
||||||
);
|
|
||||||
esArgs.set(
|
|
||||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.name`,
|
|
||||||
MOCK_IDP_ATTRIBUTE_EMAIL
|
|
||||||
);
|
|
||||||
esArgs.set(
|
|
||||||
`xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.mail`,
|
|
||||||
MOCK_IDP_ATTRIBUTE_NAME
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(esArgs).flatMap((e) => ['--env', e.join('=')]);
|
return Array.from(esArgs).flatMap((e) => ['--env', e.join('=')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,7 +480,7 @@ export function getDockerFileMountPath(hostPath: string) {
|
||||||
* Setup local volumes for Serverless ES
|
* Setup local volumes for Serverless ES
|
||||||
*/
|
*/
|
||||||
export async function setupServerlessVolumes(log: ToolingLog, options: ServerlessOptions) {
|
export async function setupServerlessVolumes(log: ToolingLog, options: ServerlessOptions) {
|
||||||
const { basePath, clean, ssl, kibanaUrl, files, resources } = options;
|
const { basePath, clean, ssl, files, resources } = options;
|
||||||
const objectStorePath = resolve(basePath, 'stateless');
|
const objectStorePath = resolve(basePath, 'stateless');
|
||||||
|
|
||||||
log.info(chalk.bold(`Checking for local serverless ES object store at ${objectStorePath}`));
|
log.info(chalk.bold(`Checking for local serverless ES object store at ${objectStorePath}`));
|
||||||
|
@ -607,16 +551,6 @@ export async function setupServerlessVolumes(log: ToolingLog, options: Serverles
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and add meta data for mock identity provider
|
|
||||||
if (ssl && kibanaUrl) {
|
|
||||||
const metadata = await createMockIdpMetadata(kibanaUrl);
|
|
||||||
await Fsp.writeFile(SERVERLESS_IDP_METADATA_PATH, metadata);
|
|
||||||
volumeCmds.push(
|
|
||||||
'--volume',
|
|
||||||
`${SERVERLESS_IDP_METADATA_PATH}:${SERVERLESS_CONFIG_PATH}secrets/idp_metadata.xml:z`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
volumeCmds.push(
|
volumeCmds.push(
|
||||||
...getESp12Volume(),
|
...getESp12Volume(),
|
||||||
...serverlessResources,
|
...serverlessResources,
|
||||||
|
@ -625,6 +559,7 @@ export async function setupServerlessVolumes(log: ToolingLog, options: Serverles
|
||||||
`${
|
`${
|
||||||
ssl ? SERVERLESS_SECRETS_SSL_PATH : SERVERLESS_SECRETS_PATH
|
ssl ? SERVERLESS_SECRETS_SSL_PATH : SERVERLESS_SECRETS_PATH
|
||||||
}:${SERVERLESS_CONFIG_PATH}secrets/secrets.json:z`,
|
}:${SERVERLESS_CONFIG_PATH}secrets/secrets.json:z`,
|
||||||
|
|
||||||
'--volume',
|
'--volume',
|
||||||
`${SERVERLESS_JWKS_PATH}:${SERVERLESS_CONFIG_PATH}secrets/jwks.json:z`
|
`${SERVERLESS_JWKS_PATH}:${SERVERLESS_CONFIG_PATH}secrets/jwks.json:z`
|
||||||
);
|
);
|
||||||
|
@ -726,6 +661,9 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO
|
||||||
process.on('SIGINT', () => teardownServerlessClusterSync(log, options));
|
process.on('SIGINT', () => teardownServerlessClusterSync(log, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.waitForReady) {
|
||||||
|
log.info('Waiting until ES is ready to serve requests...');
|
||||||
|
|
||||||
const esNodeUrl = `${options.ssl ? 'https' : 'http'}://${portCmd[1].substring(
|
const esNodeUrl = `${options.ssl ? 'https' : 'http'}://${portCmd[1].substring(
|
||||||
0,
|
0,
|
||||||
portCmd[1].lastIndexOf(':')
|
portCmd[1].lastIndexOf(':')
|
||||||
|
@ -733,10 +671,7 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO
|
||||||
|
|
||||||
const client = getESClient({
|
const client = getESClient({
|
||||||
node: esNodeUrl,
|
node: esNodeUrl,
|
||||||
auth: {
|
auth: { bearer: kibanaDevServiceAccount.token },
|
||||||
username: ELASTIC_SERVERLESS_SUPERUSER,
|
|
||||||
password: ELASTIC_SERVERLESS_SUPERUSER_PASSWORD,
|
|
||||||
},
|
|
||||||
...(options.ssl
|
...(options.ssl
|
||||||
? {
|
? {
|
||||||
tls: {
|
tls: {
|
||||||
|
@ -752,26 +687,7 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
});
|
});
|
||||||
|
await waitUntilClusterReady({ client, expectedStatus: 'green', log });
|
||||||
const readyPromise = waitUntilClusterReady({ client, expectedStatus: 'green', log }).then(
|
|
||||||
async () => {
|
|
||||||
if (!options.ssl || !options.kibanaUrl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ensureSAMLRoleMapping(client);
|
|
||||||
|
|
||||||
log.success(
|
|
||||||
`Created role mapping for mock identity provider. You can now login using ${chalk.bold.cyan(
|
|
||||||
MOCK_IDP_REALM_NAME
|
|
||||||
)} realm`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (options.waitForReady) {
|
|
||||||
log.info('Waiting until ES is ready to serve requests...');
|
|
||||||
await readyPromise;
|
|
||||||
if (!options.esArgs || !options.esArgs.includes('xpack.security.enabled=false')) {
|
if (!options.esArgs || !options.esArgs.includes('xpack.security.enabled=false')) {
|
||||||
// If security is not disabled, make sure the security index exists before running the test to avoid flakiness
|
// If security is not disabled, make sure the security index exists before running the test to avoid flakiness
|
||||||
await waitForSecurityIndex({ client, log });
|
await waitForSecurityIndex({ client, log });
|
||||||
|
|
|
@ -3,20 +3,13 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "target/types"
|
"outDir": "target/types"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["**/*.ts", "**/*.js", "**/*.json"],
|
||||||
"**/*.ts",
|
"exclude": ["target/**/*"],
|
||||||
"**/*.js",
|
|
||||||
"**/*.json"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"target/**/*"
|
|
||||||
],
|
|
||||||
"kbn_references": [
|
"kbn_references": [
|
||||||
"@kbn/tooling-log",
|
"@kbn/tooling-log",
|
||||||
"@kbn/dev-utils",
|
"@kbn/dev-utils",
|
||||||
"@kbn/dev-proc-runner",
|
"@kbn/dev-proc-runner",
|
||||||
"@kbn/ci-stats-reporter",
|
"@kbn/ci-stats-reporter",
|
||||||
"@kbn/mock-idp-plugin",
|
|
||||||
"@kbn/jest-serializers",
|
"@kbn/jest-serializers",
|
||||||
"@kbn/repo-info"
|
"@kbn/repo-info"
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,24 +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
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { resolve } from 'path';
|
|
||||||
|
|
||||||
export const MOCK_IDP_PLUGIN_PATH = resolve(__dirname, '..');
|
|
||||||
export const MOCK_IDP_METADATA_PATH = resolve(MOCK_IDP_PLUGIN_PATH, 'metadata.xml');
|
|
||||||
|
|
||||||
export const MOCK_IDP_LOGIN_PATH = '/mock_idp/login';
|
|
||||||
export const MOCK_IDP_LOGOUT_PATH = '/mock_idp/logout';
|
|
||||||
|
|
||||||
export const MOCK_IDP_REALM_NAME = 'mock-idp';
|
|
||||||
export const MOCK_IDP_ENTITY_ID = 'urn:mock-idp'; // Must match `entityID` in `metadata.xml`
|
|
||||||
export const MOCK_IDP_ROLE_MAPPING_NAME = 'mock-idp-mapping';
|
|
||||||
|
|
||||||
export const MOCK_IDP_ATTRIBUTE_PRINCIPAL = 'http://saml.elastic-cloud.com/attributes/principal';
|
|
||||||
export const MOCK_IDP_ATTRIBUTE_ROLES = 'http://saml.elastic-cloud.com/attributes/roles';
|
|
||||||
export const MOCK_IDP_ATTRIBUTE_EMAIL = 'http://saml.elastic-cloud.com/attributes/email';
|
|
||||||
export const MOCK_IDP_ATTRIBUTE_NAME = 'http://saml.elastic-cloud.com/attributes/name';
|
|
|
@ -1,27 +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
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export {
|
|
||||||
MOCK_IDP_PLUGIN_PATH,
|
|
||||||
MOCK_IDP_METADATA_PATH,
|
|
||||||
MOCK_IDP_LOGIN_PATH,
|
|
||||||
MOCK_IDP_LOGOUT_PATH,
|
|
||||||
MOCK_IDP_REALM_NAME,
|
|
||||||
MOCK_IDP_ENTITY_ID,
|
|
||||||
MOCK_IDP_ROLE_MAPPING_NAME,
|
|
||||||
MOCK_IDP_ATTRIBUTE_PRINCIPAL,
|
|
||||||
MOCK_IDP_ATTRIBUTE_ROLES,
|
|
||||||
MOCK_IDP_ATTRIBUTE_EMAIL,
|
|
||||||
MOCK_IDP_ATTRIBUTE_NAME,
|
|
||||||
} from './constants';
|
|
||||||
export {
|
|
||||||
createMockIdpMetadata,
|
|
||||||
createSAMLResponse,
|
|
||||||
ensureSAMLRoleMapping,
|
|
||||||
parseSAMLAuthnRequest,
|
|
||||||
} from './utils';
|
|
|
@ -1,231 +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
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Client } from '@elastic/elasticsearch';
|
|
||||||
import { SignedXml } from 'xml-crypto';
|
|
||||||
import { KBN_KEY_PATH, KBN_CERT_PATH } from '@kbn/dev-utils';
|
|
||||||
import { readFile } from 'fs/promises';
|
|
||||||
import zlib from 'zlib';
|
|
||||||
import { promisify } from 'util';
|
|
||||||
import { parseString } from 'xml2js';
|
|
||||||
import { X509Certificate } from 'crypto';
|
|
||||||
|
|
||||||
import {
|
|
||||||
MOCK_IDP_REALM_NAME,
|
|
||||||
MOCK_IDP_ENTITY_ID,
|
|
||||||
MOCK_IDP_ROLE_MAPPING_NAME,
|
|
||||||
MOCK_IDP_ATTRIBUTE_PRINCIPAL,
|
|
||||||
MOCK_IDP_ATTRIBUTE_ROLES,
|
|
||||||
MOCK_IDP_ATTRIBUTE_EMAIL,
|
|
||||||
MOCK_IDP_ATTRIBUTE_NAME,
|
|
||||||
MOCK_IDP_LOGIN_PATH,
|
|
||||||
MOCK_IDP_LOGOUT_PATH,
|
|
||||||
} from './constants';
|
|
||||||
|
|
||||||
const inflateRawAsync = promisify(zlib.inflateRaw);
|
|
||||||
const parseStringAsync = promisify(parseString);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates XML metadata for our mock identity provider.
|
|
||||||
*
|
|
||||||
* This can be saved to file and used to configure Elasticsearch SAML realm.
|
|
||||||
*
|
|
||||||
* @param kibanaUrl Fully qualified URL where Kibana is hosted (including base path)
|
|
||||||
*/
|
|
||||||
export async function createMockIdpMetadata(kibanaUrl: string) {
|
|
||||||
const signingKey = await readFile(KBN_CERT_PATH);
|
|
||||||
const cert = new X509Certificate(signingKey);
|
|
||||||
const trimTrailingSlash = (url: string) => (url.endsWith('/') ? url.slice(0, -1) : url);
|
|
||||||
|
|
||||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
|
|
||||||
entityID="${MOCK_IDP_ENTITY_ID}">
|
|
||||||
<md:IDPSSODescriptor WantAuthnRequestsSigned="false"
|
|
||||||
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
|
||||||
<md:KeyDescriptor use="signing">
|
|
||||||
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
|
||||||
<ds:X509Data>
|
|
||||||
<ds:X509Certificate>${cert.raw.toString('base64')}</ds:X509Certificate>
|
|
||||||
</ds:X509Data>
|
|
||||||
</ds:KeyInfo>
|
|
||||||
</md:KeyDescriptor>
|
|
||||||
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
|
||||||
Location="${trimTrailingSlash(kibanaUrl)}${MOCK_IDP_LOGOUT_PATH}" />
|
|
||||||
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
||||||
Location="${trimTrailingSlash(kibanaUrl)}${MOCK_IDP_LOGOUT_PATH}" />
|
|
||||||
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
|
||||||
Location="${trimTrailingSlash(kibanaUrl)}${MOCK_IDP_LOGIN_PATH}" />
|
|
||||||
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
||||||
Location="${trimTrailingSlash(kibanaUrl)}${MOCK_IDP_LOGIN_PATH}" />
|
|
||||||
</md:IDPSSODescriptor>
|
|
||||||
</md:EntityDescriptor>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a SAML response that can be passed directly to the Kibana ACS endpoint to authenticate a user.
|
|
||||||
*
|
|
||||||
* @example Create a SAML response.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* const samlResponse = await createSAMLResponse({
|
|
||||||
* username: '1234567890',
|
|
||||||
* email: 'mail@elastic.co',
|
|
||||||
* fullname: 'Test User',
|
|
||||||
* roles: ['t1_analyst', 'editor'],
|
|
||||||
* })
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @example Authenticate user with SAML response.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* fetch('/api/security/saml/callback', {
|
|
||||||
* method: 'POST',
|
|
||||||
* body: JSON.stringify({ SAMLResponse: samlResponse }),
|
|
||||||
* redirect: 'manual'
|
|
||||||
* })
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export async function createSAMLResponse(options: {
|
|
||||||
/** Fully qualified URL where Kibana is hosted (including base path) */
|
|
||||||
kibanaUrl: string;
|
|
||||||
/** ID from SAML authentication request */
|
|
||||||
authnRequestId?: string;
|
|
||||||
username: string;
|
|
||||||
fullname?: string;
|
|
||||||
email?: string;
|
|
||||||
roles: string[];
|
|
||||||
}) {
|
|
||||||
const issueInstant = new Date().toISOString();
|
|
||||||
const notOnOrAfter = new Date(Date.now() + 3600 * 1000).toISOString();
|
|
||||||
|
|
||||||
const samlAssertionTemplateXML = `
|
|
||||||
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="_RPs1WfOkul8lZ72DtJtes0BKyPgaCamg" IssueInstant="${issueInstant}">
|
|
||||||
<saml:Issuer>${MOCK_IDP_ENTITY_ID}</saml:Issuer>
|
|
||||||
<saml:Subject>
|
|
||||||
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:transient">_643ec1b3f5673583b9f9a1e9e73a36daa2a3748f</saml:NameID>
|
|
||||||
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
|
|
||||||
<saml:SubjectConfirmationData NotOnOrAfter="${notOnOrAfter}" ${
|
|
||||||
options.authnRequestId ? `InResponseTo="${options.authnRequestId}"` : ''
|
|
||||||
} Recipient="${options.kibanaUrl}" />
|
|
||||||
</saml:SubjectConfirmation>
|
|
||||||
</saml:Subject>
|
|
||||||
<saml:AuthnStatement AuthnInstant="${issueInstant}" SessionIndex="4464894646681600">
|
|
||||||
<saml:AuthnContext>
|
|
||||||
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>
|
|
||||||
</saml:AuthnContext>
|
|
||||||
</saml:AuthnStatement>
|
|
||||||
<saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
||||||
<saml:Attribute Name="${MOCK_IDP_ATTRIBUTE_PRINCIPAL}" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
|
||||||
<saml:AttributeValue xsi:type="xs:string">${options.username}</saml:AttributeValue>
|
|
||||||
</saml:Attribute>
|
|
||||||
<saml:Attribute Name="${MOCK_IDP_ATTRIBUTE_ROLES}" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
|
||||||
${options.roles
|
|
||||||
.map(
|
|
||||||
(role) => `<saml:AttributeValue xsi:type="xs:string">${role}</saml:AttributeValue>`
|
|
||||||
)
|
|
||||||
.join('')}
|
|
||||||
</saml:Attribute>
|
|
||||||
${
|
|
||||||
options.email
|
|
||||||
? `<saml:Attribute Name="${MOCK_IDP_ATTRIBUTE_EMAIL}" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
|
||||||
<saml:AttributeValue xsi:type="xs:string">${options.email}</saml:AttributeValue>
|
|
||||||
</saml:Attribute>`
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
${
|
|
||||||
options.fullname
|
|
||||||
? `<saml:Attribute Name="${MOCK_IDP_ATTRIBUTE_NAME}" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
|
||||||
<saml:AttributeValue xsi:type="xs:string">${options.fullname}</saml:AttributeValue>
|
|
||||||
</saml:Attribute>`
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
</saml:AttributeStatement>
|
|
||||||
</saml:Assertion>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const signature = new SignedXml();
|
|
||||||
signature.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
|
|
||||||
signature.signingKey = await readFile(KBN_KEY_PATH);
|
|
||||||
|
|
||||||
// Adds a reference to a `Assertion` xml element and an array of transform algorithms to be used during signing.
|
|
||||||
signature.addReference(
|
|
||||||
`//*[local-name(.)='Assertion']`,
|
|
||||||
[
|
|
||||||
'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
|
|
||||||
'http://www.w3.org/2001/10/xml-exc-c14n#',
|
|
||||||
],
|
|
||||||
'http://www.w3.org/2001/04/xmlenc#sha256'
|
|
||||||
);
|
|
||||||
|
|
||||||
signature.computeSignature(samlAssertionTemplateXML, {
|
|
||||||
location: { reference: `//*[local-name(.)='Issuer']`, action: 'after' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const value = await Buffer.from(
|
|
||||||
`
|
|
||||||
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_bdf1d51245ed0f71aa23" Version="2.0" IssueInstant="${issueInstant}" ${
|
|
||||||
options.authnRequestId ? `InResponseTo="${options.authnRequestId}"` : ''
|
|
||||||
} Destination="${options.kibanaUrl}">
|
|
||||||
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">${MOCK_IDP_ENTITY_ID}</saml:Issuer>
|
|
||||||
<samlp:Status>
|
|
||||||
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
|
|
||||||
</samlp:Status>${signature.getSignedXml()}
|
|
||||||
</samlp:Response>
|
|
||||||
`
|
|
||||||
).toString('base64');
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the role mapping required for developers to authenticate using SAML.
|
|
||||||
*/
|
|
||||||
export async function ensureSAMLRoleMapping(client: Client) {
|
|
||||||
return client.transport.request({
|
|
||||||
method: 'PUT',
|
|
||||||
path: `/_security/role_mapping/${MOCK_IDP_ROLE_MAPPING_NAME}`,
|
|
||||||
body: {
|
|
||||||
enabled: true,
|
|
||||||
role_templates: [
|
|
||||||
{
|
|
||||||
template: '{"source":"{{#tojson}}groups{{/tojson}}"}',
|
|
||||||
format: 'json',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
all: [
|
|
||||||
{
|
|
||||||
field: {
|
|
||||||
'realm.name': MOCK_IDP_REALM_NAME,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SAMLAuthnRequest {
|
|
||||||
'saml2p:AuthnRequest': {
|
|
||||||
$: {
|
|
||||||
AssertionConsumerServiceURL: string;
|
|
||||||
Destination: string;
|
|
||||||
ID: string;
|
|
||||||
IssueInstant: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function parseSAMLAuthnRequest(samlRequest: string) {
|
|
||||||
const inflatedSAMLRequest = (await inflateRawAsync(Buffer.from(samlRequest, 'base64'))) as Buffer;
|
|
||||||
const parsedSAMLRequest = (await parseStringAsync(
|
|
||||||
inflatedSAMLRequest.toString()
|
|
||||||
)) as SAMLAuthnRequest;
|
|
||||||
return parsedSAMLRequest['saml2p:AuthnRequest'].$;
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"type": "plugin",
|
|
||||||
"id": "@kbn/mock-idp-plugin",
|
|
||||||
"owner": "@elastic/kibana-security",
|
|
||||||
"devOnly": true,
|
|
||||||
"plugin": {
|
|
||||||
"id": "mockIdpPlugin",
|
|
||||||
"server": true,
|
|
||||||
"browser": false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@kbn/mock-idp-plugin",
|
|
||||||
"private": true,
|
|
||||||
"version": "1.0.0",
|
|
||||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
|
||||||
}
|
|
|
@ -1,9 +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
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { plugin } from './plugin';
|
|
|
@ -1,120 +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
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { PluginInitializer, Plugin } from '@kbn/core-plugins-server';
|
|
||||||
import { schema } from '@kbn/config-schema';
|
|
||||||
|
|
||||||
import {
|
|
||||||
MOCK_IDP_LOGIN_PATH,
|
|
||||||
MOCK_IDP_LOGOUT_PATH,
|
|
||||||
createSAMLResponse,
|
|
||||||
parseSAMLAuthnRequest,
|
|
||||||
} from '../common';
|
|
||||||
|
|
||||||
export const plugin: PluginInitializer<void, void> = async (): Promise<Plugin> => ({
|
|
||||||
setup(core) {
|
|
||||||
core.http.resources.register(
|
|
||||||
{
|
|
||||||
path: MOCK_IDP_LOGIN_PATH,
|
|
||||||
validate: {
|
|
||||||
query: schema.object({
|
|
||||||
SAMLRequest: schema.string(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
options: { authRequired: false },
|
|
||||||
},
|
|
||||||
async (context, request, response) => {
|
|
||||||
let samlRequest: Awaited<ReturnType<typeof parseSAMLAuthnRequest>>;
|
|
||||||
try {
|
|
||||||
samlRequest = await parseSAMLAuthnRequest(request.query.SAMLRequest);
|
|
||||||
} catch (error) {
|
|
||||||
return response.badRequest({
|
|
||||||
body: '[request query.SAMLRequest]: value is not valid SAMLRequest.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const userRoles: Array<[string, string]> = [
|
|
||||||
['system_indices_superuser', 'system_indices_superuser'],
|
|
||||||
['t1_analyst', 't1_analyst'],
|
|
||||||
['t2_analyst', 't2_analyst'],
|
|
||||||
['t3_analyst', 't3_analyst'],
|
|
||||||
['threat_intelligence_analyst', 'threat_intelligence_analyst'],
|
|
||||||
['rule_author', 'rule_author'],
|
|
||||||
['soc_manager', 'soc_manager'],
|
|
||||||
['detections_admin', 'detections_admin'],
|
|
||||||
['platform_engineer', 'platform_engineer'],
|
|
||||||
['endpoint_operations_analyst', 'endpoint_operations_analyst'],
|
|
||||||
['endpoint_policy_manager', 'endpoint_policy_manager'],
|
|
||||||
];
|
|
||||||
|
|
||||||
const samlResponses = await Promise.all(
|
|
||||||
userRoles.map(([username, role]) =>
|
|
||||||
createSAMLResponse({
|
|
||||||
authnRequestId: samlRequest.ID,
|
|
||||||
kibanaUrl: samlRequest.AssertionConsumerServiceURL,
|
|
||||||
username,
|
|
||||||
roles: [role],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return response.renderHtml({
|
|
||||||
body: `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<title>Mock Identity Provider</title>
|
|
||||||
<link rel="icon" href="data:,">
|
|
||||||
<body>
|
|
||||||
<h2>Mock Identity Provider</h2>
|
|
||||||
<form id="loginForm" method="post" action="${
|
|
||||||
samlRequest.AssertionConsumerServiceURL
|
|
||||||
}">
|
|
||||||
<h3>Pick a role:<h3>
|
|
||||||
<ul>
|
|
||||||
${userRoles
|
|
||||||
.map(
|
|
||||||
([username], i) =>
|
|
||||||
`
|
|
||||||
<li>
|
|
||||||
<button name="SAMLResponse" value="${samlResponses[i]}">${username}</button>
|
|
||||||
</li>
|
|
||||||
`
|
|
||||||
)
|
|
||||||
.join('')}
|
|
||||||
</ul>
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
core.http.resources.register(
|
|
||||||
{
|
|
||||||
path: `${MOCK_IDP_LOGIN_PATH}/submit.js`,
|
|
||||||
validate: false,
|
|
||||||
options: { authRequired: false },
|
|
||||||
},
|
|
||||||
(context, request, response) => {
|
|
||||||
return response.renderJs({ body: 'document.getElementById("loginForm").submit();' });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
core.http.resources.register(
|
|
||||||
{
|
|
||||||
path: MOCK_IDP_LOGOUT_PATH,
|
|
||||||
validate: false,
|
|
||||||
options: { authRequired: false },
|
|
||||||
},
|
|
||||||
async (context, request, response) => {
|
|
||||||
return response.redirected({ headers: { location: '/' } });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
start() {},
|
|
||||||
stop() {},
|
|
||||||
});
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "target/types"
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"**/*.ts",
|
|
||||||
"**/*.tsx"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"target/**/*"
|
|
||||||
],
|
|
||||||
"kbn_references": [
|
|
||||||
"@kbn/core-plugins-server",
|
|
||||||
"@kbn/config-schema",
|
|
||||||
"@kbn/dev-utils"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -20,7 +20,6 @@ kbnEs
|
||||||
'source-path': resolve(__dirname, '../../elasticsearch'),
|
'source-path': resolve(__dirname, '../../elasticsearch'),
|
||||||
'base-path': resolve(__dirname, '../.es'),
|
'base-path': resolve(__dirname, '../.es'),
|
||||||
ssl: false,
|
ssl: false,
|
||||||
kibanaUrl: 'https://localhost:5601/',
|
|
||||||
})
|
})
|
||||||
.catch(function (e) {
|
.catch(function (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -15,7 +15,6 @@ import { isKibanaDistributable } from '@kbn/repo-info';
|
||||||
import { readKeystore } from '../keystore/read_keystore';
|
import { readKeystore } from '../keystore/read_keystore';
|
||||||
import { compileConfigStack } from './compile_config_stack';
|
import { compileConfigStack } from './compile_config_stack';
|
||||||
import { getConfigFromFiles } from '@kbn/config';
|
import { getConfigFromFiles } from '@kbn/config';
|
||||||
import { MOCK_IDP_PLUGIN_PATH, MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-plugin/common';
|
|
||||||
|
|
||||||
const DEV_MODE_PATH = '@kbn/cli-dev-mode';
|
const DEV_MODE_PATH = '@kbn/cli-dev-mode';
|
||||||
const DEV_MODE_SUPPORTED = canRequire(DEV_MODE_PATH);
|
const DEV_MODE_SUPPORTED = canRequire(DEV_MODE_PATH);
|
||||||
|
@ -110,25 +109,6 @@ export function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
|
||||||
if (opts.dev) {
|
if (opts.dev) {
|
||||||
if (opts.serverless) {
|
if (opts.serverless) {
|
||||||
setServerlessKibanaDevServiceAccountIfPossible(get, set, opts);
|
setServerlessKibanaDevServiceAccountIfPossible(get, set, opts);
|
||||||
|
|
||||||
// Load mock identity provider plugin and configure realm if supported (ES only supports SAML when run with SSL)
|
|
||||||
if (opts.ssl) {
|
|
||||||
set('plugins.paths', _.compact([].concat(get('plugins.paths'), MOCK_IDP_PLUGIN_PATH)));
|
|
||||||
set(`xpack.security.authc.providers.saml.${MOCK_IDP_REALM_NAME}`, {
|
|
||||||
order: Number.MAX_SAFE_INTEGER,
|
|
||||||
realm: MOCK_IDP_REALM_NAME,
|
|
||||||
icon: 'user',
|
|
||||||
description: 'Continue as Test User',
|
|
||||||
hint: 'Allows testing serverless user roles',
|
|
||||||
});
|
|
||||||
// Add basic realm since defaults won't be applied when a provider has been configured
|
|
||||||
if (!has('xpack.security.authc.providers.basic')) {
|
|
||||||
set('xpack.security.authc.providers.basic.basic', {
|
|
||||||
order: 0,
|
|
||||||
enabled: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!has('elasticsearch.serviceAccountToken') && opts.devCredentials !== false) {
|
if (!has('elasticsearch.serviceAccountToken') && opts.devCredentials !== false) {
|
||||||
|
@ -294,9 +274,7 @@ export default function (program) {
|
||||||
// We can tell users they only have to run with `yarn start --run-examples` to get those
|
// We can tell users they only have to run with `yarn start --run-examples` to get those
|
||||||
// local links to work. Similar to what we do for "View in Console" links in our
|
// local links to work. Similar to what we do for "View in Console" links in our
|
||||||
// elastic.co links.
|
// elastic.co links.
|
||||||
// We also want to run without base path when running in serverless mode so that Elasticsearch can
|
basePath: opts.runExamples ? false : !!opts.basePath,
|
||||||
// connect to Kibana's mock identity provider.
|
|
||||||
basePath: opts.runExamples || isServerlessMode ? false : !!opts.basePath,
|
|
||||||
optimize: !!opts.optimize,
|
optimize: !!opts.optimize,
|
||||||
disableOptimizer: !opts.optimizer,
|
disableOptimizer: !opts.optimizer,
|
||||||
oss: !!opts.oss,
|
oss: !!opts.oss,
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
"@kbn/config",
|
"@kbn/config",
|
||||||
"@kbn/dev-utils",
|
"@kbn/dev-utils",
|
||||||
"@kbn/apm-config-loader",
|
"@kbn/apm-config-loader",
|
||||||
"@kbn/mock-idp-plugin",
|
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"target/**/*",
|
"target/**/*",
|
||||||
|
|
|
@ -1062,8 +1062,6 @@
|
||||||
"@kbn/ml-trained-models-utils/*": ["x-pack/packages/ml/trained_models_utils/*"],
|
"@kbn/ml-trained-models-utils/*": ["x-pack/packages/ml/trained_models_utils/*"],
|
||||||
"@kbn/ml-url-state": ["x-pack/packages/ml/url_state"],
|
"@kbn/ml-url-state": ["x-pack/packages/ml/url_state"],
|
||||||
"@kbn/ml-url-state/*": ["x-pack/packages/ml/url_state/*"],
|
"@kbn/ml-url-state/*": ["x-pack/packages/ml/url_state/*"],
|
||||||
"@kbn/mock-idp-plugin": ["packages/kbn-mock-idp-plugin"],
|
|
||||||
"@kbn/mock-idp-plugin/*": ["packages/kbn-mock-idp-plugin/*"],
|
|
||||||
"@kbn/monaco": ["packages/kbn-monaco"],
|
"@kbn/monaco": ["packages/kbn-monaco"],
|
||||||
"@kbn/monaco/*": ["packages/kbn-monaco/*"],
|
"@kbn/monaco/*": ["packages/kbn-monaco/*"],
|
||||||
"@kbn/monitoring-collection-plugin": ["x-pack/plugins/monitoring_collection"],
|
"@kbn/monitoring-collection-plugin": ["x-pack/plugins/monitoring_collection"],
|
||||||
|
|
|
@ -5028,10 +5028,6 @@
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
uid ""
|
uid ""
|
||||||
|
|
||||||
"@kbn/mock-idp-plugin@link:packages/kbn-mock-idp-plugin":
|
|
||||||
version "0.0.0"
|
|
||||||
uid ""
|
|
||||||
|
|
||||||
"@kbn/monaco@link:packages/kbn-monaco":
|
"@kbn/monaco@link:packages/kbn-monaco":
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
uid ""
|
uid ""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue