From e55611133d411eb9f1e964fc716060c4363ab452 Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Thu, 3 Aug 2023 15:26:35 +0100 Subject: [PATCH 01/14] only enable secret storage if all fleet servers are 8.10.0 or greater --- .../plugins/fleet/common/constants/secrets.ts | 2 + .../fleet/common/types/models/settings.ts | 1 + .../plugins/fleet/server/constants/index.ts | 1 + .../fleet/server/saved_objects/index.ts | 1 + .../fleet/server/services/agents/crud.ts | 54 ++++++++++++++++++ .../server/services/fleet_server/index.ts | 43 ++++++++++++++- .../fleet/server/services/package_policy.ts | 11 ++-- .../plugins/fleet/server/services/secrets.ts | 55 ++++++++++++++++++- .../fleet/server/types/so_attributes.ts | 1 + 9 files changed, 160 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/fleet/common/constants/secrets.ts b/x-pack/plugins/fleet/common/constants/secrets.ts index 4c42b1d68013e..d9cc9ed33fcde 100644 --- a/x-pack/plugins/fleet/common/constants/secrets.ts +++ b/x-pack/plugins/fleet/common/constants/secrets.ts @@ -6,3 +6,5 @@ */ export const SECRETS_INDEX = '.fleet-secrets'; + +export const SECRETS_MINIMUM_FLEET_SERVER_VERSION = '8.10.0'; diff --git a/x-pack/plugins/fleet/common/types/models/settings.ts b/x-pack/plugins/fleet/common/types/models/settings.ts index 01f95146e3621..e4175ae3bbfaf 100644 --- a/x-pack/plugins/fleet/common/types/models/settings.ts +++ b/x-pack/plugins/fleet/common/types/models/settings.ts @@ -14,4 +14,5 @@ export interface BaseSettings { export interface Settings extends BaseSettings { id: string; preconfigured_fields?: Array<'fleet_server_hosts'>; + secret_storage_requirements_met?: boolean; } diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index 356aaa0e0cf8e..0293ec65e0fc4 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -79,6 +79,7 @@ export { MESSAGE_SIGNING_SERVICE_API_ROUTES, // secrets SECRETS_INDEX, + SECRETS_MINIMUM_FLEET_SERVER_VERSION, } from '../../common/constants'; export { diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 43e0069d3be48..979c6e30e9834 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -88,6 +88,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ fleet_server_hosts: { type: 'keyword' }, has_seen_add_data_notice: { type: 'boolean', index: false }, prerelease_integrations_enabled: { type: 'boolean' }, + secret_storage_requirements_met: { type: 'boolean' }, }, }, migrations: { diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index fdce358049006..546a9273170a1 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -444,6 +444,60 @@ export async function getAgentsById( ); } +// given a list of agentPolicyIds, return a map of agent version => count of agents +// this is used to get all fleet server versions +export async function getAgentVersionsForAgentPolicyIds( + esClient: ElasticsearchClient, + agentPolicyIds: string[] +): Promise> { + const versionCount: Record = {}; + + if (!agentPolicyIds.length) { + return versionCount; + } + + const res = esClient.search< + FleetServerAgent, + Record<'agent_versions', { buckets: Array<{ key: string; doc_count: number }> }> + >({ + size: 0, + track_total_hits: false, + body: { + query: { + bool: { + filter: [ + { + terms: { + policy_id: agentPolicyIds, + }, + }, + ], + }, + }, + aggs: { + agent_versions: { + terms: { + field: 'local_metadata.elastic.agent.version.keyword', + size: 1000, + }, + }, + }, + }, + index: AGENTS_INDEX, + ignore_unavailable: true, + }); + + const { aggregations } = await res; + + if (aggregations && aggregations.agent_versions) { + aggregations.agent_versions.buckets.forEach((bucket) => { + versionCount[bucket.key] = bucket.doc_count; + }); + } + + return versionCount; +} + export async function getAgentByAccessAPIKeyId( esClient: ElasticsearchClient, soClient: SavedObjectsClientContract, diff --git a/x-pack/plugins/fleet/server/services/fleet_server/index.ts b/x-pack/plugins/fleet/server/services/fleet_server/index.ts index 6ba6dfcc24910..4f55ed4d35443 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/index.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/index.ts @@ -5,10 +5,14 @@ * 2.0. */ -import type { ElasticsearchClient } from '@kbn/core/server'; +import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import semverGte from 'semver/functions/gte'; +import semverCoerce from 'semver/functions/coerce'; import { FLEET_SERVER_SERVERS_INDEX } from '../../constants'; +import { getAgentVersionsForAgentPolicyIds } from '../agents'; +import { packagePolicyService } from '../package_policy'; /** * Check if at least one fleet server is connected */ @@ -23,3 +27,40 @@ export async function hasFleetServers(esClient: ElasticsearchClient) { return (res.hits.total as number) > 0; } + +export async function allFleetServerVersionsAreAbove( + esClient: ElasticsearchClient, + soClient: SavedObjectsClientContract, + version: string +): Promise { + let hasMore = true; + const policyIds = new Set(); + let page = 1; + while (hasMore) { + const res = await packagePolicyService.list(soClient, { + page: page++, + perPage: 20, + kuery: 'ingest-package-policies.package.name:fleet_server', + }); + + for (const item of res.items) { + policyIds.add(item.policy_id); + } + + if (res.items.length === 0) { + hasMore = false; + } + } + + if (policyIds.size === 0) { + return false; + } + + const versionCounts = await getAgentVersionsForAgentPolicyIds(esClient, [...policyIds]); + + return _allVersionsAreAtLeast(version, Object.keys(versionCounts)); +} + +function _allVersionsAreAtLeast(version: string, versions: string[]) { + return versions.every((v) => semverGte(semverCoerce(v)!, version)); +} diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index c17f8f4ec2ea3..d584fed4850a1 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -117,6 +117,7 @@ import { extractAndUpdateSecrets, extractAndWriteSecrets, deleteSecretsIfNotReferenced as deleteSecrets, + isSecretStorageEnabled, } from './secrets'; export type InputsOverride = Partial & { @@ -243,8 +244,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } validatePackagePolicyOrThrow(enrichedPackagePolicy, pkgInfo); - const { secretsStorage: secretsStorageEnabled } = appContextService.getExperimentalFeatures(); - if (secretsStorageEnabled) { + if (await isSecretStorageEnabled(esClient, soClient)) { const secretsRes = await extractAndWriteSecrets({ packagePolicy: { ...enrichedPackagePolicy, inputs }, packageInfo: pkgInfo, @@ -747,8 +747,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { }); validatePackagePolicyOrThrow(packagePolicy, pkgInfo); - const { secretsStorage: secretsStorageEnabled } = appContextService.getExperimentalFeatures(); - if (secretsStorageEnabled) { + if (await isSecretStorageEnabled(esClient, soClient)) { const secretsRes = await extractAndUpdateSecrets({ oldPackagePolicy, packagePolicyUpdate: { ...restOfPackagePolicy, inputs }, @@ -914,9 +913,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { ); if (pkgInfo) { validatePackagePolicyOrThrow(packagePolicy, pkgInfo); - const { secretsStorage: secretsStorageEnabled } = - appContextService.getExperimentalFeatures(); - if (secretsStorageEnabled) { + if (await isSecretStorageEnabled(esClient, soClient)) { const secretsRes = await extractAndUpdateSecrets({ oldPackagePolicy, packagePolicyUpdate: { ...restOfPackagePolicy, inputs }, diff --git a/x-pack/plugins/fleet/server/services/secrets.ts b/x-pack/plugins/fleet/server/services/secrets.ts index bd70e8435b02a..83bc7d46faf7d 100644 --- a/x-pack/plugins/fleet/server/services/secrets.ts +++ b/x-pack/plugins/fleet/server/services/secrets.ts @@ -37,12 +37,14 @@ import type { } from '../types'; import { FleetError } from '../errors'; -import { SECRETS_INDEX } from '../constants'; +import { SECRETS_INDEX, SECRETS_MINIMUM_FLEET_SERVER_VERSION } from '../constants'; import { auditLoggingService } from './audit_logging'; import { appContextService } from './app_context'; import { packagePolicyService } from './package_policy'; +import { settingsService } from '.'; +import { allFleetServerVersionsAreAbove } from './fleet_server'; interface SecretPath { path: string; @@ -364,6 +366,57 @@ export function getPolicySecretPaths( return [...packageLevelVarPaths, ...inputSecretPaths]; } +export async function isSecretStorageEnabled( + esClient: ElasticsearchClient, + soClient: SavedObjectsClientContract +): Promise { + const logger = appContextService.getLogger(); + + // first check if the feature flag is enabled, if not secrets are disabled + const { secretsStorage: secretsStorageEnabled } = appContextService.getExperimentalFeatures(); + if (!secretsStorageEnabled) { + logger.debug('Secrets storage is disabled by feature flag'); + return false; + } + + // if serverless then secrets will always be supported + const isFleetServerStandalone = + appContextService.getConfig()?.internal?.fleetServerStandalone ?? false; + + if (isFleetServerStandalone) { + logger.trace('Secrets storage is enabled as fleet server is standalone'); + return true; + } + + // now check the flag in settings to see if the fleet server requirement has already been met + // once the requirement has been met, secrets are always on + const settings = await settingsService.getSettings(soClient); + + if (settings.secret_storage_requirements_met) { + return true; + } + + // otherwise check if we have the minimum fleet server version and enable secrets if so + if ( + await allFleetServerVersionsAreAbove(esClient, soClient, SECRETS_MINIMUM_FLEET_SERVER_VERSION) + ) { + logger.debug('Enabling secrets storage as minimum fleet server version has been met'); + try { + await settingsService.saveSettings(soClient, { + secret_storage_requirements_met: true, + }); + } catch (err) { + // we can suppress this error as it will be retried on the next function call + logger.warn(`Failed to save settings after enabling secrets storage: ${err.message}`); + } + + return true; + } + + logger.debug('Secrets storage is disabled as minimum fleet server version has not been met'); + return false; +} + function _getPackageLevelSecretPaths( packagePolicy: NewPackagePolicy, packageInfo: PackageInfo diff --git a/x-pack/plugins/fleet/server/types/so_attributes.ts b/x-pack/plugins/fleet/server/types/so_attributes.ts index 13ccff751c38d..3e926457b4cbf 100644 --- a/x-pack/plugins/fleet/server/types/so_attributes.ts +++ b/x-pack/plugins/fleet/server/types/so_attributes.ts @@ -201,6 +201,7 @@ export interface SettingsSOAttributes { prerelease_integrations_enabled: boolean; has_seen_add_data_notice?: boolean; fleet_server_hosts?: string[]; + secret_storage_requirements_met?: boolean; } export interface DownloadSourceSOAttributes { From f5acad25d173c67a3a3f2d84243b3c8774693350 Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Tue, 8 Aug 2023 15:24:19 +0100 Subject: [PATCH 02/14] fix tests --- .../server/services/fleet_server/index.ts | 2 +- .../apis/policy_secrets.ts | 133 ++++++++---------- 2 files changed, 56 insertions(+), 79 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/fleet_server/index.ts b/x-pack/plugins/fleet/server/services/fleet_server/index.ts index 4f55ed4d35443..70e83f09be918 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/index.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/index.ts @@ -52,12 +52,12 @@ export async function allFleetServerVersionsAreAbove( } } + // there must be at least one fleet server for this check to pass if (policyIds.size === 0) { return false; } const versionCounts = await getAgentVersionsForAgentPolicyIds(esClient, [...policyIds]); - return _allVersionsAreAtLeast(version, Object.keys(versionCounts)); } diff --git a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts index 34f20e88b0a81..78b18bcacbad8 100644 --- a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts +++ b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts @@ -10,8 +10,9 @@ // So start investigating from earliest test failure in the file. import type { Client } from '@elastic/elasticsearch'; -import expect from '@kbn/expect'; +import expect from '@kbn/expect/expect'; import { FullAgentPolicy } from '@kbn/fleet-plugin/common'; +import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common/constants'; import { v4 as uuidv4 } from 'uuid'; import { FtrProviderContext } from '../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../helpers'; @@ -41,39 +42,55 @@ function createdPolicyToUpdatePolicy(policy: any) { return updatedPolicy; } +const SECRETS_INDEX_NAME = '.fleet-secrets'; export default function (providerContext: FtrProviderContext) { - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/162732 - describe.skip('fleet policy secrets', () => { + describe('fleet policy secrets', () => { const { getService } = providerContext; const es: Client = getService('es'); const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); - const getPackagePolicyById = async (id: string) => { - const { body } = await supertest.get(`/api/fleet/package_policies/${id}`); - return body.item; + const getSecrets = async (ids?: string[]) => { + const query = ids ? { terms: { _id: ids } } : { match_all: {} }; + return es.search({ + index: SECRETS_INDEX_NAME, + body: { + query, + }, + }); }; - const maybeCreateSecretsIndex = async () => { - // create mock .secrets index for testing - if (await es.indices.exists({ index: '.fleet-test-secrets' })) { - await es.indices.delete({ index: '.fleet-test-secrets' }); - } - await es.indices.create({ - index: '.fleet-test-secrets', + const deleteAllSecrets = async () => { + return es.deleteByQuery({ + index: SECRETS_INDEX_NAME, body: { - mappings: { - properties: { - value: { - type: 'keyword', - }, - }, + query: { + match_all: {}, }, }, }); }; + const getPackagePolicyById = async (id: string) => { + const { body } = await supertest.get(`/api/fleet/package_policies/${id}`); + return body.item; + }; + + const enableSecrets = async (enabled: boolean) => { + try { + await kibanaServer.savedObjects.create({ + type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + attributes: { + secret_storage_requirements_met: true, + }, + overwrite: true, + }); + } catch (e) { + throw e; + } + }; + const getFullAgentPolicyById = async (id: string) => { const { body } = await supertest.get(`/api/fleet/agent_policies/${id}/full`).expect(200); return body.item; @@ -137,10 +154,10 @@ export default function (providerContext: FtrProviderContext) { let agentPolicyId: string; before(async () => { await kibanaServer.savedObjects.cleanStandardList(); - await getService('esArchiver').load( - 'x-pack/test/functional/es_archives/fleet/empty_fleet_server' - ); - await maybeCreateSecretsIndex(); + + await deleteAllSecrets(); + + await enableSecrets(true); }); setupFleetAndAgents(providerContext); @@ -261,16 +278,7 @@ export default function (providerContext: FtrProviderContext) { }); it('should have correctly created the secrets', async () => { - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - ids: { - values: [packageVarId, inputVarId, streamVarId], - }, - }, - }, - }); + const searchRes = await getSecrets([packageVarId, inputVarId, streamVarId]); expect(searchRes.hits.hits.length).to.eql(3); @@ -337,14 +345,7 @@ export default function (providerContext: FtrProviderContext) { }); it('should have correctly deleted unused secrets after update', async () => { - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + const searchRes = await getSecrets(); expect(searchRes.hits.hits.length).to.eql(3); // should have created 1 and deleted 1 doc @@ -374,14 +375,7 @@ export default function (providerContext: FtrProviderContext) { expectCompiledPolicyVars(policyDoc, updatedPackageVarId); - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + const searchRes = await getSecrets(); expect(searchRes.hits.hits.length).to.eql(3); @@ -413,53 +407,36 @@ export default function (providerContext: FtrProviderContext) { updatedPackagePolicy.vars.package_var_secret.value.id, updatedPackageVarId, ]; - - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - terms: { - _id: packageVarSecretIds, - }, - }, - }, - }); + const searchRes = await getSecrets(packageVarSecretIds); expect(searchRes.hits.hits.length).to.eql(2); }); it('should not delete used secrets on package policy delete', async () => { - return supertest + await supertest .delete(`/api/fleet/package_policies/${duplicatedPackagePolicyId}`) .set('kbn-xsrf', 'xxxx') .expect(200); - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + // sleep to allow for secrets to be deleted + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const searchRes = await getSecrets(); + // should have deleted new_package_secret_val_2 expect(searchRes.hits.hits.length).to.eql(3); }); it('should delete all secrets on package policy delete', async () => { - return supertest + await supertest .delete(`/api/fleet/package_policies/${createdPackagePolicyId}`) .set('kbn-xsrf', 'xxxx') .expect(200); - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + // sleep to allow for secrets to be deleted + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const searchRes = await getSecrets(); expect(searchRes.hits.hits.length).to.eql(0); }); From 24026548085cecd5dc7dddb64a497cba0877df9a Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Tue, 8 Aug 2023 17:01:16 +0100 Subject: [PATCH 03/14] fix policy secret tests --- .../apis/policy_secrets.ts | 124 +++++++----------- 1 file changed, 44 insertions(+), 80 deletions(-) diff --git a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts index 34f20e88b0a81..8c2195d7cb488 100644 --- a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts +++ b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts @@ -10,7 +10,7 @@ // So start investigating from earliest test failure in the file. import type { Client } from '@elastic/elasticsearch'; -import expect from '@kbn/expect'; +import expect from '@kbn/expect/expect'; import { FullAgentPolicy } from '@kbn/fleet-plugin/common'; import { v4 as uuidv4 } from 'uuid'; import { FtrProviderContext } from '../../api_integration/ftr_provider_context'; @@ -41,37 +41,43 @@ function createdPolicyToUpdatePolicy(policy: any) { return updatedPolicy; } +const SECRETS_INDEX_NAME = '.fleet-secrets'; export default function (providerContext: FtrProviderContext) { - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/162732 - describe.skip('fleet policy secrets', () => { + describe('fleet policy secrets', () => { const { getService } = providerContext; const es: Client = getService('es'); const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); - const getPackagePolicyById = async (id: string) => { - const { body } = await supertest.get(`/api/fleet/package_policies/${id}`); - return body.item; + const getSecrets = async (ids?: string[]) => { + const query = ids ? { terms: { _id: ids } } : { match_all: {} }; + return es.search({ + index: SECRETS_INDEX_NAME, + body: { + query, + }, + }); }; - const maybeCreateSecretsIndex = async () => { - // create mock .secrets index for testing - if (await es.indices.exists({ index: '.fleet-test-secrets' })) { - await es.indices.delete({ index: '.fleet-test-secrets' }); - } - await es.indices.create({ - index: '.fleet-test-secrets', - body: { - mappings: { - properties: { - value: { - type: 'keyword', - }, + const deleteAllSecrets = async () => { + try { + await es.deleteByQuery({ + index: SECRETS_INDEX_NAME, + body: { + query: { + match_all: {}, }, }, - }, - }); + }); + } catch (err) { + // index doesnt exis + } + }; + + const getPackagePolicyById = async (id: string) => { + const { body } = await supertest.get(`/api/fleet/package_policies/${id}`); + return body.item; }; const getFullAgentPolicyById = async (id: string) => { @@ -137,10 +143,8 @@ export default function (providerContext: FtrProviderContext) { let agentPolicyId: string; before(async () => { await kibanaServer.savedObjects.cleanStandardList(); - await getService('esArchiver').load( - 'x-pack/test/functional/es_archives/fleet/empty_fleet_server' - ); - await maybeCreateSecretsIndex(); + + await deleteAllSecrets(); }); setupFleetAndAgents(providerContext); @@ -261,16 +265,7 @@ export default function (providerContext: FtrProviderContext) { }); it('should have correctly created the secrets', async () => { - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - ids: { - values: [packageVarId, inputVarId, streamVarId], - }, - }, - }, - }); + const searchRes = await getSecrets([packageVarId, inputVarId, streamVarId]); expect(searchRes.hits.hits.length).to.eql(3); @@ -337,14 +332,7 @@ export default function (providerContext: FtrProviderContext) { }); it('should have correctly deleted unused secrets after update', async () => { - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + const searchRes = await getSecrets(); expect(searchRes.hits.hits.length).to.eql(3); // should have created 1 and deleted 1 doc @@ -374,14 +362,7 @@ export default function (providerContext: FtrProviderContext) { expectCompiledPolicyVars(policyDoc, updatedPackageVarId); - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + const searchRes = await getSecrets(); expect(searchRes.hits.hits.length).to.eql(3); @@ -413,53 +394,36 @@ export default function (providerContext: FtrProviderContext) { updatedPackagePolicy.vars.package_var_secret.value.id, updatedPackageVarId, ]; - - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - terms: { - _id: packageVarSecretIds, - }, - }, - }, - }); + const searchRes = await getSecrets(packageVarSecretIds); expect(searchRes.hits.hits.length).to.eql(2); }); it('should not delete used secrets on package policy delete', async () => { - return supertest + await supertest .delete(`/api/fleet/package_policies/${duplicatedPackagePolicyId}`) .set('kbn-xsrf', 'xxxx') .expect(200); - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + // sleep to allow for secrets to be deleted + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const searchRes = await getSecrets(); + // should have deleted new_package_secret_val_2 expect(searchRes.hits.hits.length).to.eql(3); }); it('should delete all secrets on package policy delete', async () => { - return supertest + await supertest .delete(`/api/fleet/package_policies/${createdPackagePolicyId}`) .set('kbn-xsrf', 'xxxx') .expect(200); - const searchRes = await es.search({ - index: '.fleet-test-secrets', - body: { - query: { - match_all: {}, - }, - }, - }); + // sleep to allow for secrets to be deleted + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const searchRes = await getSecrets(); expect(searchRes.hits.hits.length).to.eql(0); }); From 8b7a337d35ce192613e7f29aff2e5a40423e1a97 Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Tue, 8 Aug 2023 17:09:31 +0100 Subject: [PATCH 04/14] fix deleting when index doesn't exist --- .../apis/policy_secrets.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts index 78b18bcacbad8..b46d4401ad730 100644 --- a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts +++ b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts @@ -62,14 +62,18 @@ export default function (providerContext: FtrProviderContext) { }; const deleteAllSecrets = async () => { - return es.deleteByQuery({ - index: SECRETS_INDEX_NAME, - body: { - query: { - match_all: {}, + try { + await es.deleteByQuery({ + index: SECRETS_INDEX_NAME, + body: { + query: { + match_all: {}, + }, }, - }, - }); + }); + } catch (err) { + // index doesnt exist + } }; const getPackagePolicyById = async (id: string) => { From 7d6588038d93096cc7bb26904e62819072abd900 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:14:04 +0000 Subject: [PATCH 05/14] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- x-pack/test/fleet_api_integration/apis/policy_secrets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts index 8c2195d7cb488..52b614f389ba9 100644 --- a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts +++ b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts @@ -10,7 +10,7 @@ // So start investigating from earliest test failure in the file. import type { Client } from '@elastic/elasticsearch'; -import expect from '@kbn/expect/expect'; +import expect from '@kbn/expect'; import { FullAgentPolicy } from '@kbn/fleet-plugin/common'; import { v4 as uuidv4 } from 'uuid'; import { FtrProviderContext } from '../../api_integration/ftr_provider_context'; From e1dde30bcd62e5eaa2abc2211ccf0f945c24ee43 Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Tue, 8 Aug 2023 20:17:45 +0100 Subject: [PATCH 06/14] remove test index from config --- x-pack/test/fleet_api_integration/config.base.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/test/fleet_api_integration/config.base.ts b/x-pack/test/fleet_api_integration/config.base.ts index e5746278a26f9..3e4b35988efba 100644 --- a/x-pack/test/fleet_api_integration/config.base.ts +++ b/x-pack/test/fleet_api_integration/config.base.ts @@ -74,7 +74,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { 'secretsStorage', 'agentTamperProtectionEnabled', ])}`, - `--xpack.fleet.developer.testSecretsIndex=.fleet-test-secrets`, `--logging.loggers=${JSON.stringify([ ...getKibanaCliLoggers(xPackAPITestsConfig.get('kbnTestServer.serverArgs')), From 96fbdb2a18d1522ed2d2f142ad0080b8e43de8cd Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Tue, 8 Aug 2023 20:19:44 +0100 Subject: [PATCH 07/14] remove test code --- x-pack/plugins/fleet/server/config.ts | 1 - x-pack/plugins/fleet/server/services/secrets.ts | 17 ++++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/fleet/server/config.ts b/x-pack/plugins/fleet/server/config.ts index 14e5a86aa73ad..9726837375eed 100644 --- a/x-pack/plugins/fleet/server/config.ts +++ b/x-pack/plugins/fleet/server/config.ts @@ -139,7 +139,6 @@ export const config: PluginConfigDescriptor = { disableRegistryVersionCheck: schema.boolean({ defaultValue: false }), allowAgentUpgradeSourceUri: schema.boolean({ defaultValue: false }), bundledPackageLocation: schema.string({ defaultValue: DEFAULT_BUNDLED_PACKAGE_LOCATION }), - testSecretsIndex: schema.maybe(schema.string()), }), packageVerification: schema.object({ gpgKeyPath: schema.string({ defaultValue: DEFAULT_GPG_KEY_PATH }), diff --git a/x-pack/plugins/fleet/server/services/secrets.ts b/x-pack/plugins/fleet/server/services/secrets.ts index bd70e8435b02a..c6f14f38a54d7 100644 --- a/x-pack/plugins/fleet/server/services/secrets.ts +++ b/x-pack/plugins/fleet/server/services/secrets.ts @@ -49,15 +49,6 @@ interface SecretPath { value: PackagePolicyConfigRecordEntry; } -// This will be removed once the secrets index PR is merged into elasticsearch -function getSecretsIndex() { - const testIndex = appContextService.getConfig()?.developer?.testSecretsIndex; - if (testIndex) { - return testIndex; - } - return SECRETS_INDEX; -} - export async function createSecrets(opts: { esClient: ElasticsearchClient; values: string[]; @@ -66,7 +57,7 @@ export async function createSecrets(opts: { const logger = appContextService.getLogger(); const body = values.flatMap((value) => [ { - create: { _index: getSecretsIndex() }, + create: { _index: SECRETS_INDEX }, }, { value }, ]); @@ -99,7 +90,7 @@ export async function createSecrets(opts: { value: values[i], })); } catch (e) { - const msg = `Error creating secrets in ${getSecretsIndex()} index: ${e}`; + const msg = `Error creating secrets in ${SECRETS_INDEX} index: ${e}`; logger.error(msg); throw new FleetError(msg); } @@ -192,7 +183,7 @@ export async function _deleteSecrets(opts: { const logger = appContextService.getLogger(); const body = ids.flatMap((id) => [ { - delete: { _index: getSecretsIndex(), _id: id }, + delete: { _index: SECRETS_INDEX, _id: id }, }, ]); @@ -221,7 +212,7 @@ export async function _deleteSecrets(opts: { throw new Error(JSON.stringify(errorItems)); } } catch (e) { - const msg = `Error deleting secrets from ${getSecretsIndex()} index: ${e}`; + const msg = `Error deleting secrets from ${SECRETS_INDEX} index: ${e}`; logger.error(msg); throw new FleetError(msg); } From 366654882fad79ece2e4f615137546be1350fc9f Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Thu, 10 Aug 2023 16:16:20 +0100 Subject: [PATCH 08/14] fix bug moving from plain secret vals to secret references --- x-pack/plugins/fleet/server/services/secrets.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/services/secrets.ts b/x-pack/plugins/fleet/server/services/secrets.ts index fd710b8e6b6de..cd6f29a6c2b51 100644 --- a/x-pack/plugins/fleet/server/services/secrets.ts +++ b/x-pack/plugins/fleet/server/services/secrets.ts @@ -272,10 +272,21 @@ export async function extractAndUpdateSecrets(opts: { ...createdSecrets.map(({ id }) => ({ id })), ]; + const secretsToDelete: PolicySecretReference[] = []; + + toDelete.forEach((secretPath) => { + // check if the previous secret is actually a secret refrerence + // it may be that secrets were not enabled at the time of creation + // in which case they are just stored as plain text + if (secretPath.value.value.isSecretRef) { + secretsToDelete.push({ id: secretPath.value.value.id }); + } + }); + return { packagePolicyUpdate: policyWithSecretRefs, secretReferences, - secretsToDelete: toDelete.map((secretPath) => ({ id: secretPath.value.value.id })), + secretsToDelete, }; } From 97a9c4ce5f5a42f57dd04609b5c13ce35f0a524a Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Thu, 10 Aug 2023 16:16:31 +0100 Subject: [PATCH 09/14] integration tests --- .../apis/policy_secrets.ts | 211 +++++++++++++++++- 1 file changed, 206 insertions(+), 5 deletions(-) diff --git a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts index 9ad1bde876b0e..1b52ce14f3347 100644 --- a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts +++ b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts @@ -51,6 +51,124 @@ export default function (providerContext: FtrProviderContext) { const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); + const createFleetServerAgentPolicy = async () => { + const agentPolicyResponse = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxx') + .send({ + name: `Fleet server policy ${uuidv4()}`, + namespace: 'default', + }) + .expect(200); + + const agentPolicyId = agentPolicyResponse.body.item.id; + + // create fleet_server package policy + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxx') + .send({ + force: true, + package: { + name: 'fleet_server', + version: '1.3.1', + }, + name: `Fleet Server ${uuidv4()}`, + namespace: 'default', + policy_id: agentPolicyId, + vars: {}, + inputs: { + 'fleet_server-fleet-server': { + enabled: true, + vars: { + custom: '', + }, + streams: {}, + }, + }, + }) + .expect(200); + + return agentPolicyId; + }; + + const createPolicyWithSecrets = async () => { + return supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: `secrets-${Date.now()}`, + description: '', + namespace: 'default', + policy_id: agentPolicyId, + inputs: { + 'secrets-test_input': { + enabled: true, + vars: { + input_var_secret: 'input_secret_val', + }, + streams: { + 'secrets.log': { + enabled: true, + vars: { + stream_var_secret: 'stream_secret_val', + }, + }, + }, + }, + }, + vars: { + package_var_secret: 'package_secret_val', + }, + package: { + name: 'secrets', + version: '1.0.0', + }, + }) + .expect(200); + }; + + const createFleetServerAgent = async ( + agentPolicyId: string, + hostname: string, + agentVersion: string + ) => { + const agentResponse = await es.index({ + index: '.fleet-agents', + body: { + access_api_key_id: 'api-key-3', + active: true, + policy_id: agentPolicyId, + type: 'PERMANENT', + local_metadata: { + host: { hostname }, + elastic: { agent: { version: agentVersion } }, + }, + user_provided_metadata: {}, + enrolled_at: '2022-06-21T12:14:25Z', + last_checkin: '2022-06-27T12:28:29Z', + tags: ['tag1'], + }, + }); + + return agentResponse._id; + }; + + const clearAgents = async () => { + try { + await es.deleteByQuery({ + index: '.fleet-agents', + body: { + query: { + match_all: {}, + }, + }, + }); + } catch (err) { + // index doesn't exist + } + }; + const getSecrets = async (ids?: string[]) => { const query = ids ? { terms: { _id: ids } } : { match_all: {} }; return es.search({ @@ -81,20 +199,38 @@ export default function (providerContext: FtrProviderContext) { return body.item; }; - const enableSecrets = async (enabled: boolean) => { + const enableSecrets = async () => { try { - await kibanaServer.savedObjects.create({ + await kibanaServer.savedObjects.update({ type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + id: 'fleet-default-settings', attributes: { secret_storage_requirements_met: true, }, - overwrite: true, + overwrite: false, }); } catch (e) { throw e; } }; + const disableSecrets = async () => { + try { + await kibanaServer.savedObjects.update({ + type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + id: 'fleet-default-settings', + attributes: { + secret_storage_requirements_met: false, + }, + overwrite: false, + }); + // sleep to wait for refresh + await new Promise((resolve) => setTimeout(resolve, 2000)); + } catch (e) { + throw e; + } + }; + const getFullAgentPolicyById = async (id: string) => { const { body } = await supertest.get(`/api/fleet/agent_policies/${id}/full`).expect(200); return body.item; @@ -156,12 +292,13 @@ export default function (providerContext: FtrProviderContext) { skipIfNoDockerRegistry(providerContext); let agentPolicyId: string; + let fleetServerAgentPolicyId: string; before(async () => { await kibanaServer.savedObjects.cleanStandardList(); await deleteAllSecrets(); - - await enableSecrets(true); + await clearAgents(); + await enableSecrets(); }); setupFleetAndAgents(providerContext); @@ -177,6 +314,8 @@ export default function (providerContext: FtrProviderContext) { .expect(200); agentPolicyId = agentPolicyResponse.item.id; + + fleetServerAgentPolicyId = await createFleetServerAgentPolicy(); }); after(async () => { @@ -444,5 +583,67 @@ export default function (providerContext: FtrProviderContext) { expect(searchRes.hits.hits.length).to.eql(0); }); + + let fleetServerCheckPolicy: any; + it('should not store secrets if fleet server does not meet minimum version', async () => { + await createFleetServerAgent(fleetServerAgentPolicyId, 'server_1', '7.0.0'); + await disableSecrets(); + const { body: createResBody } = await createPolicyWithSecrets(); + + const createdPolicy = createResBody.item; + + // secret should be in plain text i.e not a secret refrerence + expect(createdPolicy.vars.package_var_secret.value).eql('package_secret_val'); + + fleetServerCheckPolicy = createdPolicy; + }); + + it('should not store secrets if there are no fleet servers', async () => { + await clearAgents(); + + const { body: createResBody } = await createPolicyWithSecrets(); + + const createdPolicy = createResBody.item; + + // secret should be in plain text i.e not a secret refrerence + expect(createdPolicy.vars.package_var_secret.value).eql('package_secret_val'); + }); + + it('should convert plain text values to secrets once fleet server requirements are met', async () => { + if (!fleetServerCheckPolicy) { + throw new Error('fleetServerCheckPolicy not set, previous test must have failed'); + } + await clearAgents(); + await createFleetServerAgent(fleetServerAgentPolicyId, 'server_2', '9.0.0'); + + const updatedPolicy = createdPolicyToUpdatePolicy(fleetServerCheckPolicy); + delete updatedPolicy.name; + + updatedPolicy.vars.package_var_secret.value = 'package_secret_val_2'; + + const updateRes = await supertest + .put(`/api/fleet/package_policies/${fleetServerCheckPolicy.id}`) + .set('kbn-xsrf', 'xxxx') + .send(updatedPolicy) + .expect(200); + + const updatedPolicyRes = updateRes.body.item; + + expect(updatedPolicyRes.vars.package_var_secret.value.isSecretRef).eql(true); + expect(updatedPolicyRes.inputs[0].vars.input_var_secret.value.isSecretRef).eql(true); + expect(updatedPolicyRes.inputs[0].streams[0].vars.stream_var_secret.value.isSecretRef).eql( + true + ); + }); + + it('should not revert to plaintext values if the user adds an out of date fleet server', async () => { + await createFleetServerAgent(fleetServerAgentPolicyId, 'server_3', '7.0.0'); + + const { body: createResBody } = await createPolicyWithSecrets(); + + const createdPolicy = createResBody.item; + + expect(createdPolicy.vars.package_var_secret.value.isSecretRef).eql(true); + }); }); } From c298bd2c60d8cc6086aebddb22a34b880ac0975d Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 10 Aug 2023 16:02:05 +0000 Subject: [PATCH 10/14] [CI] Auto-commit changed files from 'node scripts/check_mappings_update --fix' --- packages/kbn-check-mappings-update-cli/current_mappings.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index c2c9022b42b2a..468018de2d680 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -1505,6 +1505,9 @@ }, "prerelease_integrations_enabled": { "type": "boolean" + }, + "secret_storage_requirements_met": { + "type": "boolean" } } }, From 25e23065299249a511f088c3fcc5026acdb5fb3f Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Fri, 11 Aug 2023 13:57:55 +0200 Subject: [PATCH 11/14] review comments --- .../migrations/group2/check_registered_types.test.ts | 2 +- x-pack/plugins/fleet/server/saved_objects/index.ts | 2 +- x-pack/plugins/fleet/server/services/fleet_server/index.ts | 2 +- x-pack/plugins/fleet/server/services/secrets.ts | 6 +++--- x-pack/test/fleet_api_integration/apis/policy_secrets.ts | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index d0b9d01272b24..7a32d15732535 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -107,7 +107,7 @@ describe('checking migration metadata changes on all registered SO types', () => "ingest-download-sources": "d7edc5e588d9afa61c4b831604582891c54ef1c7", "ingest-outputs": "b4e636b13a5d0f89f0400fb67811d4cca4736eb0", "ingest-package-policies": "55816507db0134b8efbe0509e311a91ce7e1c6cc", - "ingest_manager_settings": "418311b03c8eda53f5d2ea6f54c1356afaa65511", + "ingest_manager_settings": "64955ef1b7a9ffa894d4bb9cf863b5602bfa6885", "inventory-view": "b8683c8e352a286b4aca1ab21003115a4800af83", "kql-telemetry": "93c1d16c1a0dfca9c8842062cf5ef8f62ae401ad", "legacy-url-alias": "9b8cca3fbb2da46fd12823d3cd38fdf1c9f24bc8", diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index d4f41c0348311..cc3ec940432aa 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -90,7 +90,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ fleet_server_hosts: { type: 'keyword' }, has_seen_add_data_notice: { type: 'boolean', index: false }, prerelease_integrations_enabled: { type: 'boolean' }, - secret_storage_requirements_met: { type: 'boolean' }, + secret_storage_requirements_met: { type: 'boolean', index: false }, }, }, migrations: { diff --git a/x-pack/plugins/fleet/server/services/fleet_server/index.ts b/x-pack/plugins/fleet/server/services/fleet_server/index.ts index 70e83f09be918..f8c14384bb53d 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/index.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/index.ts @@ -28,7 +28,7 @@ export async function hasFleetServers(esClient: ElasticsearchClient) { return (res.hits.total as number) > 0; } -export async function allFleetServerVersionsAreAbove( +export async function allFleetServerVersionsAreAtLeast( esClient: ElasticsearchClient, soClient: SavedObjectsClientContract, version: string diff --git a/x-pack/plugins/fleet/server/services/secrets.ts b/x-pack/plugins/fleet/server/services/secrets.ts index cd6f29a6c2b51..d33283e51b234 100644 --- a/x-pack/plugins/fleet/server/services/secrets.ts +++ b/x-pack/plugins/fleet/server/services/secrets.ts @@ -43,7 +43,7 @@ import { auditLoggingService } from './audit_logging'; import { appContextService } from './app_context'; import { packagePolicyService } from './package_policy'; import { settingsService } from '.'; -import { allFleetServerVersionsAreAbove } from './fleet_server'; +import { allFleetServerVersionsAreAtLeast } from './fleet_server'; export async function createSecrets(opts: { esClient: ElasticsearchClient; @@ -389,7 +389,7 @@ export async function isSecretStorageEnabled( // otherwise check if we have the minimum fleet server version and enable secrets if so if ( - await allFleetServerVersionsAreAbove(esClient, soClient, SECRETS_MINIMUM_FLEET_SERVER_VERSION) + await allFleetServerVersionsAreAtLeast(esClient, soClient, SECRETS_MINIMUM_FLEET_SERVER_VERSION) ) { logger.debug('Enabling secrets storage as minimum fleet server version has been met'); try { @@ -404,7 +404,7 @@ export async function isSecretStorageEnabled( return true; } - logger.debug('Secrets storage is disabled as minimum fleet server version has not been met'); + logger.info('Secrets storage is disabled as minimum fleet server version has not been met'); return false; } diff --git a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts index 1b52ce14f3347..12f8f47042b80 100644 --- a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts +++ b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts @@ -158,6 +158,7 @@ export default function (providerContext: FtrProviderContext) { try { await es.deleteByQuery({ index: '.fleet-agents', + refresh: true, body: { query: { match_all: {}, From 796d6a09cd1a57a5c0c30c99635a97ccc885d88e Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Fri, 11 Aug 2023 15:10:12 +0200 Subject: [PATCH 12/14] fix integration tests --- .../fleet/server/services/agents/crud.ts | 66 ++++++++++--------- .../server/services/fleet_server/index.ts | 10 +-- .../plugins/fleet/server/services/secrets.ts | 1 + .../apis/policy_secrets.ts | 29 ++++---- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 546a9273170a1..b6a124d3c32ab 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -456,43 +456,49 @@ export async function getAgentVersionsForAgentPolicyIds( return versionCount; } - const res = esClient.search< - FleetServerAgent, - Record<'agent_versions', { buckets: Array<{ key: string; doc_count: number }> }> - >({ - size: 0, - track_total_hits: false, - body: { - query: { - bool: { - filter: [ - { - terms: { - policy_id: agentPolicyIds, + try { + const res = esClient.search< + FleetServerAgent, + Record<'agent_versions', { buckets: Array<{ key: string; doc_count: number }> }> + >({ + size: 0, + track_total_hits: false, + body: { + query: { + bool: { + filter: [ + { + terms: { + policy_id: agentPolicyIds, + }, }, - }, - ], + ], + }, }, - }, - aggs: { - agent_versions: { - terms: { - field: 'local_metadata.elastic.agent.version.keyword', - size: 1000, + aggs: { + agent_versions: { + terms: { + field: 'local_metadata.elastic.agent.version.keyword', + size: 1000, + }, }, }, }, - }, - index: AGENTS_INDEX, - ignore_unavailable: true, - }); + index: AGENTS_INDEX, + ignore_unavailable: true, + }); - const { aggregations } = await res; + const { aggregations } = await res; - if (aggregations && aggregations.agent_versions) { - aggregations.agent_versions.buckets.forEach((bucket) => { - versionCount[bucket.key] = bucket.doc_count; - }); + if (aggregations && aggregations.agent_versions) { + aggregations.agent_versions.buckets.forEach((bucket) => { + versionCount[bucket.key] = bucket.doc_count; + }); + } + } catch (error) { + if (error.statusCode !== 404) { + throw error; + } } return versionCount; diff --git a/x-pack/plugins/fleet/server/services/fleet_server/index.ts b/x-pack/plugins/fleet/server/services/fleet_server/index.ts index f8c14384bb53d..3690e86a71f48 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/index.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/index.ts @@ -52,13 +52,15 @@ export async function allFleetServerVersionsAreAtLeast( } } - // there must be at least one fleet server for this check to pass - if (policyIds.size === 0) { + const versionCounts = await getAgentVersionsForAgentPolicyIds(esClient, [...policyIds]); + const versions = Object.keys(versionCounts); + + // there must be at least one fleet server agent for this check to pass + if (versions.length === 0) { return false; } - const versionCounts = await getAgentVersionsForAgentPolicyIds(esClient, [...policyIds]); - return _allVersionsAreAtLeast(version, Object.keys(versionCounts)); + return _allVersionsAreAtLeast(version, versions); } function _allVersionsAreAtLeast(version: string, versions: string[]) { diff --git a/x-pack/plugins/fleet/server/services/secrets.ts b/x-pack/plugins/fleet/server/services/secrets.ts index d33283e51b234..886e7d7243172 100644 --- a/x-pack/plugins/fleet/server/services/secrets.ts +++ b/x-pack/plugins/fleet/server/services/secrets.ts @@ -384,6 +384,7 @@ export async function isSecretStorageEnabled( const settings = await settingsService.getSettings(soClient); if (settings.secret_storage_requirements_met) { + logger.debug('Secrets storage already met, turned on is settings'); return true; } diff --git a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts index 12f8f47042b80..63878420084a2 100644 --- a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts +++ b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts @@ -135,6 +135,7 @@ export default function (providerContext: FtrProviderContext) { ) => { const agentResponse = await es.index({ index: '.fleet-agents', + refresh: true, body: { access_api_key_id: 'api-key-3', active: true, @@ -225,8 +226,6 @@ export default function (providerContext: FtrProviderContext) { }, overwrite: false, }); - // sleep to wait for refresh - await new Promise((resolve) => setTimeout(resolve, 2000)); } catch (e) { throw e; } @@ -585,20 +584,22 @@ export default function (providerContext: FtrProviderContext) { expect(searchRes.hits.hits.length).to.eql(0); }); - let fleetServerCheckPolicy: any; it('should not store secrets if fleet server does not meet minimum version', async () => { await createFleetServerAgent(fleetServerAgentPolicyId, 'server_1', '7.0.0'); await disableSecrets(); - const { body: createResBody } = await createPolicyWithSecrets(); - const createdPolicy = createResBody.item; + const createdPolicy = await createPolicyWSecretVar(); // secret should be in plain text i.e not a secret refrerence expect(createdPolicy.vars.package_var_secret.value).eql('package_secret_val'); - - fleetServerCheckPolicy = createdPolicy; }); + async function createPolicyWSecretVar() { + const { body: createResBody } = await createPolicyWithSecrets(); + const createdPolicy = createResBody.item; + return createdPolicy; + } + it('should not store secrets if there are no fleet servers', async () => { await clearAgents(); @@ -611,19 +612,19 @@ export default function (providerContext: FtrProviderContext) { }); it('should convert plain text values to secrets once fleet server requirements are met', async () => { - if (!fleetServerCheckPolicy) { - throw new Error('fleetServerCheckPolicy not set, previous test must have failed'); - } await clearAgents(); + + const createdPolicy = await createPolicyWSecretVar(); + await createFleetServerAgent(fleetServerAgentPolicyId, 'server_2', '9.0.0'); - const updatedPolicy = createdPolicyToUpdatePolicy(fleetServerCheckPolicy); + const updatedPolicy = createdPolicyToUpdatePolicy(createdPolicy); delete updatedPolicy.name; updatedPolicy.vars.package_var_secret.value = 'package_secret_val_2'; const updateRes = await supertest - .put(`/api/fleet/package_policies/${fleetServerCheckPolicy.id}`) + .put(`/api/fleet/package_policies/${createdPolicy.id}`) .set('kbn-xsrf', 'xxxx') .send(updatedPolicy) .expect(200); @@ -640,9 +641,7 @@ export default function (providerContext: FtrProviderContext) { it('should not revert to plaintext values if the user adds an out of date fleet server', async () => { await createFleetServerAgent(fleetServerAgentPolicyId, 'server_3', '7.0.0'); - const { body: createResBody } = await createPolicyWithSecrets(); - - const createdPolicy = createResBody.item; + const createdPolicy = await createPolicyWSecretVar(); expect(createdPolicy.vars.package_var_secret.value.isSecretRef).eql(true); }); From c7ffd7491b60bba346f2b5d93c3d7aa5c8fd6e5e Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Fri, 11 Aug 2023 15:49:48 +0200 Subject: [PATCH 13/14] fix core test --- .../migrations/group2/check_registered_types.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 7a32d15732535..16f100aabdecb 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -107,7 +107,7 @@ describe('checking migration metadata changes on all registered SO types', () => "ingest-download-sources": "d7edc5e588d9afa61c4b831604582891c54ef1c7", "ingest-outputs": "b4e636b13a5d0f89f0400fb67811d4cca4736eb0", "ingest-package-policies": "55816507db0134b8efbe0509e311a91ce7e1c6cc", - "ingest_manager_settings": "64955ef1b7a9ffa894d4bb9cf863b5602bfa6885", + "ingest_manager_settings": "db39f0b81908aa3d1ef3fad92041354557e1032b", "inventory-view": "b8683c8e352a286b4aca1ab21003115a4800af83", "kql-telemetry": "93c1d16c1a0dfca9c8842062cf5ef8f62ae401ad", "legacy-url-alias": "9b8cca3fbb2da46fd12823d3cd38fdf1c9f24bc8", From d96e5cd6bd1ac6c5918ef540fffe1010cbb82f9c Mon Sep 17 00:00:00 2001 From: Julia Bardi Date: Fri, 11 Aug 2023 17:55:01 +0200 Subject: [PATCH 14/14] revert index:false --- .../migrations/group2/check_registered_types.test.ts | 2 +- x-pack/plugins/fleet/server/saved_objects/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 16f100aabdecb..7a32d15732535 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -107,7 +107,7 @@ describe('checking migration metadata changes on all registered SO types', () => "ingest-download-sources": "d7edc5e588d9afa61c4b831604582891c54ef1c7", "ingest-outputs": "b4e636b13a5d0f89f0400fb67811d4cca4736eb0", "ingest-package-policies": "55816507db0134b8efbe0509e311a91ce7e1c6cc", - "ingest_manager_settings": "db39f0b81908aa3d1ef3fad92041354557e1032b", + "ingest_manager_settings": "64955ef1b7a9ffa894d4bb9cf863b5602bfa6885", "inventory-view": "b8683c8e352a286b4aca1ab21003115a4800af83", "kql-telemetry": "93c1d16c1a0dfca9c8842062cf5ef8f62ae401ad", "legacy-url-alias": "9b8cca3fbb2da46fd12823d3cd38fdf1c9f24bc8", diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index cc3ec940432aa..d4f41c0348311 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -90,7 +90,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ fleet_server_hosts: { type: 'keyword' }, has_seen_add_data_notice: { type: 'boolean', index: false }, prerelease_integrations_enabled: { type: 'boolean' }, - secret_storage_requirements_met: { type: 'boolean', index: false }, + secret_storage_requirements_met: { type: 'boolean' }, }, }, migrations: {