From e46e08240784677e82ab726a02cd98f56ec23400 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 8 Dec 2021 10:52:50 -0500 Subject: [PATCH] [Fleet] Configure ca trusted fingerprint for on prem users (#120549) --- .../server/kibana_config_writer.test.ts | 11 ++-- .../server/kibana_config_writer.ts | 7 ++- .../openapi/components/schemas/output.yaml | 2 + .../openapi/paths/outputs@{output_id}.yaml | 2 + .../fleet/common/types/models/output.ts | 1 + .../fleet/common/types/rest_spec/output.ts | 2 + .../fleet_server_on_prem_instructions.tsx | 14 ++++- .../components/install_command_utils.test.ts | 33 ++++++++--- .../components/install_command_utils.ts | 44 ++++++++------ .../fleet/server/saved_objects/index.ts | 1 + .../agent_policies/full_agent_policy.test.ts | 57 ++++++++++++++++++- .../agent_policies/full_agent_policy.ts | 7 ++- .../fleet/server/services/preconfiguration.ts | 1 + .../server/types/models/preconfiguration.ts | 1 + .../fleet/server/types/rest_spec/output.ts | 2 + 15 files changed, 148 insertions(+), 37 deletions(-) diff --git a/src/plugins/interactive_setup/server/kibana_config_writer.test.ts b/src/plugins/interactive_setup/server/kibana_config_writer.test.ts index 82d02882698d..d097d7cc4a05 100644 --- a/src/plugins/interactive_setup/server/kibana_config_writer.test.ts +++ b/src/plugins/interactive_setup/server/kibana_config_writer.test.ts @@ -35,7 +35,8 @@ describe('KibanaConfigWriter', () => { throw new Error('Invalid certificate'); } return { - fingerprint256: 'fingerprint256', + fingerprint256: + 'D4:86:CE:00:AC:71:E4:1D:2B:70:D0:87:A5:55:FA:5D:D1:93:6C:DB:45:80:79:53:7B:A3:AC:13:3E:48:34:D6', }; }; @@ -131,7 +132,7 @@ describe('KibanaConfigWriter', () => { elasticsearch.hosts: [some-host] elasticsearch.serviceAccountToken: some-value elasticsearch.ssl.certificateAuthorities: [/data/ca_1234.crt] - xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_sha256: fingerprint256}] + xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_trusted_fingerprint: d486ce00ac71e41d2b70d087a555fa5dd1936cdb458079537ba3ac133e4834d6}] ", ], @@ -198,7 +199,7 @@ describe('KibanaConfigWriter', () => { elasticsearch.username: username elasticsearch.password: password elasticsearch.ssl.certificateAuthorities: [/data/ca_1234.crt] - xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_sha256: fingerprint256}] + xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_trusted_fingerprint: d486ce00ac71e41d2b70d087a555fa5dd1936cdb458079537ba3ac133e4834d6}] ", ], @@ -275,7 +276,7 @@ describe('KibanaConfigWriter', () => { elasticsearch.hosts: [some-host] elasticsearch.serviceAccountToken: some-value elasticsearch.ssl.certificateAuthorities: [/data/ca_1234.crt] - xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_sha256: fingerprint256}] + xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_trusted_fingerprint: d486ce00ac71e41d2b70d087a555fa5dd1936cdb458079537ba3ac133e4834d6}] ", ], @@ -329,7 +330,7 @@ describe('KibanaConfigWriter', () => { monitoring.ui.container.elasticsearch.enabled: true elasticsearch.serviceAccountToken: some-value elasticsearch.ssl.certificateAuthorities: [/data/ca_1234.crt] - xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_sha256: fingerprint256}] + xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_trusted_fingerprint: d486ce00ac71e41d2b70d087a555fa5dd1936cdb458079537ba3ac133e4834d6}] ", ], diff --git a/src/plugins/interactive_setup/server/kibana_config_writer.ts b/src/plugins/interactive_setup/server/kibana_config_writer.ts index af177fee33bc..eac1bd0cef17 100644 --- a/src/plugins/interactive_setup/server/kibana_config_writer.ts +++ b/src/plugins/interactive_setup/server/kibana_config_writer.ts @@ -38,7 +38,7 @@ interface FleetOutputConfig { is_default_monitoring: boolean; type: 'elasticsearch'; hosts: string[]; - ca_sha256: string; + ca_trusted_fingerprint: string; } export class KibanaConfigWriter { @@ -187,7 +187,8 @@ export class KibanaConfigWriter { */ private static getFleetDefaultOutputConfig(caCert: string, host: string): FleetOutputConfig[] { const cert = new X509Certificate(caCert); - const certFingerprint = cert.fingerprint256; + // fingerprint256 is a ":" separated uppercase hexadecimal string + const certFingerprint = cert.fingerprint256.split(':').join('').toLowerCase(); return [ { @@ -197,7 +198,7 @@ export class KibanaConfigWriter { is_default_monitoring: true, type: 'elasticsearch', hosts: [host], - ca_sha256: certFingerprint, + ca_trusted_fingerprint: certFingerprint, }, ]; } diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml index e695f0048e6a..a91ec2cb14e9 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml @@ -18,6 +18,8 @@ properties: type: string ca_sha256: type: string + ca_trusted_fingerprint: + type: string api_key: type: string config: diff --git a/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml index 326a65692a03..d70c78dd7de5 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/outputs@{output_id}.yaml @@ -61,6 +61,8 @@ put: type: string ca_sha256: type: string + ca_trusted_fingerprint: + type: string config_yaml: type: string required: diff --git a/x-pack/plugins/fleet/common/types/models/output.ts b/x-pack/plugins/fleet/common/types/models/output.ts index fada8171b91f..2ff50c0fc7bd 100644 --- a/x-pack/plugins/fleet/common/types/models/output.ts +++ b/x-pack/plugins/fleet/common/types/models/output.ts @@ -17,6 +17,7 @@ export interface NewOutput { type: ValueOf; hosts?: string[]; ca_sha256?: string; + ca_trusted_fingerprint?: string; api_key?: string; config_yaml?: string; is_preconfigured?: boolean; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/output.ts b/x-pack/plugins/fleet/common/types/rest_spec/output.ts index 98376b3ba522..9a5001a3af10 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/output.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/output.ts @@ -32,6 +32,7 @@ export interface PutOutputRequest { name?: string; hosts?: string[]; ca_sha256?: string; + ca_trusted_fingerprint?: string; config_yaml?: string; is_default?: boolean; is_default_monitoring?: boolean; @@ -45,6 +46,7 @@ export interface PostOutputRequest { name: string; hosts?: string[]; ca_sha256?: string; + ca_trusted_fingerprint?: string; is_default?: boolean; is_default_monitoring?: boolean; config_yaml?: string; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx index 4efff98fe39b..b2eaf904ee1b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx @@ -245,6 +245,7 @@ export const useFleetServerInstructions = (policyId?: string) => { const { data: settings, resendRequest: refreshSettings } = useGetSettings(); const fleetServerHost = settings?.item.fleet_server_hosts?.[0]; const esHost = output?.hosts?.[0]; + const sslCATrustedFingerprint: string | undefined = output?.ca_trusted_fingerprint; const installCommand = useMemo((): string => { if (!serviceToken || !esHost) { @@ -257,9 +258,18 @@ export const useFleetServerInstructions = (policyId?: string) => { serviceToken, policyId, fleetServerHost, - deploymentMode === 'production' + deploymentMode === 'production', + sslCATrustedFingerprint ); - }, [serviceToken, esHost, platform, policyId, fleetServerHost, deploymentMode]); + }, [ + serviceToken, + esHost, + platform, + policyId, + fleetServerHost, + deploymentMode, + sslCATrustedFingerprint, + ]); const getServiceToken = useCallback(async () => { setIsLoadingServiceToken(true); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts index 62580a1445f0..d05107e5058d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts @@ -17,7 +17,7 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + "sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1" `); @@ -31,7 +31,7 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - ".\\\\elastic-agent.exe install \` + ".\\\\elastic-agent.exe install \` --fleet-server-es=http://elasticsearch:9200 \` --fleet-server-service-token=service-token-1" `); @@ -45,11 +45,30 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - "sudo elastic-agent enroll \\\\ + "sudo elastic-agent enroll \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1" `); }); + + it('should return the correct command sslCATrustedFingerprint option is passed', () => { + const res = getInstallCommandForPlatform( + 'linux-mac', + 'http://elasticsearch:9200', + 'service-token-1', + undefined, + undefined, + false, + 'fingerprint123456' + ); + + expect(res).toMatchInlineSnapshot(` + "sudo ./elastic-agent install \\\\ + --fleet-server-es=http://elasticsearch:9200 \\\\ + --fleet-server-service-token=service-token-1 \\\\ + --fleet-server-es-ca-trusted-fingerprint=fingerprint123456" + `); + }); }); describe('with policy id', () => { @@ -62,7 +81,7 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + "sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" @@ -78,7 +97,7 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - ".\\\\elastic-agent.exe install \` + ".\\\\elastic-agent.exe install \` --fleet-server-es=http://elasticsearch:9200 \` --fleet-server-service-token=service-token-1 \` --fleet-server-policy=policy-1" @@ -94,7 +113,7 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - "sudo elastic-agent enroll \\\\ + "sudo elastic-agent enroll \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" @@ -178,7 +197,7 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - "sudo elastic-agent enroll \\\\ + "sudo elastic-agent enroll \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1" `); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts index f5c40e807169..64ae4903af53 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts @@ -13,37 +13,49 @@ export function getInstallCommandForPlatform( serviceToken: string, policyId?: string, fleetServerHost?: string, - isProductionDeployment?: boolean + isProductionDeployment?: boolean, + sslCATrustedFingerprint?: string ) { - let commandArguments = ''; - const newLineSeparator = platform === 'windows' ? '`' : '\\'; + const commandArguments = []; + const newLineSeparator = platform === 'windows' ? '`\n' : '\\\n'; if (isProductionDeployment && fleetServerHost) { - commandArguments += `--url=${fleetServerHost} ${newLineSeparator}\n`; - } else { - commandArguments += ` ${newLineSeparator}\n`; + commandArguments.push(['url', fleetServerHost]); } - commandArguments += ` --fleet-server-es=${esHost}`; - commandArguments += ` ${newLineSeparator}\n --fleet-server-service-token=${serviceToken}`; + commandArguments.push(['fleet-server-es', esHost]); + commandArguments.push(['fleet-server-service-token', serviceToken]); if (policyId) { - commandArguments += ` ${newLineSeparator}\n --fleet-server-policy=${policyId}`; + commandArguments.push(['fleet-server-policy', policyId]); + } + + if (sslCATrustedFingerprint) { + commandArguments.push(['fleet-server-es-ca-trusted-fingerprint', sslCATrustedFingerprint]); } if (isProductionDeployment) { - commandArguments += ` ${newLineSeparator}\n --certificate-authorities=`; - commandArguments += ` ${newLineSeparator}\n --fleet-server-es-ca=`; - commandArguments += ` ${newLineSeparator}\n --fleet-server-cert=`; - commandArguments += ` ${newLineSeparator}\n --fleet-server-cert-key=`; + commandArguments.push(['certificate-authorities', '']); + if (!sslCATrustedFingerprint) { + commandArguments.push(['fleet-server-es-ca', '']); + } + commandArguments.push(['fleet-server-cert', '']); + commandArguments.push(['fleet-server-cert-key', '']); } + const commandArgumentsStr = commandArguments.reduce((acc, [key, val]) => { + if (acc === '' && key === 'url') { + return `--${key}=${val}`; + } + return (acc += ` ${newLineSeparator} --${key}=${val}`); + }, ''); + switch (platform) { case 'linux-mac': - return `sudo ./elastic-agent install ${commandArguments}`; + return `sudo ./elastic-agent install ${commandArgumentsStr}`; case 'windows': - return `.\\elastic-agent.exe install ${commandArguments}`; + return `.\\elastic-agent.exe install ${commandArgumentsStr}`; case 'rpm-deb': - return `sudo elastic-agent enroll ${commandArguments}`; + return `sudo elastic-agent enroll ${commandArgumentsStr}`; default: return ''; } diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 26adf7b9fcbc..6058cfba12ca 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -113,6 +113,7 @@ const getSavedObjectTypes = ( is_default_monitoring: { type: 'boolean' }, hosts: { type: 'keyword' }, ca_sha256: { type: 'keyword', index: false }, + ca_trusted_fingerprint: { type: 'keyword', index: false }, config: { type: 'flattened' }, config_yaml: { type: 'text' }, is_preconfigured: { type: 'boolean', index: false }, diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index d720aa72e18f..1bc191922624 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -12,7 +12,7 @@ import type { AgentPolicy, Output } from '../../types'; import { agentPolicyService } from '../agent_policy'; import { agentPolicyUpdateEventHandler } from '../agent_policy_update'; -import { getFullAgentPolicy } from './full_agent_policy'; +import { getFullAgentPolicy, transformOutputToFullPolicyOutput } from './full_agent_policy'; import { getMonitoringPermissions } from './monitoring_permissions'; const mockedGetElasticAgentMonitoringPermissions = getMonitoringPermissions as jest.Mock< @@ -305,3 +305,58 @@ describe('getFullAgentPolicy', () => { expect(agentPolicy?.outputs.default).toBeDefined(); }); }); + +describe('transformOutputToFullPolicyOutput', () => { + it('should works with only required field on a output', () => { + const policyOutput = transformOutputToFullPolicyOutput({ + id: 'id123', + hosts: ['http://host.fr'], + is_default: false, + is_default_monitoring: false, + name: 'test output', + type: 'elasticsearch', + api_key: 'apikey123', + }); + + expect(policyOutput).toMatchInlineSnapshot(` + Object { + "api_key": "apikey123", + "ca_sha256": undefined, + "hosts": Array [ + "http://host.fr", + ], + "type": "elasticsearch", + } + `); + }); + it('should support ca_trusted_fingerprint field on a output', () => { + const policyOutput = transformOutputToFullPolicyOutput({ + id: 'id123', + hosts: ['http://host.fr'], + is_default: false, + is_default_monitoring: false, + name: 'test output', + type: 'elasticsearch', + api_key: 'apikey123', + ca_trusted_fingerprint: 'fingerprint123', + config_yaml: ` +test: 1234 +ssl.test: 123 + `, + }); + + expect(policyOutput).toMatchInlineSnapshot(` + Object { + "api_key": "apikey123", + "ca_sha256": undefined, + "hosts": Array [ + "http://host.fr", + ], + "ssl.ca_trusted_fingerprint": "fingerprint123", + "ssl.test": 123, + "test": 1234, + "type": "elasticsearch", + } + `); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts index f89a186c1a5f..166b2f77dc27 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts @@ -168,19 +168,20 @@ export async function getFullAgentPolicy( return fullAgentPolicy; } -function transformOutputToFullPolicyOutput( +export function transformOutputToFullPolicyOutput( output: Output, standalone = false ): FullAgentPolicyOutput { // eslint-disable-next-line @typescript-eslint/naming-convention - const { config_yaml, type, hosts, ca_sha256, api_key } = output; + const { config_yaml, type, hosts, ca_sha256, ca_trusted_fingerprint, api_key } = output; const configJs = config_yaml ? safeLoad(config_yaml) : {}; const newOutput: FullAgentPolicyOutput = { + ...configJs, type, hosts, ca_sha256, api_key, - ...configJs, + ...(ca_trusted_fingerprint ? { 'ssl.ca_trusted_fingerprint': ca_trusted_fingerprint } : {}), }; if (standalone) { diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index 84c5c73524f1..a41c7606287e 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -61,6 +61,7 @@ function isPreconfiguredOutputDifferentFromCurrent( preconfiguredOutput.hosts.map(normalizeHostsForAgents) )) || existingOutput.ca_sha256 !== preconfiguredOutput.ca_sha256 || + existingOutput.ca_trusted_fingerprint !== preconfiguredOutput.ca_trusted_fingerprint || existingOutput.config_yaml !== preconfiguredOutput.config_yaml ); } diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts index 64ab8f8ee3a8..4d030e1e87ed 100644 --- a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts @@ -87,6 +87,7 @@ export const PreconfiguredOutputsSchema = schema.arrayOf( type: schema.oneOf([schema.literal(outputType.Elasticsearch)]), hosts: schema.maybe(schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }))), ca_sha256: schema.maybe(schema.string()), + ca_trusted_fingerprint: schema.maybe(schema.string()), config: schema.maybe(schema.object({}, { unknowns: 'allow' })), }), { diff --git a/x-pack/plugins/fleet/server/types/rest_spec/output.ts b/x-pack/plugins/fleet/server/types/rest_spec/output.ts index dc60b2608721..de2ddeb3a1bf 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/output.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/output.ts @@ -30,6 +30,7 @@ export const PostOutputRequestSchema = { is_default_monitoring: schema.boolean({ defaultValue: false }), hosts: schema.maybe(schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }))), ca_sha256: schema.maybe(schema.string()), + ca_trusted_fingerprint: schema.maybe(schema.string()), config_yaml: schema.maybe(schema.string()), }), }; @@ -45,6 +46,7 @@ export const PutOutputRequestSchema = { is_default_monitoring: schema.maybe(schema.boolean()), hosts: schema.maybe(schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }))), ca_sha256: schema.maybe(schema.string()), + ca_trusted_fingerprint: schema.maybe(schema.string()), config_yaml: schema.maybe(schema.string()), }), };