[kbn/es serverless] Enable security and transport ssl by default (#166023)

## Summary

Due to most users needing security plugin enabled by default, this
adjusts to that. Which requires transport SSL to be enabled as well. The
`--ssl` flag now will enable HTTP SSL only.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Aleh Zasypkin <aleh.zasypkin@gmail.com>
This commit is contained in:
Brad White 2023-09-11 18:01:55 -06:00 committed by GitHub
parent 127d4dfce7
commit d7573c77a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 101 additions and 99 deletions

View file

@ -86,6 +86,8 @@ function createServerlessES() {
clean: true,
kill: true,
waitForReady: true,
// security is enabled by default, if needed kibana requires serviceAccountToken
esArgs: ['xpack.security.enabled=false'],
});
const client = getServerlessESClient({ port: esPort });

View file

@ -28,7 +28,7 @@ export const docker: Command = {
--image Full path to image of ES to run, has precedence over tag. [default: ${DOCKER_IMG}]
--password Sets password for elastic user [default: ${password}]
--port The port to bind to on 127.0.0.1 [default: ${DEFAULT_PORT}]
--ssl Sets up SSL and enables security plugin on Elasticsearch
--ssl Sets up HTTP and Transport SSL and enables security plugin on Elasticsearch
--kill Kill running ES nodes if detected
-E Additional key=value settings to pass to Elasticsearch
-D Override Docker command

View file

@ -26,7 +26,7 @@ export const serverless: Command = {
--image Full path of ESS image to run, has precedence over tag. [default: ${SERVERLESS_IMG}]
--clean Remove existing file system object store before running
--port The port to bind to on 127.0.0.1 [default: ${DEFAULT_PORT}]
--ssl Sets up SSL and enables security plugin on Elasticsearch
--ssl Enable HTTP SSL on Elasticsearch
--kill Kill running ESS nodes if detected
--background Start ESS without attaching to the first node's logs
-E Additional key=value settings to pass to Elasticsearch

View file

@ -4,7 +4,6 @@
"compatibility": "8.11.0"
},
"string_secrets": {
"xpack.security.http.ssl.keystore.secure_password": "storepass",
"xpack.security.transport.ssl.keystore.secure_password": "storepass",
"xpack.security.authc.realms.jwt.jwt1.client_authentication.shared_secret": "my_super_secret"
}

View file

@ -0,0 +1,11 @@
{
"metadata": {
"version": "1",
"compatibility": "8.11.0"
},
"string_secrets": {
"xpack.security.http.ssl.keystore.secure_password": "storepass",
"xpack.security.transport.ssl.keystore.secure_password": "storepass",
"xpack.security.authc.realms.jwt.jwt1.client_authentication.shared_secret": "my_super_secret"
}
}

View file

@ -35,6 +35,8 @@ export const ESS_ROLE_MAPPING_PATH = resolve(__dirname, './ess_resources/role_ma
export const ESS_SECRETS_PATH = resolve(__dirname, './ess_resources/secrets.json');
export const ESS_SECRETS_SSL_PATH = resolve(__dirname, './ess_resources/secrets_ssl.json');
export const ESS_JWKS_PATH = resolve(__dirname, './ess_resources/jwks.json');
export const ESS_RESOURCES_PATHS = [

View file

@ -9,6 +9,7 @@ import mockFs from 'mock-fs';
import { existsSync } from 'fs';
import { stat } from 'fs/promises';
import { basename } from 'path';
import {
DOCKER_IMG,
@ -27,10 +28,11 @@ import {
stopServerlessCluster,
teardownServerlessClusterSync,
verifyDockerInstalled,
getESp12Volume,
} from './docker';
import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log';
import { ES_P12_PATH } from '@kbn/dev-utils';
import { ESS_RESOURCES_PATHS } from '../paths';
import { ESS_CONFIG_PATH, ESS_RESOURCES_PATHS, ESS_SECRETS_PATH, ESS_JWKS_PATH } from '../paths';
jest.mock('execa');
const execa = jest.requireMock('execa');
@ -68,9 +70,23 @@ afterEach(() => {
jest.clearAllMocks();
});
const essResources = ESS_RESOURCES_PATHS.reduce<string[]>((acc, path) => {
acc.push(`${path}:${ESS_CONFIG_PATH}${basename(path)}`);
return acc;
}, []);
const volumeCmdTest = async (volumeCmd: string[]) => {
expect(volumeCmd).toHaveLength(2);
expect(volumeCmd).toEqual(expect.arrayContaining(['--volume', `${baseEsPath}:/objectstore:z`]));
expect(volumeCmd).toHaveLength(20);
expect(volumeCmd).toEqual(
expect.arrayContaining([
...getESp12Volume(),
...essResources,
`${baseEsPath}:/objectstore:z`,
`${ESS_SECRETS_PATH}:${ESS_CONFIG_PATH}secrets/secrets.json:z`,
`${ESS_JWKS_PATH}:${ESS_CONFIG_PATH}secrets/jwks.json:z`,
])
);
// extract only permission from mode
// eslint-disable-next-line no-bitwise
@ -341,13 +357,10 @@ describe('resolveEsArgs()', () => {
`);
});
test('should add SSL args and enable security when SSL is passed', () => {
const esArgs = resolveEsArgs([...defaultEsArgs, ['xpack.security.enabled', 'false']], {
ssl: true,
});
test('should add SSL args when SSL is passed', () => {
const esArgs = resolveEsArgs(defaultEsArgs, { ssl: true });
expect(esArgs).toHaveLength(20);
expect(esArgs).not.toEqual(expect.arrayContaining(['xpack.security.enabled=false']));
expect(esArgs).toHaveLength(10);
expect(esArgs).toMatchInlineSnapshot(`
Array [
"--env",
@ -355,21 +368,11 @@ describe('resolveEsArgs()', () => {
"--env",
"qux=zip",
"--env",
"xpack.security.enabled=true",
"--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.transport.ssl.enabled=true",
"--env",
"xpack.security.transport.ssl.keystore.path=/usr/share/elasticsearch/config/certs/elasticsearch.p12",
"--env",
"xpack.security.transport.ssl.verification_mode=certificate",
"--env",
"xpack.security.operator_privileges.enabled=true",
]
`);
});

View file

@ -29,6 +29,7 @@ import {
ESS_JWKS_PATH,
ESS_CONFIG_PATH,
ESS_FILES_PATH,
ESS_SECRETS_SSL_PATH,
} from '../paths';
import {
ELASTIC_SERVERLESS_SUPERUSER,
@ -153,44 +154,49 @@ const DEFAULT_SERVERLESS_ESARGS: Array<[string, string]> = [
['xpack.ml.enabled', 'true'],
['xpack.security.enabled', 'false'],
];
const DEFAULT_SSL_ESARGS: Array<[string, string]> = [
['xpack.security.enabled', 'true'],
['xpack.security.http.ssl.enabled', 'true'],
// JWT realm settings are to closer emulate a real ES serverless env
['xpack.security.authc.realms.jwt.jwt1.allowed_audiences', 'elasticsearch'],
['xpack.security.http.ssl.keystore.path', `${ESS_CONFIG_PATH}certs/elasticsearch.p12`],
['xpack.security.authc.realms.jwt.jwt1.allowed_issuer', 'https://kibana.elastic.co/jwt/'],
['xpack.security.http.ssl.verification_mode', 'certificate'],
['xpack.security.authc.realms.jwt.jwt1.claims.principal', 'sub'],
['xpack.security.authc.realms.jwt.jwt1.client_authentication.type', 'shared_secret'],
['xpack.security.authc.realms.jwt.jwt1.order', '-98'],
['xpack.security.authc.realms.jwt.jwt1.pkc_jwkset_path', `${ESS_CONFIG_PATH}secrets/jwks.json`],
['xpack.security.operator_privileges.enabled', 'true'],
['xpack.security.transport.ssl.enabled', 'true'],
['xpack.security.transport.ssl.keystore.path', `${ESS_CONFIG_PATH}certs/elasticsearch.p12`],
['xpack.security.transport.ssl.verification_mode', 'certificate'],
['xpack.security.operator_privileges.enabled', 'true'],
];
const SERVERLESS_SSL_ESARGS: Array<[string, string]> = [
['xpack.security.authc.realms.jwt.jwt1.client_authentication.type', 'shared_secret'],
const DEFAULT_SSL_ESARGS: Array<[string, string]> = [
['xpack.security.http.ssl.enabled', 'true'],
['xpack.security.authc.realms.jwt.jwt1.order', '-98'],
['xpack.security.http.ssl.keystore.path', `${ESS_CONFIG_PATH}certs/elasticsearch.p12`],
['xpack.security.authc.realms.jwt.jwt1.allowed_issuer', 'https://kibana.elastic.co/jwt/'],
['xpack.security.authc.realms.jwt.jwt1.allowed_audiences', 'elasticsearch'],
['xpack.security.authc.realms.jwt.jwt1.pkc_jwkset_path', `${ESS_CONFIG_PATH}secrets/jwks.json`],
['xpack.security.authc.realms.jwt.jwt1.claims.principal', 'sub'],
['xpack.security.http.ssl.verification_mode', 'certificate'],
];
const DOCKER_SSL_ESARGS: Array<[string, string]> = [
['xpack.security.enabled', 'true'],
['xpack.security.http.ssl.keystore.password', ES_P12_PASSWORD],
['xpack.security.transport.ssl.enabled', 'true'],
['xpack.security.transport.ssl.keystore.path', `${ESS_CONFIG_PATH}certs/elasticsearch.p12`],
['xpack.security.transport.ssl.verification_mode', 'certificate'],
['xpack.security.transport.ssl.keystore.password', ES_P12_PASSWORD],
];
@ -418,13 +424,6 @@ export function resolveEsArgs(
args.forEach((arg) => {
const [key, ...value] = arg.split('=');
// Guide the user to use SSL flag instead of manual setup
if (key === 'xpack.security.enabled' && value?.[0] === 'true') {
throw createCliError(
'Use the --ssl flag to automatically enable and set up the security plugin.'
);
}
esArgs.set(key.trim(), value.join('=').trim());
});
}
@ -436,7 +435,7 @@ export function resolveEsArgs(
return Array.from(esArgs).flatMap((e) => ['--env', e.join('=')]);
}
function getESp12Volume() {
export function getESp12Volume() {
return ['--volume', `${ES_P12_PATH}:${ESS_CONFIG_PATH}certs/elasticsearch.p12`];
}
@ -492,24 +491,22 @@ export async function setupServerlessVolumes(log: ToolingLog, options: Serverles
volumeCmds.push(...fileCmds);
}
if (ssl) {
const essResources = ESS_RESOURCES_PATHS.reduce<string[]>((acc, path) => {
acc.push('--volume', `${path}:${ESS_CONFIG_PATH}${basename(path)}`);
const essResources = ESS_RESOURCES_PATHS.reduce<string[]>((acc, path) => {
acc.push('--volume', `${path}:${ESS_CONFIG_PATH}${basename(path)}`);
return acc;
}, []);
return acc;
}, []);
volumeCmds.push(
...getESp12Volume(),
...essResources,
volumeCmds.push(
...getESp12Volume(),
...essResources,
'--volume',
`${ESS_SECRETS_PATH}:${ESS_CONFIG_PATH}secrets/secrets.json:z`,
'--volume',
`${ssl ? ESS_SECRETS_SSL_PATH : ESS_SECRETS_PATH}:${ESS_CONFIG_PATH}secrets/secrets.json:z`,
'--volume',
`${ESS_JWKS_PATH}:${ESS_CONFIG_PATH}secrets/jwks.json:z`
);
}
'--volume',
`${ESS_JWKS_PATH}:${ESS_CONFIG_PATH}secrets/jwks.json:z`
);
return volumeCmds;
}
@ -577,13 +574,7 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO
...node,
image,
params: node.params.concat(
resolveEsArgs(
DEFAULT_SERVERLESS_ESARGS.concat(
node.esArgs ?? [],
options.ssl ? SERVERLESS_SSL_ESARGS : []
),
options
),
resolveEsArgs(DEFAULT_SERVERLESS_ESARGS.concat(node.esArgs ?? []), options),
i === 0 ? portCmd : [],
volumeCmd
),
@ -593,20 +584,15 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO
);
log.success(`Serverless ES cluster running.
Stop the cluster: ${chalk.bold(`docker container stop ${nodeNames.join(' ')}`)}
Login with username ${chalk.bold.cyan(ELASTIC_SERVERLESS_SUPERUSER)} or ${chalk.bold.cyan(
SYSTEM_INDICES_SUPERUSER
)} and password ${chalk.bold.magenta(ELASTIC_SERVERLESS_SUPERUSER_PASSWORD)}
Stop the cluster: ${chalk.bold(`docker container stop ${nodeNames.join(' ')}`)}
`);
if (options.ssl) {
log.success(`SSL and Security have been enabled for ES.
Login through your browser with username ${chalk.bold.cyan(
ELASTIC_SERVERLESS_SUPERUSER
)} or ${chalk.bold.cyan(SYSTEM_INDICES_SUPERUSER)} and password ${chalk.bold.magenta(
ELASTIC_SERVERLESS_SUPERUSER_PASSWORD
)}.
`);
log.warning(`Kibana should be started with the SSL flag so that it can authenticate with ES.
See packages/kbn-es/src/ess_resources/README.md for additional information on authentication.
log.warning(`SSL has been enabled for ES. Kibana should be started with the SSL flag so that it can authenticate with ES.
See packages/kbn-es/src/ess_resources/README.md for additional information on authentication.
`);
}
@ -620,9 +606,9 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO
const client = getESClient({
node: esNodeUrl,
auth: { bearer: kibanaDevServiceAccount.token },
...(options.ssl
? {
auth: { bearer: kibanaDevServiceAccount.token },
tls: {
ca: [fs.readFileSync(CA_CERT_PATH)],
// NOTE: Even though we've added ca into the tls options, we are using 127.0.0.1 instead of localhost

View file

@ -234,9 +234,9 @@ export function createTestEsCluster<
port,
clean: true,
teardown: true,
ssl: true,
background: true,
files,
ssl,
kill: true, // likely don't need this but avoids any issues where the ESS cluster wasn't cleaned up
waitForReady: true,
});

View file

@ -52,31 +52,30 @@ export default async () => {
files: [idpPath, jwksPath],
serverArgs: [
'xpack.security.authc.realms.file.file1.order=-100',
'xpack.security.authc.realms.jwt.jwt1.order=-98',
`xpack.security.authc.realms.jwt.jwt1.token_type=access_token`,
'xpack.security.authc.realms.jwt.jwt1.client_authentication.type=shared_secret',
`xpack.security.authc.realms.jwt.jwt1.allowed_issuer=https://kibana.elastic.co/jwt/`,
`xpack.security.authc.realms.jwt.jwt1.allowed_subjects=elastic-agent`,
'xpack.security.authc.realms.jwt.jwt1.allowed_audiences=elasticsearch',
`xpack.security.authc.realms.jwt.jwt1.allowed_signature_algorithms=[RS256]`,
`xpack.security.authc.realms.jwt.jwt1.claims.principal=sub`,
`xpack.security.authc.realms.jwt.jwt1.pkc_jwkset_path=${getDockerFileMountPath(jwksPath)}`,
`xpack.security.authc.realms.native.native1.enabled=false`,
`xpack.security.authc.realms.native.native1.order=-97`,
'xpack.security.authc.token.enabled=true',
'xpack.security.authc.realms.jwt.jwt1.allowed_audiences=elasticsearch',
`xpack.security.authc.realms.jwt.jwt1.allowed_issuer=https://kibana.elastic.co/jwt/`,
`xpack.security.authc.realms.jwt.jwt1.allowed_signature_algorithms=[RS256]`,
`xpack.security.authc.realms.jwt.jwt1.allowed_subjects=elastic-agent`,
`xpack.security.authc.realms.jwt.jwt1.claims.principal=sub`,
'xpack.security.authc.realms.jwt.jwt1.client_authentication.type=shared_secret',
'xpack.security.authc.realms.jwt.jwt1.order=-98',
`xpack.security.authc.realms.jwt.jwt1.pkc_jwkset_path=${getDockerFileMountPath(jwksPath)}`,
`xpack.security.authc.realms.jwt.jwt1.token_type=access_token`,
'xpack.security.authc.realms.saml.cloud-saml-kibana.attributes.principal=urn:oid:0.0.7',
'xpack.security.authc.realms.saml.cloud-saml-kibana.idp.entity_id=http://www.elastic.co/saml1',
'xpack.security.authc.realms.saml.cloud-saml-kibana.order=101',
`xpack.security.authc.realms.saml.cloud-saml-kibana.idp.metadata.path=${getDockerFileMountPath(
idpPath
)}`,
'xpack.security.authc.realms.saml.cloud-saml-kibana.idp.entity_id=http://www.elastic.co/saml1',
`xpack.security.authc.realms.saml.cloud-saml-kibana.sp.acs=http://localhost:${servers.kibana.port}/api/security/saml/callback`,
`xpack.security.authc.realms.saml.cloud-saml-kibana.sp.entity_id=http://localhost:${servers.kibana.port}`,
`xpack.security.authc.realms.saml.cloud-saml-kibana.sp.logout=http://localhost:${servers.kibana.port}/logout`,
`xpack.security.authc.realms.saml.cloud-saml-kibana.sp.acs=http://localhost:${servers.kibana.port}/api/security/saml/callback`,
'xpack.security.authc.realms.saml.cloud-saml-kibana.attributes.principal=urn:oid:0.0.7',
],
ssl: true, // not needed as for serverless ssl is always on but added it anyway
ssl: true, // SSL is required for SAML realm
},
kbnTestServer: {