Skip to content

Commit

Permalink
Add mock identity provider for serverless (2nd attempt) (elastic#171513)
Browse files Browse the repository at this point in the history
Attempting to merge elastic#170852 again now that the release artefact step has
been fixed as part of elastic#171457

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Aleh Zasypkin <[email protected]>
Co-authored-by: Dzmitry Lemechko <[email protected]>
  • Loading branch information
4 people authored Nov 23, 2023
1 parent 5fa20cc commit 9220e4d
Show file tree
Hide file tree
Showing 20 changed files with 722 additions and 41 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ x-pack/packages/ml/string_hash @elastic/ml-ui
x-pack/packages/ml/trained_models_utils @elastic/ml-ui
x-pack/packages/ml/ui_actions @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
x-pack/plugins/monitoring_collection @elastic/obs-ux-infra_services-team
x-pack/plugins/monitoring @elastic/obs-ux-infra_services-team
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,7 @@
"@kbn/managed-vscode-config": "link:packages/kbn-managed-vscode-config",
"@kbn/managed-vscode-config-cli": "link:packages/kbn-managed-vscode-config-cli",
"@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/optimizer": "link:packages/kbn-optimizer",
"@kbn/optimizer-webpack-helpers": "link:packages/kbn-optimizer-webpack-helpers",
Expand Down
3 changes: 2 additions & 1 deletion packages/kbn-es/src/cli_commands/serverless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const serverless: Command = {
--host Publish ES docker container on additional host IP
--port The port to bind to on 127.0.0.1 [default: ${DEFAULT_PORT}]
--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
--waitForReady Wait for the ES cluster to be ready to serve requests
--resources Overrides resources under ES 'config/' directory, which are by default
Expand Down Expand Up @@ -73,7 +74,7 @@ export const serverless: Command = {
files: 'F',
},

string: ['tag', 'image', 'basePath', 'resources', 'host'],
string: ['tag', 'image', 'basePath', 'resources', 'host', 'kibanaUrl'],
boolean: ['clean', 'ssl', 'kill', 'background', 'skipTeardown', 'waitForReady'],

default: defaults,
Expand Down
3 changes: 3 additions & 0 deletions packages/kbn-es/src/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import Os from 'os';
import { resolve } from 'path';
import { REPO_ROOT } from '@kbn/repo-info';

function maybeUseBat(bin: string) {
return Os.platform().startsWith('win') ? `${bin}.bat` : bin;
Expand Down Expand Up @@ -51,6 +52,8 @@ export const SERVERLESS_SECRETS_SSL_PATH = resolve(

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 = [
SERVERLESS_OPERATOR_USERS_PATH,
SERVERLESS_ROLE_MAPPING_PATH,
Expand Down
106 changes: 101 additions & 5 deletions packages/kbn-es/src/utils/docker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@ import {
ServerlessOptions,
} from './docker';
import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log';
import { ES_P12_PATH } from '@kbn/dev-utils';
import { CA_CERT_PATH, ES_P12_PATH } from '@kbn/dev-utils';
import {
SERVERLESS_CONFIG_PATH,
SERVERLESS_RESOURCES_PATHS,
SERVERLESS_SECRETS_PATH,
SERVERLESS_JWKS_PATH,
SERVERLESS_IDP_METADATA_PATH,
} from '../paths';
import * as waitClusterUtil from './wait_until_cluster_ready';
import * as waitForSecurityIndexUtil from './wait_for_security_index';
import * as mockIdpPluginUtil from '@kbn/mock-idp-plugin/common';

jest.mock('execa');
const execa = jest.requireMock('execa');
Expand All @@ -58,6 +60,8 @@ jest.mock('./wait_for_security_index', () => ({
waitForSecurityIndex: jest.fn(),
}));

jest.mock('@kbn/mock-idp-plugin/common');

const log = new ToolingLog();
const logWriter = new ToolingLogCollectingWriter();
log.setWriters([logWriter]);
Expand All @@ -69,6 +73,8 @@ const serverlessObjectStorePath = `${baseEsPath}/${serverlessDir}`;

const waitUntilClusterReadyMock = jest.spyOn(waitClusterUtil, 'waitUntilClusterReady');
const waitForSecurityIndexMock = jest.spyOn(waitForSecurityIndexUtil, 'waitForSecurityIndex');
const ensureSAMLRoleMappingMock = jest.spyOn(mockIdpPluginUtil, 'ensureSAMLRoleMapping');
const createMockIdpMetadataMock = jest.spyOn(mockIdpPluginUtil, 'createMockIdpMetadata');

beforeEach(() => {
jest.resetAllMocks();
Expand Down Expand Up @@ -423,6 +429,66 @@ 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()', () => {
Expand Down Expand Up @@ -463,21 +529,29 @@ describe('setupServerlessVolumes()', () => {
expect(existsSync(`${serverlessObjectStorePath}/cluster_state/lease`)).toBe(false);
});

test('should add SSL volumes when ssl is passed', async () => {
test('should add SSL and IDP metadata volumes when ssl and kibanaUrl are passed', async () => {
mockFs(existingObjectStore);
createMockIdpMetadataMock.mockResolvedValue('<xml/>');

const volumeCmd = await setupServerlessVolumes(log, { basePath: baseEsPath, ssl: true });
const volumeCmd = await setupServerlessVolumes(log, {
basePath: baseEsPath,
ssl: true,
kibanaUrl: 'https://localhost:5603/',
});

expect(createMockIdpMetadataMock).toHaveBeenCalledTimes(1);
expect(createMockIdpMetadataMock).toHaveBeenCalledWith('https://localhost:5603/');

const requiredPaths = [
`${baseEsPath}:/objectstore:z`,
SERVERLESS_IDP_METADATA_PATH,
ES_P12_PATH,
...SERVERLESS_RESOURCES_PATHS,
];
const pathsNotIncludedInCmd = requiredPaths.filter(
(path) => !volumeCmd.some((cmd) => cmd.includes(path))
);

expect(volumeCmd).toHaveLength(20);
expect(volumeCmd).toHaveLength(22);
expect(pathsNotIncludedInCmd).toEqual([]);
});

Expand Down Expand Up @@ -543,6 +617,7 @@ describe('runServerlessEsNode()', () => {

describe('runServerlessCluster()', () => {
test('should start 3 serverless nodes', async () => {
waitUntilClusterReadyMock.mockResolvedValue();
mockFs({
[baseEsPath]: {},
});
Expand All @@ -567,7 +642,27 @@ describe('runServerlessCluster()', () => {
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 () => {
waitUntilClusterReadyMock.mockResolvedValue();
waitForSecurityIndexMock.mockResolvedValue();
mockFs({
[baseEsPath]: {},
Expand All @@ -580,6 +675,7 @@ describe('runServerlessCluster()', () => {
});

test(`should not wait for the security index when security is disabled`, async () => {
waitUntilClusterReadyMock.mockResolvedValue();
mockFs({
[baseEsPath]: {},
});
Expand Down
Loading

0 comments on commit 9220e4d

Please sign in to comment.