Interactive setup functional tests (#115274)

This commit is contained in:
Thom Heymann 2021-10-26 19:03:00 +01:00 committed by GitHub
parent e185afefa2
commit 99d07acfce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 534 additions and 12 deletions

1
.github/CODEOWNERS vendored
View file

@ -289,6 +289,7 @@
/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-core
/src/plugins/interactive_setup/ @elastic/kibana-security
/test/interactive_setup_api_integration/ @elastic/kibana-security
/test/interactive_setup_functional/ @elastic/kibana-security
/x-pack/plugins/spaces/ @elastic/kibana-security
/x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security
/x-pack/plugins/security/ @elastic/kibana-security

View file

@ -17,6 +17,14 @@ const alwaysImportedTests = [
require.resolve(
'../test/interactive_setup_api_integration/manual_configuration_flow_without_tls.config.ts'
),
require.resolve('../test/interactive_setup_functional/enrollment_token.config.ts'),
require.resolve('../test/interactive_setup_functional/manual_configuration.config.ts'),
require.resolve(
'../test/interactive_setup_functional/manual_configuration_without_security.config.ts'
),
require.resolve(
'../test/interactive_setup_functional/manual_configuration_without_tls.config.ts'
),
];
// eslint-disable-next-line no-restricted-syntax
const onlyNotInCoverageTests = [

View file

@ -16,31 +16,48 @@ import useTimeoutFn from 'react-use/lib/useTimeoutFn';
import { i18n } from '@kbn/i18n';
import type { IHttpFetchError } from 'kibana/public';
import type { StatusResponse } from '../../../core/types/status';
import { useKibana } from './use_kibana';
export interface ProgressIndicatorProps {
onSuccess?(): void;
}
function isKibanaPastPreboot(response?: Response, body?: StatusResponse) {
if (!response?.headers.get('content-type')?.includes('application/json')) {
return false;
}
return (
// Status endpoint may require authentication after `preboot` stage.
response?.status === 401 ||
// We're only interested in the availability of the critical core services.
(body?.status?.core?.elasticsearch?.level === 'available' &&
body?.status?.core?.savedObjects?.level === 'available')
);
}
export const ProgressIndicator: FunctionComponent<ProgressIndicatorProps> = ({ onSuccess }) => {
const { http } = useKibana();
const [status, checkStatus] = useAsyncFn(async () => {
let isAvailable: boolean | undefined = false;
let isPastPreboot: boolean | undefined = false;
try {
const { response } = await http.get('/api/status', { asResponse: true });
const { response, body } = await http.get<StatusResponse | undefined>('/api/status', {
asResponse: true,
});
isAvailable = response ? response.status < 500 : undefined;
isPastPreboot = response?.headers.get('content-type')?.includes('application/json');
isPastPreboot = isKibanaPastPreboot(response, body);
} catch (error) {
const { response } = error as IHttpFetchError;
const { response, body = {} } = error as IHttpFetchError;
isAvailable = response ? response.status < 500 : undefined;
isPastPreboot = response?.headers.get('content-type')?.includes('application/json');
isPastPreboot = isKibanaPastPreboot(response, body);
}
return isAvailable === true && isPastPreboot === true
return isAvailable === true && isPastPreboot
? 'complete'
: isAvailable === false
? 'unavailable'
: isAvailable === true && isPastPreboot === false
: isAvailable === true && !isPastPreboot
? 'preboot'
: 'unknown';
});

View file

@ -183,8 +183,8 @@ describe('KibanaConfigWriter', () => {
# This section was automatically generated during setup.
elasticsearch.hosts: [some-host]
elasticsearch.password: password
elasticsearch.username: username
elasticsearch.password: password
elasticsearch.ssl.certificateAuthorities: [/data/ca_1234.crt]
",
@ -212,8 +212,8 @@ describe('KibanaConfigWriter', () => {
# This section was automatically generated during setup.
elasticsearch.hosts: [some-host]
elasticsearch.password: password
elasticsearch.username: username
elasticsearch.password: password
",
],

View file

@ -62,11 +62,11 @@ export class KibanaConfigWriter {
public async writeConfig(params: WriteConfigParameters) {
const caPath = path.join(this.dataDirectoryPath, `ca_${Date.now()}.crt`);
const config: Record<string, string | string[]> = { 'elasticsearch.hosts': [params.host] };
if ('serviceAccountToken' in params) {
if ('serviceAccountToken' in params && params.serviceAccountToken) {
config['elasticsearch.serviceAccountToken'] = params.serviceAccountToken.value;
} else if ('username' in params) {
config['elasticsearch.password'] = params.password;
} else if ('username' in params && params.username) {
config['elasticsearch.username'] = params.username;
config['elasticsearch.password'] = params.password;
}
if (params.caCert) {
config['elasticsearch.ssl.certificateAuthorities'] = [caPath];

View file

@ -46,7 +46,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
.filter((arg: string) => !arg.startsWith('--elasticsearch.')),
`--plugin-path=${testEndpointsPlugin}`,
`--config=${tempKibanaYamlFile}`,
'--interactiveSetup.enabled=true',
],
runOptions: {
...xPackAPITestsConfig.get('kbnTestServer.runOptions'),

View file

@ -0,0 +1,57 @@
/*
* 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 fs from 'fs/promises';
import { join, resolve } from 'path';
import type { FtrConfigProviderContext } from '@kbn/test';
import { getDataPath } from '@kbn/utils';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const manualConfigurationConfig = await readConfigFile(
require.resolve('./manual_configuration.config.ts')
);
const tempKibanaYamlFile = join(getDataPath(), `interactive_setup_kibana_${Date.now()}.yml`);
await fs.writeFile(tempKibanaYamlFile, '');
const caPath = resolve(
__dirname,
'../interactive_setup_api_integration/fixtures/elasticsearch.p12'
);
return {
...manualConfigurationConfig.getAll(),
testFiles: [require.resolve('./tests/enrollment_token')],
junit: {
reportName: 'Interactive Setup Functional Tests (Enrollment token)',
},
esTestCluster: {
...manualConfigurationConfig.get('esTestCluster'),
serverArgs: [
...manualConfigurationConfig.get('esTestCluster.serverArgs'),
'xpack.security.enrollment.enabled=true',
`xpack.security.http.ssl.keystore.path=${caPath}`,
'xpack.security.http.ssl.keystore.password=storepass',
],
},
kbnTestServer: {
...manualConfigurationConfig.get('kbnTestServer'),
serverArgs: [
...manualConfigurationConfig
.get('kbnTestServer.serverArgs')
.filter((arg: string) => !arg.startsWith('--config')),
`--config=${tempKibanaYamlFile}`,
],
},
};
}

View file

@ -0,0 +1,55 @@
/*
* 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 fs from 'fs/promises';
import { join } from 'path';
import type { FtrConfigProviderContext } from '@kbn/test';
import { getDataPath } from '@kbn/utils';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const withoutTlsConfig = await readConfigFile(
require.resolve('./manual_configuration_without_tls.config.ts')
);
const tempKibanaYamlFile = join(getDataPath(), `interactive_setup_kibana_${Date.now()}.yml`);
await fs.writeFile(tempKibanaYamlFile, '');
return {
...withoutTlsConfig.getAll(),
testFiles: [require.resolve('./tests/manual_configuration')],
servers: {
...withoutTlsConfig.get('servers'),
elasticsearch: {
...withoutTlsConfig.get('servers.elasticsearch'),
protocol: 'https',
},
},
junit: {
reportName: 'Interactive Setup Functional Tests (Manual configuration)',
},
esTestCluster: {
...withoutTlsConfig.get('esTestCluster'),
ssl: true,
},
kbnTestServer: {
...withoutTlsConfig.get('kbnTestServer'),
serverArgs: [
...withoutTlsConfig
.get('kbnTestServer.serverArgs')
.filter((arg: string) => !arg.startsWith('--config')),
`--config=${tempKibanaYamlFile}`,
],
},
};
}

View file

@ -0,0 +1,64 @@
/*
* 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 fs from 'fs/promises';
import { join, resolve } from 'path';
import type { FtrConfigProviderContext } from '@kbn/test';
import { getDataPath } from '@kbn/utils';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(require.resolve('../functional/config'));
const testEndpointsPlugin = resolve(
__dirname,
'../interactive_setup_api_integration/fixtures/test_endpoints'
);
const tempKibanaYamlFile = join(getDataPath(), `interactive_setup_kibana_${Date.now()}.yml`);
await fs.writeFile(tempKibanaYamlFile, '');
return {
...functionalConfig.getAll(),
testFiles: [require.resolve('./tests/manual_configuration_without_security')],
junit: {
reportName: 'Interactive Setup Functional Tests (Manual configuration without Security)',
},
security: { disableTestUser: true },
esTestCluster: {
...functionalConfig.get('esTestCluster'),
serverArgs: [
...functionalConfig
.get('esTestCluster.serverArgs')
.filter((arg: string) => !arg.startsWith('xpack.security.')),
'xpack.security.enabled=false',
],
},
kbnTestServer: {
...functionalConfig.get('kbnTestServer'),
serverArgs: [
...functionalConfig
.get('kbnTestServer.serverArgs')
.filter((arg: string) => !arg.startsWith('--elasticsearch.')),
`--plugin-path=${testEndpointsPlugin}`,
`--config=${tempKibanaYamlFile}`,
],
runOptions: {
...functionalConfig.get('kbnTestServer.runOptions'),
wait: /Kibana has not been configured/,
},
},
uiSettings: {}, // UI settings can't be set during `preboot` stage
};
}

View file

@ -0,0 +1,52 @@
/*
* 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 fs from 'fs/promises';
import { join } from 'path';
import type { FtrConfigProviderContext } from '@kbn/test';
import { getDataPath } from '@kbn/utils';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const withoutSecurityConfig = await readConfigFile(
require.resolve('./manual_configuration_without_security.config')
);
const tempKibanaYamlFile = join(getDataPath(), `interactive_setup_kibana_${Date.now()}.yml`);
await fs.writeFile(tempKibanaYamlFile, '');
return {
...withoutSecurityConfig.getAll(),
testFiles: [require.resolve('./tests/manual_configuration_without_tls')],
junit: {
reportName: 'Interactive Setup Functional Tests (Manual configuration without TLS)',
},
esTestCluster: {
...withoutSecurityConfig.get('esTestCluster'),
serverArgs: [
...withoutSecurityConfig
.get('esTestCluster.serverArgs')
.filter((arg: string) => !arg.startsWith('xpack.security.')),
'xpack.security.enabled=true',
],
},
kbnTestServer: {
...withoutSecurityConfig.get('kbnTestServer'),
serverArgs: [
...withoutSecurityConfig
.get('kbnTestServer.serverArgs')
.filter((arg: string) => !arg.startsWith('--config')),
`--config=${tempKibanaYamlFile}`,
],
},
};
}

View file

@ -0,0 +1,88 @@
/*
* 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 { kibanaPackageJson } from '@kbn/utils';
import type { FtrProviderContext } from '../../functional/ftr_provider_context';
import { getElasticsearchCaCertificate } from '../../interactive_setup_api_integration/fixtures/tls_tools';
export default function ({ getService }: FtrProviderContext) {
const browser = getService('browser');
const find = getService('find');
const supertest = getService('supertest');
const deployment = getService('deployment');
const es = getService('es');
const config = getService('config');
const retry = getService('retry');
const log = getService('log');
describe('Interactive Setup Functional Tests (Enrollment token)', function () {
this.tags(['skipCloud', 'ciGroup2']);
const elasticsearchConfig = config.get('servers.elasticsearch');
let verificationCode: string;
let caFingerprint: string;
before(async function () {
verificationCode = (await supertest.get('/test_endpoints/verification_code').expect(200)).body
.verificationCode;
log.info(`Verification code: ${verificationCode}`);
caFingerprint = (
await getElasticsearchCaCertificate(elasticsearchConfig.hostname, elasticsearchConfig.port)
).fingerprint256
.replace(/:/g, '')
.toLowerCase();
log.info(`Elasticsearch ca fingerprint: ${caFingerprint}`);
});
let enrollmentAPIKey: string;
beforeEach(async function () {
const apiResponse = await es.security.createApiKey({ body: { name: 'enrollment_api_key' } });
enrollmentAPIKey = `${apiResponse.id}:${apiResponse.api_key}`;
log.info(`API key for enrollment token: ${enrollmentAPIKey}`);
});
afterEach(async function () {
await es.security.invalidateApiKey({ body: { name: 'enrollment_api_key' } });
});
it('should configure Kibana successfully', async function () {
this.timeout(150_000);
const enrollmentToken = btoa(
JSON.stringify({
ver: kibanaPackageJson.version,
adr: [`${elasticsearchConfig.hostname}:${elasticsearchConfig.port}`],
fgr: caFingerprint,
key: enrollmentAPIKey,
})
);
await browser.get(`${deployment.getHostPort()}?code=${verificationCode}`);
const initialUrl = await browser.getCurrentUrl();
log.info(`Opened interactive setup: ${initialUrl}`);
const tokenField = await find.byName('token');
await tokenField.clearValueWithKeyboard();
await tokenField.type(enrollmentToken);
log.info(`Entered enrollment token: ${enrollmentToken}`);
await find.clickByButtonText('Configure Elastic');
log.info('Submitted form');
await retry.waitForWithTimeout('redirect to login page', 120_000, async () => {
log.debug(`Current URL: ${await browser.getCurrentUrl()}, initial URL: ${initialUrl}`);
return (await browser.getCurrentUrl()) !== initialUrl;
});
});
});
}
function btoa(str: string) {
return Buffer.from(str, 'binary').toString('base64');
}

View file

@ -0,0 +1,67 @@
/*
* 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 { getUrl, kibanaServerTestUser } from '@kbn/test';
import type { FtrProviderContext } from '../../functional/ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
const browser = getService('browser');
const find = getService('find');
const supertest = getService('supertest');
const deployment = getService('deployment');
const config = getService('config');
const retry = getService('retry');
const log = getService('log');
describe('Interactive Setup Functional Tests (Manual configuration)', function () {
this.tags(['skipCloud', 'ciGroup2']);
let verificationCode: string;
before(async function () {
verificationCode = (await supertest.get('/test_endpoints/verification_code').expect(200)).body
.verificationCode;
});
it('should configure Kibana successfully', async function () {
this.timeout(150_000);
await browser.get(`${deployment.getHostPort()}?code=${verificationCode}`);
const url = await browser.getCurrentUrl();
await find.clickByButtonText('Configure manually');
const elasticsearchHost = getUrl.baseUrl(config.get('servers.elasticsearch'));
const hostField = await find.byName('host');
await hostField.clearValueWithKeyboard();
await hostField.type(elasticsearchHost);
await find.clickByButtonText('Check address');
const usernameField = await find.byName('username');
await usernameField.clearValueWithKeyboard();
await usernameField.type(kibanaServerTestUser.username);
const passwordField = await find.byName('password');
await passwordField.clearValueWithKeyboard();
await passwordField.type(kibanaServerTestUser.password);
const caCertField = await find.byCssSelector('input[type="checkbox"]');
if (!(await caCertField.isSelected())) {
const id = await caCertField.getAttribute('id');
await find.clickByCssSelector(`label[for="${id}"]`);
}
await find.clickByButtonText('Configure Elastic');
await retry.waitForWithTimeout('redirect to login page', 120_000, async () => {
log.debug(`Current URL: ${await browser.getCurrentUrl()}, initial URL: ${url}`);
return (await browser.getCurrentUrl()) !== url;
});
});
});
}

View file

@ -0,0 +1,53 @@
/*
* 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 { getUrl } from '@kbn/test';
import type { FtrProviderContext } from '../../functional/ftr_provider_context';
export default function ({ getService, getPageObject }: FtrProviderContext) {
const browser = getService('browser');
const find = getService('find');
const supertest = getService('supertest');
const deployment = getService('deployment');
const config = getService('config');
const retry = getService('retry');
const log = getService('log');
describe('Interactive Setup Functional Tests (Manual configuration without Security)', function () {
this.tags(['skipCloud', 'ciGroup2']);
let verificationCode: string;
before(async function () {
verificationCode = (await supertest.get('/test_endpoints/verification_code').expect(200)).body
.verificationCode;
});
it('should configure Kibana successfully', async function () {
this.timeout(150_000);
await browser.get(`${deployment.getHostPort()}?code=${verificationCode}`);
const url = await browser.getCurrentUrl();
await find.clickByButtonText('Configure manually');
const elasticsearchHost = getUrl.baseUrl(config.get('servers.elasticsearch'));
const hostField = await find.byName('host');
await hostField.clearValueWithKeyboard();
await hostField.type(elasticsearchHost);
await find.clickByButtonText('Check address');
await find.clickByButtonText('Configure Elastic');
await retry.waitForWithTimeout('redirect to home page', 120_000, async () => {
log.debug(`Current URL: ${await browser.getCurrentUrl()}, initial URL: ${url}`);
return (await browser.getCurrentUrl()) !== url;
});
});
});
}

View file

@ -0,0 +1,61 @@
/*
* 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 { getUrl, kibanaServerTestUser } from '@kbn/test';
import type { FtrProviderContext } from '../../functional/ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
const browser = getService('browser');
const find = getService('find');
const supertest = getService('supertest');
const deployment = getService('deployment');
const config = getService('config');
const retry = getService('retry');
const log = getService('log');
describe('Interactive Setup Functional Tests (Manual configuration without TLS)', function () {
this.tags(['skipCloud', 'ciGroup2']);
let verificationCode: string;
before(async function () {
verificationCode = (await supertest.get('/test_endpoints/verification_code').expect(200)).body
.verificationCode;
});
it('should configure Kibana successfully', async function () {
this.timeout(150_000);
await browser.get(`${deployment.getHostPort()}?code=${verificationCode}`);
const url = await browser.getCurrentUrl();
await find.clickByButtonText('Configure manually');
const elasticsearchHost = getUrl.baseUrl(config.get('servers.elasticsearch'));
const hostField = await find.byName('host');
await hostField.clearValueWithKeyboard();
await hostField.type(elasticsearchHost);
await find.clickByButtonText('Check address');
const usernameField = await find.byName('username');
await usernameField.clearValueWithKeyboard();
await usernameField.type(kibanaServerTestUser.username);
const passwordField = await find.byName('password');
await passwordField.clearValueWithKeyboard();
await passwordField.type(kibanaServerTestUser.password);
await find.clickByButtonText('Configure Elastic');
await retry.waitForWithTimeout('redirect to login page', 120_000, async () => {
log.debug(`Current URL: ${await browser.getCurrentUrl()}, initial URL: ${url}`);
return (await browser.getCurrentUrl()) !== url;
});
});
});
}