Expose decoded cloudId components from the cloud plugin's contract (#159442)

## Summary

Fix https://github.com/elastic/kibana/issues/138813

- decode the cloudId and expose the id components from the `cloud`
plugin's browser and server-side contracts
- remove the existing `decodeCloudId` helpers from the other plugins
- adapt the usages accordingly

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Pierre Gayvallet 2023-06-13 04:58:31 -04:00 committed by GitHub
parent 486c24bdd3
commit 2b42341d04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 358 additions and 529 deletions

View file

@ -5,9 +5,16 @@
* 2.0.
*/
import { loggerMock, MockedLogger } from '@kbn/logging-mocks';
import { decodeCloudId } from './decode_cloud_id';
describe('Fleet - decodeCloudId', () => {
let logger: MockedLogger;
beforeEach(() => {
logger = loggerMock.create();
});
it('parses various CloudID formats', () => {
const tests = [
{
@ -73,7 +80,7 @@ describe('Fleet - decodeCloudId', () => {
];
for (const test of tests) {
const decoded = decodeCloudId(test.cloudID);
const decoded = decodeCloudId(test.cloudID, logger);
expect(decoded).toBeTruthy();
expect(decoded?.elasticsearchUrl === test.expectedEsURL).toBe(true);
expect(decoded?.kibanaUrl === test.expectedKibanaURL).toBe(true);
@ -94,7 +101,7 @@ describe('Fleet - decodeCloudId', () => {
];
for (const test of tests) {
const decoded = decodeCloudId(test.cloudID);
const decoded = decodeCloudId(test.cloudID, logger);
expect(decoded).toBe(undefined);
// decodeCloudId currently only logs; not throws errors
}

View file

@ -5,21 +5,21 @@
* 2.0.
*/
import type { Logger } from '@kbn/logging';
export interface DecodedCloudId {
host: string;
defaultPort: string;
elasticsearchUrl: string;
kibanaUrl: string;
}
// decodeCloudId decodes the c.id into c.esURL and c.kibURL
export function decodeCloudId(cid: string):
| {
host: string;
defaultPort: string;
elasticsearchUrl: string;
kibanaUrl: string;
}
| undefined {
export function decodeCloudId(cid: string, logger: Logger): DecodedCloudId | undefined {
// 1. Ignore anything before `:`.
const id = cid.split(':').pop();
if (!id) {
// throw new Error(`Unable to decode ${id}`);
// eslint-disable-next-line no-console
console.debug(`Unable to decode ${id}`);
logger.debug(`Unable to decode ${id}`);
return;
}
@ -28,18 +28,14 @@ export function decodeCloudId(cid: string):
try {
decoded = Buffer.from(id, 'base64').toString('utf8');
} catch {
// throw new Error(`base64 decoding failed on ${id}`);
// eslint-disable-next-line no-console
console.debug(`base64 decoding failed on ${id}`);
logger.debug(`base64 decoding failed on ${id}`);
return;
}
// 3. separate based on `$`
const words = decoded.split('$');
if (words.length < 3) {
// throw new Error(`Expected at least 3 parts in ${decoded}`);
// eslint-disable-next-line no-console
console.debug(`Expected at least 3 parts in ${decoded}`);
logger.debug(`Expected at least 3 parts in ${decoded}`);
return;
}
// 4. extract port from the ES and Kibana host
@ -56,6 +52,7 @@ export function decodeCloudId(cid: string):
kibanaUrl: kbUrl,
};
}
// extractPortFromName takes a string in the form `id:port` and returns the
// Id and the port. If there's no `:`, the default port is returned
function extractPortFromName(word: string, defaultPort = '443') {

View file

@ -8,7 +8,8 @@
import { PluginInitializerContext } from '@kbn/core/public';
import { CloudPlugin } from './plugin';
export type { CloudSetup, CloudConfigType, CloudStart } from './plugin';
export type { CloudSetup, CloudStart } from './types';
export type { CloudConfigType } from './plugin';
export function plugin(initializerContext: PluginInitializerContext) {
return new CloudPlugin(initializerContext);

View file

@ -7,17 +7,22 @@
import React from 'react';
import { CloudStart } from '.';
import type { CloudSetup, CloudStart } from './types';
function createSetupMock() {
function createSetupMock(): jest.Mocked<CloudSetup> {
return {
cloudId: 'mock-cloud-id',
deploymentId: 'mock-deployment-id',
isCloudEnabled: true,
cname: 'cname',
baseUrl: 'base-url',
deploymentUrl: 'deployment-url',
profileUrl: 'profile-url',
organizationUrl: 'organization-url',
elasticsearchUrl: 'elasticsearch-url',
kibanaUrl: 'kibana-url',
cloudHost: 'cloud-host',
cloudDefaultPort: '443',
isElasticStaffOwned: true,
trialEndDate: new Date('2020-10-01T14:13:12Z'),
registerCloudService: jest.fn(),

View file

@ -0,0 +1,22 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const parseDeploymentIdFromDeploymentUrlMock = jest.fn();
jest.doMock('../common/parse_deployment_id_from_deployment_url', () => {
return {
parseDeploymentIdFromDeploymentUrl: parseDeploymentIdFromDeploymentUrlMock,
};
});
export const decodeCloudIdMock = jest.fn();
jest.doMock('../common/decode_cloud_id', () => {
return {
decodeCloudId: decodeCloudIdMock,
};
});

View file

@ -5,8 +5,10 @@
* 2.0.
*/
import { decodeCloudIdMock, parseDeploymentIdFromDeploymentUrlMock } from './plugin.test.mocks';
import { coreMock } from '@kbn/core/public/mocks';
import { CloudPlugin } from './plugin';
import type { DecodedCloudId } from '../common/decode_cloud_id';
const baseConfig = {
base_url: 'https://cloud.elastic.co',
@ -16,6 +18,11 @@ const baseConfig = {
};
describe('Cloud Plugin', () => {
beforeEach(() => {
parseDeploymentIdFromDeploymentUrlMock.mockReset().mockReturnValue('deployment-id');
decodeCloudIdMock.mockReset().mockReturnValue({});
});
describe('#setup', () => {
describe('interface', () => {
const setupPlugin = () => {
@ -76,6 +83,37 @@ describe('Cloud Plugin', () => {
const { setup } = setupPlugin();
expect(setup.registerCloudService).toBeDefined();
});
it('exposes deploymentId', () => {
parseDeploymentIdFromDeploymentUrlMock.mockReturnValue('some-deployment-id');
const { setup } = setupPlugin();
expect(setup.deploymentId).toBe('some-deployment-id');
expect(parseDeploymentIdFromDeploymentUrlMock).toHaveBeenCalledTimes(2); // called when registering the analytic context too
expect(parseDeploymentIdFromDeploymentUrlMock).toHaveBeenCalledWith(
baseConfig.deployment_url
);
});
it('exposes components decoded from the cloudId', () => {
const decodedId: DecodedCloudId = {
defaultPort: '9000',
host: 'host',
elasticsearchUrl: 'elasticsearch-url',
kibanaUrl: 'kibana-url',
};
decodeCloudIdMock.mockReturnValue(decodedId);
const { setup } = setupPlugin();
expect(setup).toEqual(
expect.objectContaining({
cloudDefaultPort: '9000',
cloudHost: 'host',
elasticsearchUrl: 'elasticsearch-url',
kibanaUrl: 'kibana-url',
})
);
expect(decodeCloudIdMock).toHaveBeenCalledTimes(1);
expect(decodeCloudIdMock).toHaveBeenCalledWith('cloudId', expect.any(Object));
});
});
});

View file

@ -6,11 +6,14 @@
*/
import React, { FC } from 'react';
import type { Logger } from '@kbn/logging';
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';
import { registerCloudDeploymentMetadataAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context';
import { getIsCloudEnabled } from '../common/is_cloud_enabled';
import { parseDeploymentIdFromDeploymentUrl } from '../common/parse_deployment_id_from_deployment_url';
import { ELASTIC_SUPPORT_LINK, CLOUD_SNAPSHOTS_PATH } from '../common/constants';
import { decodeCloudId, type DecodedCloudId } from '../common/decode_cloud_id';
import type { CloudSetup, CloudStart } from './types';
import { getFullCloudUrl } from './utils';
export interface CloudConfigType {
@ -24,81 +27,6 @@ export interface CloudConfigType {
is_elastic_staff_owned?: boolean;
}
export interface CloudStart {
/**
* A React component that provides a pre-wired `React.Context` which connects components to Cloud services.
*/
CloudContextProvider: FC<{}>;
/**
* `true` when Kibana is running on Elastic Cloud.
*/
isCloudEnabled: boolean;
/**
* Cloud ID. Undefined if not running on Cloud.
*/
cloudId?: string;
/**
* The full URL to the deployment management page on Elastic Cloud. Undefined if not running on Cloud.
*/
deploymentUrl?: string;
/**
* The full URL to the user profile page on Elastic Cloud. Undefined if not running on Cloud.
*/
profileUrl?: string;
/**
* The full URL to the organization management page on Elastic Cloud. Undefined if not running on Cloud.
*/
organizationUrl?: string;
}
export interface CloudSetup {
/**
* Cloud ID. Undefined if not running on Cloud.
*/
cloudId?: string;
/**
* This value is the same as `baseUrl` on ESS but can be customized on ECE.
*/
cname?: string;
/**
* This is the URL of the Cloud interface.
*/
baseUrl?: string;
/**
* The full URL to the deployment management page on Elastic Cloud. Undefined if not running on Cloud.
*/
deploymentUrl?: string;
/**
* The full URL to the user profile page on Elastic Cloud. Undefined if not running on Cloud.
*/
profileUrl?: string;
/**
* The full URL to the organization management page on Elastic Cloud. Undefined if not running on Cloud.
*/
organizationUrl?: string;
/**
* This is the path to the Snapshots page for the deployment to which the Kibana instance belongs. The value is already prepended with `deploymentUrl`.
*/
snapshotsUrl?: string;
/**
* `true` when Kibana is running on Elastic Cloud.
*/
isCloudEnabled: boolean;
/**
* When the Cloud Trial ends/ended for the organization that owns this deployment. Only available when running on Elastic Cloud.
*/
trialEndDate?: Date;
/**
* `true` if the Elastic Cloud organization that owns this deployment is owned by an Elastician. Only available when running on Elastic Cloud.
*/
isElasticStaffOwned?: boolean;
/**
* Registers CloudServiceProviders so start's `CloudContextProvider` hooks them.
* @param contextProvider The React component from the Service Provider.
*/
registerCloudService: (contextProvider: FC) => void;
}
interface CloudUrls {
deploymentUrl?: string;
profileUrl?: string;
@ -110,10 +38,12 @@ export class CloudPlugin implements Plugin<CloudSetup> {
private readonly config: CloudConfigType;
private readonly isCloudEnabled: boolean;
private readonly contextProviders: FC[] = [];
private readonly logger: Logger;
constructor(private readonly initializerContext: PluginInitializerContext) {
this.config = this.initializerContext.config.get<CloudConfigType>();
this.isCloudEnabled = getIsCloudEnabled(this.config.id);
this.logger = initializerContext.logger.get();
}
public setup(core: CoreSetup): CloudSetup {
@ -127,11 +57,21 @@ export class CloudPlugin implements Plugin<CloudSetup> {
is_elastic_staff_owned: isElasticStaffOwned,
} = this.config;
let decodedId: DecodedCloudId | undefined;
if (id) {
decodedId = decodeCloudId(id, this.logger);
}
return {
cloudId: id,
deploymentId: parseDeploymentIdFromDeploymentUrl(this.config.deployment_url),
cname,
baseUrl,
...this.getCloudUrls(),
elasticsearchUrl: decodedId?.elasticsearchUrl,
kibanaUrl: decodedId?.kibanaUrl,
cloudHost: decodedId?.host,
cloudDefaultPort: decodedId?.defaultPort,
trialEndDate: trialEndDate ? new Date(trialEndDate) : undefined,
isElasticStaffOwned,
isCloudEnabled: this.isCloudEnabled,

View file

@ -0,0 +1,111 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { FC } from 'react';
export interface CloudStart {
/**
* A React component that provides a pre-wired `React.Context` which connects components to Cloud services.
*/
CloudContextProvider: FC<{}>;
/**
* `true` when Kibana is running on Elastic Cloud.
*/
isCloudEnabled: boolean;
/**
* Cloud ID. Undefined if not running on Cloud.
*/
cloudId?: string;
/**
* The full URL to the deployment management page on Elastic Cloud. Undefined if not running on Cloud.
*/
deploymentUrl?: string;
/**
* The full URL to the user profile page on Elastic Cloud. Undefined if not running on Cloud.
*/
profileUrl?: string;
/**
* The full URL to the organization management page on Elastic Cloud. Undefined if not running on Cloud.
*/
organizationUrl?: string;
/**
* The full URL to the elasticsearch cluster.
*/
elasticsearchUrl?: string;
/**
* The full URL to the Kibana deployment.
*/
kibanaUrl?: string;
}
export interface CloudSetup {
/**
* Cloud ID. Undefined if not running on Cloud.
*/
cloudId?: string;
/**
* The deployment's ID. Only available when running on Elastic Cloud.
*/
deploymentId?: string;
/**
* This value is the same as `baseUrl` on ESS but can be customized on ECE.
*/
cname?: string;
/**
* This is the URL of the Cloud interface.
*/
baseUrl?: string;
/**
* The full URL to the deployment management page on Elastic Cloud. Undefined if not running on Cloud.
*/
deploymentUrl?: string;
/**
* The full URL to the user profile page on Elastic Cloud. Undefined if not running on Cloud.
*/
profileUrl?: string;
/**
* The full URL to the organization management page on Elastic Cloud. Undefined if not running on Cloud.
*/
organizationUrl?: string;
/**
* This is the path to the Snapshots page for the deployment to which the Kibana instance belongs. The value is already prepended with `deploymentUrl`.
*/
snapshotsUrl?: string;
/**
* The full URL to the elasticsearch cluster.
*/
elasticsearchUrl?: string;
/**
* The full URL to the Kibana deployment.
*/
kibanaUrl?: string;
/**
* {host} from the deployment url https://<deploymentId>.<application>.<host><?:port>
*/
cloudHost?: string;
/**
* {port} from the deployment url https://<deploymentId>.<application>.<host><?:port>
*/
cloudDefaultPort?: string;
/**
* `true` when Kibana is running on Elastic Cloud.
*/
isCloudEnabled: boolean;
/**
* When the Cloud Trial ends/ended for the organization that owns this deployment. Only available when running on Elastic Cloud.
*/
trialEndDate?: Date;
/**
* `true` if the Elastic Cloud organization that owns this deployment is owned by an Elastician. Only available when running on Elastic Cloud.
*/
isElasticStaffOwned?: boolean;
/**
* Registers CloudServiceProviders so start's `CloudContextProvider` hooks them.
* @param contextProvider The React component from the Service Provider.
*/
registerCloudService: (contextProvider: FC) => void;
}

View file

@ -5,13 +5,17 @@
* 2.0.
*/
import { CloudSetup } from '.';
import type { CloudSetup } from './plugin';
function createSetupMock(): jest.Mocked<CloudSetup> {
return {
cloudId: 'mock-cloud-id',
instanceSizeMb: 1234,
deploymentId: 'deployment-id',
elasticsearchUrl: 'elasticsearch-url',
kibanaUrl: 'kibana-url',
cloudHost: 'cloud-host',
cloudDefaultPort: '443',
instanceSizeMb: 1234,
isCloudEnabled: true,
isElasticStaffOwned: true,
trialEndDate: new Date('2020-10-01T14:13:12Z'),

View file

@ -0,0 +1,22 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const parseDeploymentIdFromDeploymentUrlMock = jest.fn();
jest.doMock('../common/parse_deployment_id_from_deployment_url', () => {
return {
parseDeploymentIdFromDeploymentUrl: parseDeploymentIdFromDeploymentUrlMock,
};
});
export const decodeCloudIdMock = jest.fn();
jest.doMock('../common/decode_cloud_id', () => {
return {
decodeCloudId: decodeCloudIdMock,
};
});

View file

@ -5,8 +5,10 @@
* 2.0.
*/
import { decodeCloudIdMock, parseDeploymentIdFromDeploymentUrlMock } from './plugin.test.mocks';
import { coreMock } from '@kbn/core/server/mocks';
import { CloudPlugin } from './plugin';
import type { DecodedCloudId } from '../common/decode_cloud_id';
const baseConfig = {
base_url: 'https://cloud.elastic.co',
@ -16,6 +18,11 @@ const baseConfig = {
};
describe('Cloud Plugin', () => {
beforeEach(() => {
parseDeploymentIdFromDeploymentUrlMock.mockReset().mockReturnValue('deployment-id');
decodeCloudIdMock.mockReset().mockReturnValue({});
});
const setupPlugin = () => {
const initContext = coreMock.createPluginInitializerContext({
...baseConfig,
@ -48,15 +55,41 @@ describe('Cloud Plugin', () => {
expect(setup.instanceSizeMb).toBeUndefined();
});
it('exposes deploymentId', () => {
const { setup } = setupPlugin();
expect(setup.deploymentId).toBe('abc123');
});
it('exposes apm', () => {
const { setup } = setupPlugin();
expect(setup.apm).toStrictEqual({ url: undefined, secretToken: undefined });
});
it('exposes deploymentId', () => {
parseDeploymentIdFromDeploymentUrlMock.mockReturnValue('some-deployment-id');
const { setup } = setupPlugin();
expect(setup.deploymentId).toBe('some-deployment-id');
expect(parseDeploymentIdFromDeploymentUrlMock).toHaveBeenCalledTimes(2); // called when registering the analytic context too
expect(parseDeploymentIdFromDeploymentUrlMock).toHaveBeenCalledWith(
baseConfig.deployment_url
);
});
it('exposes components decoded from the cloudId', () => {
const decodedId: DecodedCloudId = {
defaultPort: '9000',
host: 'host',
elasticsearchUrl: 'elasticsearch-url',
kibanaUrl: 'kibana-url',
};
decodeCloudIdMock.mockReturnValue(decodedId);
const { setup } = setupPlugin();
expect(setup).toEqual(
expect.objectContaining({
cloudDefaultPort: '9000',
cloudHost: 'host',
elasticsearchUrl: 'elasticsearch-url',
kibanaUrl: 'kibana-url',
})
);
expect(decodeCloudIdMock).toHaveBeenCalledTimes(1);
expect(decodeCloudIdMock).toHaveBeenCalledWith('cloudId', expect.any(Object));
});
});
});

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import type { Logger } from '@kbn/logging';
import type { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/server';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { registerCloudDeploymentMetadataAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context';
@ -12,6 +13,7 @@ import type { CloudConfigType } from './config';
import { registerCloudUsageCollector } from './collectors';
import { getIsCloudEnabled } from '../common/is_cloud_enabled';
import { parseDeploymentIdFromDeploymentUrl } from '../common/parse_deployment_id_from_deployment_url';
import { decodeCloudId, DecodedCloudId } from '../common/decode_cloud_id';
import { readInstanceSizeMb } from './env';
interface PluginsSetup {
@ -30,6 +32,22 @@ export interface CloudSetup {
* The deployment's ID. Only available when running on Elastic Cloud.
*/
deploymentId?: string;
/**
* The full URL to the elasticsearch cluster.
*/
elasticsearchUrl?: string;
/**
* The full URL to the Kibana deployment.
*/
kibanaUrl?: string;
/**
* {host} from the deployment url https://<deploymentId>.<application>.<host><?:port>
*/
cloudHost?: string;
/**
* {port} from the deployment url https://<deploymentId>.<application>.<host><?:port>
*/
cloudDefaultPort?: string;
/**
* `true` when running on Elastic Cloud.
*/
@ -67,9 +85,11 @@ export interface CloudStart {
export class CloudPlugin implements Plugin<CloudSetup, CloudStart> {
private readonly config: CloudConfigType;
private readonly logger: Logger;
constructor(private readonly context: PluginInitializerContext) {
this.config = this.context.config.get<CloudConfigType>();
this.logger = this.context.logger.get();
}
public setup(core: CoreSetup, { usageCollection }: PluginsSetup): CloudSetup {
@ -81,10 +101,19 @@ export class CloudPlugin implements Plugin<CloudSetup, CloudStart> {
isElasticStaffOwned: this.config.is_elastic_staff_owned,
});
let decodedId: DecodedCloudId | undefined;
if (this.config.id) {
decodedId = decodeCloudId(this.config.id, this.logger);
}
return {
cloudId: this.config.id,
instanceSizeMb: readInstanceSizeMb(),
deploymentId: parseDeploymentIdFromDeploymentUrl(this.config.deployment_url),
elasticsearchUrl: decodedId?.elasticsearchUrl,
kibanaUrl: decodedId?.kibanaUrl,
cloudHost: decodedId?.host,
cloudDefaultPort: decodedId?.defaultPort,
isCloudEnabled,
trialEndDate: this.config.trial_end_date ? new Date(this.config.trial_end_date) : undefined,
isElasticStaffOwned: this.config.is_elastic_staff_owned,

View file

@ -15,6 +15,8 @@
"@kbn/usage-collection-plugin",
"@kbn/analytics-client",
"@kbn/config-schema",
"@kbn/logging-mocks",
"@kbn/logging",
],
"exclude": [
"target/**/*",

View file

@ -55,7 +55,7 @@ describe('AnalyticsCollectionIntegrate', () => {
.toMatchInlineSnapshot(`
"<script type=\\"text/javascript\\">
window.elasticAnalytics.createTracker({
endpoint: \\"https://localhost:9200\\",
endpoint: \\"elasticsearch-url\\",
collectionName: \\"example\\",
apiKey: \\"########\\",
// Optional: sampling rate percentage: 0-1, 0 = no events, 1 = all events

View file

@ -28,7 +28,6 @@ import { i18n } from '@kbn/i18n';
import { AnalyticsCollection } from '../../../../../../common/types/analytics';
import { useCloudDetails } from '../../../../shared/cloud_details/cloud_details';
import { decodeCloudId } from '../../../../shared/decode_cloud_id/decode_cloud_id';
import { docLinks } from '../../../../shared/doc_links';
import { KibanaLogic } from '../../../../shared/kibana';
@ -196,8 +195,7 @@ export const AnalyticsCollectionIntegrateView: React.FC<AnalyticsCollectionInteg
const DEFAULT_URL = 'https://localhost:9200';
const cloudContext = useCloudDetails();
const baseUrl =
(cloudContext.cloudId && decodeCloudId(cloudContext.cloudId)?.elasticsearchUrl) || DEFAULT_URL;
const baseUrl = cloudContext.elasticsearchUrl || DEFAULT_URL;
const analyticsConfig: AnalyticsConfig = {
apiKey: apiKey || '########',

View file

@ -24,7 +24,6 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { CloudDetails, useCloudDetails } from '../../../../shared/cloud_details/cloud_details';
import { decodeCloudId } from '../../../../shared/decode_cloud_id/decode_cloud_id';
import { docLinks } from '../../../../shared/doc_links';
import { KibanaLogic } from '../../../../shared/kibana';
@ -36,8 +35,7 @@ import { GenerateEngineApiKeyModal } from './generate_engine_api_key_modal/gener
export const elasticsearchUrl = (cloudContext: CloudDetails): string => {
const defaultUrl = 'https://localhost:9200';
const url =
(cloudContext.cloudId && decodeCloudId(cloudContext.cloudId)?.elasticsearchUrl) || defaultUrl;
const url = cloudContext.elasticsearchUrl || defaultUrl;
return url;
};

View file

@ -12,8 +12,6 @@ import { EuiCodeBlock } from '@elastic/eui';
import { IngestPipelineParams } from '../../../../../../../common/types/connectors';
import { useCloudDetails } from '../../../../../shared/cloud_details/cloud_details';
import { decodeCloudId } from '../../../../../shared/decode_cloud_id/decode_cloud_id';
interface CurlRequestParams {
apiKey?: string;
document?: Record<string, unknown>;
@ -30,8 +28,7 @@ export const CurlRequest: React.FC<CurlRequestParams> = ({
const cloudContext = useCloudDetails();
const DEFAULT_URL = 'https://localhost:9200';
const baseUrl =
(cloudContext.cloudId && decodeCloudId(cloudContext.cloudId)?.elasticsearchUrl) || DEFAULT_URL;
const baseUrl = cloudContext.elasticsearchUrl || DEFAULT_URL;
const apiKeyExample = apiKey || '<Replace_with_created_API_key>';
const { name: pipelineName, ...pipelineParams } = pipeline ?? {};
// We have to prefix the parameters with an underscore because that's what the actual pipeline looks for

View file

@ -12,6 +12,8 @@ import { KibanaLogic } from '../kibana';
export interface CloudDetails {
cloudId: string | undefined;
deploymentUrl: string | undefined;
elasticsearchUrl: string | undefined;
kibanaUrl: string | undefined;
}
export const useCloudDetails = (): CloudDetails => {
@ -19,5 +21,7 @@ export const useCloudDetails = (): CloudDetails => {
return {
cloudId: cloud?.cloudId,
deploymentUrl: cloud?.deploymentUrl,
elasticsearchUrl: cloud?.elasticsearchUrl,
kibanaUrl: cloud?.kibanaUrl,
};
};

View file

@ -1,104 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { decodeCloudId } from './decode_cloud_id';
// Copied from Fleet's solution
// x-pack/fleet/common/services/decode_cloud_id.test.ts
describe('Enterprise Search - decodeCloudId', () => {
it('parses various CloudID formats', () => {
const tests = [
{
cloudID:
'staging:dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
expectedEsURL: 'https://cec6f261a74bf24ce33bb8811b84294f.us-east-1.aws.found.io:443',
expectedKibanaURL: 'https://c6c2ca6d042249af0cc7d7a9e9625743.us-east-1.aws.found.io:443',
},
{
cloudID:
'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
expectedEsURL: 'https://cec6f261a74bf24ce33bb8811b84294f.us-east-1.aws.found.io:443',
expectedKibanaURL: 'https://c6c2ca6d042249af0cc7d7a9e9625743.us-east-1.aws.found.io:443',
},
{
cloudID:
':dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
expectedEsURL: 'https://cec6f261a74bf24ce33bb8811b84294f.us-east-1.aws.found.io:443',
expectedKibanaURL: 'https://c6c2ca6d042249af0cc7d7a9e9625743.us-east-1.aws.found.io:443',
},
{
cloudID:
'gcp-cluster:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJDhhMDI4M2FmMDQxZjE5NWY3NzI5YmMwNGM2NmEwZmNlJDBjZDVjZDU2OGVlYmU1M2M4OWViN2NhZTViYWM4YjM3',
expectedEsURL: 'https://8a0283af041f195f7729bc04c66a0fce.us-central1.gcp.cloud.es.io:443',
expectedKibanaURL:
'https://0cd5cd568eebe53c89eb7cae5bac8b37.us-central1.gcp.cloud.es.io:443',
},
{
cloudID:
'custom-port:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA=',
expectedEsURL: 'https://ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243',
expectedKibanaURL:
'https://a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9243',
},
{
cloudID:
'different-es-kb-port:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2OjkyNDMkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA6OTI0NA==',
expectedEsURL: 'https://ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243',
expectedKibanaURL:
'https://a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244',
},
{
cloudID:
'only-kb-set:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2JGE0YzA2MjMwZTQ4YzhmY2U3YmU4OGEwNzRhM2JiM2UwOjkyNDQ=',
expectedEsURL: 'https://ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:443',
expectedKibanaURL:
'https://a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244',
},
{
cloudID:
'host-and-kb-set:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA6OTI0NA==',
expectedEsURL: 'https://ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243',
expectedKibanaURL:
'https://a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244',
},
{
cloudID:
'extra-items:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2JGE0YzA2MjMwZTQ4YzhmY2U3YmU4OGEwNzRhM2JiM2UwJGFub3RoZXJpZCRhbmRhbm90aGVy',
expectedEsURL: 'https://ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:443',
expectedKibanaURL:
'https://a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:443',
},
];
for (const test of tests) {
const decoded = decodeCloudId(test.cloudID);
expect(decoded).toBeTruthy();
expect(decoded?.elasticsearchUrl === test.expectedEsURL).toBe(true);
expect(decoded?.kibanaUrl === test.expectedKibanaURL).toBe(true);
}
});
it('returns undefined for invalid formats', () => {
const tests = [
{
cloudID:
'staging:garbagedXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
errorMsg: 'base64 decoding failed',
},
{
cloudID: 'dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJDhhMDI4M2FmMDQxZjE5NWY3NzI5YmMwNGM2NmEwZg==',
errorMsg: 'Expected at least 3 parts',
},
];
for (const test of tests) {
const decoded = decodeCloudId(test.cloudID);
expect(decoded).toBe(undefined);
// decodeCloudId currently only logs; not throws errors
}
});
});

View file

@ -1,66 +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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// copied this solution from fleet due to time constraints
// x-pack/fleet/common/services/decode_cloud_id.ts
// decodeCloudId decodes the c.id into c.esURL and c.kibURL
export function decodeCloudId(cid: string):
| {
defaultPort: string;
elasticsearchUrl: string;
host: string;
kibanaUrl: string;
}
| undefined {
// 1. Ignore anything before `:`.
const id = cid.split(':').pop();
if (!id) {
// throw new Error(`Unable to decode ${id}`);
// eslint-disable-next-line no-console
console.debug(`Unable to decode ${id}`);
return;
}
// 2. base64 decode
let decoded: string | undefined;
try {
decoded = Buffer.from(id, 'base64').toString('utf8');
} catch {
// throw new Error(`base64 decoding failed on ${id}`);
// eslint-disable-next-line no-console
console.debug(`base64 decoding failed on ${id}`);
return;
}
// 3. separate based on `$`
const words = decoded.split('$');
if (words.length < 3) {
// throw new Error(`Expected at least 3 parts in ${decoded}`);
// eslint-disable-next-line no-console
console.debug(`Expected at least 3 parts in ${decoded}`);
return;
}
// 4. extract port from the ES and Kibana host
const [host, defaultPort] = extractPortFromName(words[0]);
const [esId, esPort] = extractPortFromName(words[1], defaultPort);
const [kbId, kbPort] = extractPortFromName(words[2], defaultPort);
// 5. form the URLs
const esUrl = `https://${esId}.${host}:${esPort}`;
const kbUrl = `https://${kbId}.${host}:${kbPort}`;
return {
defaultPort,
elasticsearchUrl: esUrl,
host,
kibanaUrl: kbUrl,
};
}
// extractPortFromName takes a string in the form `id:port` and returns the
// Id and the port. If there's no `:`, the default port is returned
function extractPortFromName(word: string, defaultPort = '443') {
const [host, port = defaultPort] = word.split(':');
return [host, port];
}

View file

@ -10,14 +10,20 @@ import { getCloudEnterpriseSearchHost } from './get_cloud_enterprise_search_host
const defaultPortCloud = {
cloudId:
'gcp-cluster:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJDhhMDI4M2FmMDQxZjE5NWY3NzI5YmMwNGM2NmEwZmNlJDBjZDVjZDU2OGVlYmU1M2M4OWViN2NhZTViYWM4YjM3',
deploymentId: 'gcp-cluster',
isCloudEnabled: true,
cloudHost: 'us-central1.gcp.cloud.es.io',
cloudDefaultPort: '443',
registerCloudService: jest.fn(),
};
// 9243
const customPortCloud = {
cloudId:
'custom-port:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA=',
deploymentId: 'custom-port',
isCloudEnabled: true,
cloudHost: 'us-central1.gcp.cloud.es.io',
cloudDefaultPort: '9243',
registerCloudService: jest.fn(),
};
const missingDeploymentIdCloud = {

View file

@ -5,29 +5,13 @@
* 2.0.
*/
import { CloudSetup } from '@kbn/cloud-plugin/public';
import { decodeCloudId } from '../decode_cloud_id/decode_cloud_id';
import type { CloudSetup } from '@kbn/cloud-plugin/public';
export function getCloudEnterpriseSearchHost(cloud: CloudSetup | undefined): string | undefined {
if (cloud && cloud.isCloudEnabled && cloud.cloudId) {
const deploymentId = getDeploymentId(cloud.cloudId);
const res = decodeCloudId(cloud.cloudId);
if (!(deploymentId && res)) {
return;
}
if (cloud && cloud.isCloudEnabled && cloud.cloudId && cloud.deploymentId && cloud.cloudHost) {
// Enterprise Search Server url are formed like this `https://<deploymentId>.ent.<host>
return `https://${deploymentId}.ent.${res.host}${
res.defaultPort !== '443' ? `:${res.defaultPort}` : ''
return `https://${cloud.deploymentId}.ent.${cloud.cloudHost}${
cloud.cloudDefaultPort && cloud.cloudDefaultPort !== '443' ? `:${cloud.cloudDefaultPort}` : ''
}`;
}
}
function getDeploymentId(cloudId: string): string | undefined {
const [deploymentId, rest] = cloudId.split(':');
if (deploymentId && rest) {
return deploymentId;
}
}

View file

@ -71,8 +71,6 @@ export {
// Package policy helpers
isValidNamespace,
INVALID_NAMESPACE_CHARACTERS,
// TODO Should probably not be exposed by Fleet
decodeCloudId,
getFileMetadataIndexName,
getFileDataIndexName,
} from './services';

View file

@ -15,7 +15,6 @@ export {
} from './package_to_package_policy';
export { fullAgentPolicyToYaml } from './full_agent_policy_to_yaml';
export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from './limited_package';
export { decodeCloudId } from './decode_cloud_id';
export { isValidNamespace, INVALID_NAMESPACE_CHARACTERS } from './is_valid_namespace';
export { isDiffPathProtocol } from './is_diff_path_protocol';
export { LicenseService } from './license';

View file

@ -1050,11 +1050,12 @@ describe('Output Service', () => {
mockedAppContextService.getConfig.mockReset();
mockedAppContextService.getConfig.mockReset();
});
it('Should use cloud ID as the source of truth for ES hosts', () => {
it('Should use cloud plugin as the source of truth for ES hosts', () => {
// @ts-expect-error
mockedAppContextService.getCloud.mockReturnValue({
isCloudEnabled: true,
cloudId: CLOUD_ID,
elasticsearchUrl: 'https://cec6f261a74bf24ce33bb8811b84294f.us-east-1.aws.found.io:443',
});
mockedAppContextService.getConfig.mockReturnValue(CONFIG_WITH_ES_HOSTS);

View file

@ -25,7 +25,7 @@ import {
AGENT_POLICY_SAVED_OBJECT_TYPE,
} from '../constants';
import { SO_SEARCH_LIMIT, outputType } from '../../common/constants';
import { decodeCloudId, normalizeHostsForAgents } from '../../common/services';
import { normalizeHostsForAgents } from '../../common/services';
import {
OutputUnauthorizedError,
OutputInvalidError,
@ -289,8 +289,7 @@ class OutputService {
public getDefaultESHosts(): string[] {
const cloud = appContextService.getCloud();
const cloudId = cloud?.isCloudEnabled && cloud.cloudId;
const cloudUrl = cloudId && decodeCloudId(cloudId)?.elasticsearchUrl;
const cloudUrl = cloud?.elasticsearchUrl;
const cloudHosts = cloudUrl ? [cloudUrl] : undefined;
const flagHosts =
appContextService.getConfig()!.agents?.elasticsearch?.hosts &&

View file

@ -118,6 +118,7 @@ describe('getCloudFleetServersHosts', () => {
'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
isCloudEnabled: true,
deploymentId: 'deployment-id-1',
cloudHost: 'us-east-1.aws.found.io',
apm: {},
});
@ -134,6 +135,8 @@ describe('getCloudFleetServersHosts', () => {
'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl',
isCloudEnabled: true,
deploymentId: 'deployment-id-1',
cloudHost: 'test.fr',
cloudDefaultPort: '9243',
apm: {},
});
@ -185,6 +188,7 @@ describe('createCloudFleetServerHostIfNeeded', () => {
'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
isCloudEnabled: true,
deploymentId: 'deployment-id-1',
cloudHost: 'us-east-1.aws.found.io',
apm: {},
});
mockedGetDefaultFleetServerHost.mockResolvedValue(null);

View file

@ -7,7 +7,7 @@
import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
import { decodeCloudId, normalizeHostsForAgents } from '../../../common/services';
import { normalizeHostsForAgents } from '../../../common/services';
import type { FleetConfigType } from '../../config';
import { DEFAULT_FLEET_SERVER_HOST_ID } from '../../constants';
@ -27,16 +27,13 @@ import { isDifferent } from './utils';
export function getCloudFleetServersHosts() {
const cloudSetup = appContextService.getCloud();
if (cloudSetup && cloudSetup.isCloudEnabled && cloudSetup.cloudId && cloudSetup.deploymentId) {
const res = decodeCloudId(cloudSetup.cloudId);
if (!res) {
return;
}
if (cloudSetup && cloudSetup.isCloudEnabled && cloudSetup.cloudHost) {
// Fleet Server url are formed like this `https://<deploymentId>.fleet.<host>
return [
`https://${cloudSetup.deploymentId}.fleet.${res.host}${
res.defaultPort !== '443' ? `:${res.defaultPort}` : ''
`https://${cloudSetup.deploymentId}.fleet.${cloudSetup.cloudHost}${
cloudSetup.cloudDefaultPort && cloudSetup.cloudDefaultPort !== '443'
? `:${cloudSetup.cloudDefaultPort}`
: ''
}`,
];
}

View file

@ -1,18 +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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { decodeCloudId } from '@kbn/fleet-plugin/common';
export function getCloudUrls(cloudId: string) {
const decodedCloudId = decodeCloudId(cloudId);
if (decodedCloudId) {
return {
elasticsearchUrl: decodedCloudId.elasticsearchUrl,
kibanaUrl: decodedCloudId.kibanaUrl,
};
}
}

View file

@ -11,7 +11,6 @@ import { ObservabilityOnboardingState } from '../../saved_objects/observability_
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { createShipperApiKey } from './api_key/create_shipper_api_key';
import { hasLogMonitoringPrivileges } from './api_key/has_log_monitoring_privileges';
import { getCloudUrls } from './get_cloud_urls';
import { getFallbackUrls } from './get_fallback_urls';
import { getHasLogs } from './get_has_logs';
import { getObservabilityOnboardingState } from './get_observability_onboarding_state';
@ -50,9 +49,8 @@ const installShipperSetupRoute = createObservabilityOnboardingServerRoute({
const { core, plugins } = resources;
const coreStart = await core.start();
const cloudId = plugins.cloud.setup.cloudId;
const { kibanaUrl } =
(cloudId && getCloudUrls(cloudId)) || getFallbackUrls(coreStart);
const kibanaUrl =
plugins.cloud?.setup?.kibanaUrl ?? getFallbackUrls(coreStart).kibanaUrl;
const scriptDownloadUrl = `${kibanaUrl}/plugins/observabilityOnboarding/assets/standalone_agent_setup.sh`;
const apiEndpoint = `${kibanaUrl}/api/observability_onboarding`;

View file

@ -9,7 +9,6 @@ import { getAuthenticationAPIKey } from '../../lib/get_authentication_api_key';
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { getObservabilityOnboardingState } from '../custom_logs/get_observability_onboarding_state';
import { generateYml } from './generate_yml';
import { getCloudUrls } from '../custom_logs/get_cloud_urls';
import { getFallbackUrls } from '../custom_logs/get_fallback_urls';
const generateConfig = createObservabilityOnboardingServerRoute({
@ -23,9 +22,9 @@ const generateConfig = createObservabilityOnboardingServerRoute({
const savedObjectsClient =
coreStart.savedObjects.createInternalRepository();
const cloudId = plugins.cloud.setup.cloudId;
const { elasticsearchUrl } =
(cloudId && getCloudUrls(cloudId)) || getFallbackUrls(coreStart);
const elasticsearchUrl =
plugins.cloud?.setup?.elasticsearchUrl ??
getFallbackUrls(coreStart).elasticsearchUrl;
const savedState = await getObservabilityOnboardingState({
savedObjectsClient,

View file

@ -24,7 +24,6 @@
"@kbn/shared-ux-router",
"@kbn/i18n-react",
"@kbn/cloud-plugin",
"@kbn/fleet-plugin",
"@kbn/usage-collection-plugin",
"@kbn/observability-shared-plugin",
"@kbn/core-http-server",

View file

@ -1,104 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { decodeCloudId } from './decode_cloud_id';
// Copied from Fleet's solution
// x-pack/fleet/common/services/decode_cloud_id.test.ts
describe('Enterprise Search - decodeCloudId', () => {
it('parses various CloudID formats', () => {
const tests = [
{
cloudID:
'staging:dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
expectedEsURL: 'https://cec6f261a74bf24ce33bb8811b84294f.us-east-1.aws.found.io:443',
expectedKibanaURL: 'https://c6c2ca6d042249af0cc7d7a9e9625743.us-east-1.aws.found.io:443',
},
{
cloudID:
'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
expectedEsURL: 'https://cec6f261a74bf24ce33bb8811b84294f.us-east-1.aws.found.io:443',
expectedKibanaURL: 'https://c6c2ca6d042249af0cc7d7a9e9625743.us-east-1.aws.found.io:443',
},
{
cloudID:
':dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
expectedEsURL: 'https://cec6f261a74bf24ce33bb8811b84294f.us-east-1.aws.found.io:443',
expectedKibanaURL: 'https://c6c2ca6d042249af0cc7d7a9e9625743.us-east-1.aws.found.io:443',
},
{
cloudID:
'gcp-cluster:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJDhhMDI4M2FmMDQxZjE5NWY3NzI5YmMwNGM2NmEwZmNlJDBjZDVjZDU2OGVlYmU1M2M4OWViN2NhZTViYWM4YjM3',
expectedEsURL: 'https://8a0283af041f195f7729bc04c66a0fce.us-central1.gcp.cloud.es.io:443',
expectedKibanaURL:
'https://0cd5cd568eebe53c89eb7cae5bac8b37.us-central1.gcp.cloud.es.io:443',
},
{
cloudID:
'custom-port:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA=',
expectedEsURL: 'https://ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243',
expectedKibanaURL:
'https://a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9243',
},
{
cloudID:
'different-es-kb-port:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2OjkyNDMkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA6OTI0NA==',
expectedEsURL: 'https://ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243',
expectedKibanaURL:
'https://a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244',
},
{
cloudID:
'only-kb-set:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2JGE0YzA2MjMwZTQ4YzhmY2U3YmU4OGEwNzRhM2JiM2UwOjkyNDQ=',
expectedEsURL: 'https://ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:443',
expectedKibanaURL:
'https://a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244',
},
{
cloudID:
'host-and-kb-set:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA6OTI0NA==',
expectedEsURL: 'https://ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243',
expectedKibanaURL:
'https://a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244',
},
{
cloudID:
'extra-items:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2JGE0YzA2MjMwZTQ4YzhmY2U3YmU4OGEwNzRhM2JiM2UwJGFub3RoZXJpZCRhbmRhbm90aGVy',
expectedEsURL: 'https://ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:443',
expectedKibanaURL:
'https://a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:443',
},
];
for (const test of tests) {
const decoded = decodeCloudId(test.cloudID);
expect(decoded).toBeTruthy();
expect(decoded?.elasticsearchUrl === test.expectedEsURL).toBe(true);
expect(decoded?.kibanaUrl === test.expectedKibanaURL).toBe(true);
}
});
it('returns undefined for invalid formats', () => {
const tests = [
{
cloudID:
'staging:garbagedXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
errorMsg: 'base64 decoding failed',
},
{
cloudID: 'dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJDhhMDI4M2FmMDQxZjE5NWY3NzI5YmMwNGM2NmEwZg==',
errorMsg: 'Expected at least 3 parts',
},
];
for (const test of tests) {
const decoded = decodeCloudId(test.cloudID);
expect(decoded).toBe(undefined);
// decodeCloudId currently only logs; not throws errors
}
});
});

View file

@ -1,66 +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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// copied this solution from fleet due to time constraints
// x-pack/fleet/common/services/decode_cloud_id.ts
// decodeCloudId decodes the c.id into c.esURL and c.kibURL
export function decodeCloudId(cid: string):
| {
defaultPort: string;
elasticsearchUrl: string;
host: string;
kibanaUrl: string;
}
| undefined {
// 1. Ignore anything before `:`.
const id = cid.split(':').pop();
if (!id) {
// throw new Error(`Unable to decode ${id}`);
// eslint-disable-next-line no-console
console.debug(`Unable to decode ${id}`);
return;
}
// 2. base64 decode
let decoded: string | undefined;
try {
decoded = Buffer.from(id, 'base64').toString('utf8');
} catch {
// throw new Error(`base64 decoding failed on ${id}`);
// eslint-disable-next-line no-console
console.debug(`base64 decoding failed on ${id}`);
return;
}
// 3. separate based on `$`
const words = decoded.split('$');
if (words.length < 3) {
// throw new Error(`Expected at least 3 parts in ${decoded}`);
// eslint-disable-next-line no-console
console.debug(`Expected at least 3 parts in ${decoded}`);
return;
}
// 4. extract port from the ES and Kibana host
const [host, defaultPort] = extractPortFromName(words[0]);
const [esId, esPort] = extractPortFromName(words[1], defaultPort);
const [kbId, kbPort] = extractPortFromName(words[2], defaultPort);
// 5. form the URLs
const esUrl = `https://${esId}.${host}:${esPort}`;
const kbUrl = `https://${kbId}.${host}:${kbPort}`;
return {
defaultPort,
elasticsearchUrl: esUrl,
host,
kibanaUrl: kbUrl,
};
}
// extractPortFromName takes a string in the form `id:port` and returns the
// Id and the port. If there's no `:`, the default port is returned
function extractPortFromName(word: string, defaultPort = '443') {
const [host, port = defaultPort] = word.split(':');
return [host, port];
}

View file

@ -22,7 +22,6 @@ import { i18n } from '@kbn/i18n';
import React, { useMemo, useState } from 'react';
import { docLinks } from '../../../common/doc_links';
import { PLUGIN_ID } from '../../../common';
import { decodeCloudId } from '../../../common/services/decode_cloud_id';
import { useKibanaServices } from '../hooks/use_kibana';
import { CodeBox } from './code_box';
import { javascriptDefinition } from './languages/javascript';
@ -49,12 +48,9 @@ export const ElasticsearchOverview = () => {
http,
userProfile,
} = useKibanaServices();
const cloudId = cloud.cloudId ?? '';
const elasticsearchURL = useMemo(() => {
if (cloudId.length === 0) return ELASTICSEARCH_URL_PLACEHOLDER;
const decodedCloudId = decodeCloudId(cloudId);
return decodedCloudId?.elasticsearchUrl ?? ELASTICSEARCH_URL_PLACEHOLDER;
}, [cloudId]);
return cloud?.elasticsearchUrl ?? ELASTICSEARCH_URL_PLACEHOLDER;
}, [cloud]);
const assetBasePath = http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets/`);
const codeSnippetArguments: LanguageDefinitionSnippetArguments = {
url: elasticsearchURL,

View file

@ -20,6 +20,7 @@ describe('getEsHostsTest', () => {
cloudId:
'TLS_Test:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJDI0ZDYwY2NjYmZjODRhZmZhNGRjYTQ3M2M2YjFlZDgwJGUxMjkyY2YzMTczZTRkNTViZDViM2NlNzYyZDg1NzY3',
isCloudEnabled: true,
elasticsearchUrl: 'https://24d60cccbfc84affa4dca473c6b1ed80.us-central1.gcp.cloud.es.io:443',
} as CloudSetup;
it('should return expected host in cloud', function () {

View file

@ -12,8 +12,7 @@
* 2.0.
*/
import { CloudSetup } from '@kbn/cloud-plugin/server';
import { decodeCloudId } from '@kbn/fleet-plugin/common';
import type { CloudSetup } from '@kbn/cloud-plugin/server';
import { ServiceConfig } from '../../common/config';
export function getEsHosts({
@ -23,8 +22,7 @@ export function getEsHosts({
cloud?: CloudSetup;
config: ServiceConfig;
}): string[] {
const cloudId = cloud?.isCloudEnabled && cloud.cloudId;
const cloudUrl = cloudId && decodeCloudId(cloudId)?.elasticsearchUrl;
const cloudUrl = cloud?.isCloudEnabled && cloud?.elasticsearchUrl;
const cloudHosts = cloudUrl ? [cloudUrl] : undefined;
if (cloudHosts && cloudHosts.length > 0) {
return cloudHosts;