Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Use new Fleet Secrets ES APIs instead of reading/writing secrets index #163075

Merged
merged 3 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/common/constants/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
* 2.0.
*/

export const SECRETS_INDEX = '.fleet-secrets';
export const SECRETS_ENDPOINT_PATH = '/_fleet/secret';
15 changes: 13 additions & 2 deletions x-pack/plugins/fleet/common/types/models/secret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { PackagePolicyConfigRecordEntry } from '../..';
export interface Secret {
id: string;
value: string;
}

export interface SecretElasticDoc {
Expand All @@ -18,7 +17,19 @@ export interface VarSecretReference {
id: string;
isSecretRef: true;
}
export interface SecretPath {
path: string;
value: PackagePolicyConfigRecordEntry;
}
// this is used in the top level secret_refs array on package and agent policies
export interface PolicySecretReference {
id: string;
}

export interface DeletedSecretResponse {
deleted: boolean;
}
export interface DeletedSecretReference {
id: string;
deleted: boolean;
}
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/server/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export {
// Message signing service
MESSAGE_SIGNING_SERVICE_API_ROUTES,
// secrets
SECRETS_INDEX,
SECRETS_ENDPOINT_PATH,
} from '../../common/constants';

export {
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/fleet/server/services/package_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,6 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
packageInfo: pkgInfo,
esClient,
});

restOfPackagePolicy = secretsRes.packagePolicyUpdate;
secretReferences = secretsRes.secretReferences;
secretsToDelete = secretsRes.secretsToDelete;
Expand Down
144 changes: 62 additions & 82 deletions x-pack/plugins/fleet/server/services/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,13 @@
*/

import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
import type { BulkResponse } from '@elastic/elasticsearch/lib/api/types';

import { keyBy, partition } from 'lodash';
import { keyBy } from 'lodash';
import { set } from '@kbn/safer-lodash-set';

import { packageHasNoPolicyTemplates } from '../../common/services/policy_template';

import type {
NewPackagePolicy,
PackagePolicyConfigRecordEntry,
RegistryStream,
UpdatePackagePolicy,
} from '../../common';
import type { NewPackagePolicy, RegistryStream, UpdatePackagePolicy } from '../../common';
import { SO_SEARCH_LIMIT } from '../../common';

import {
Expand All @@ -34,75 +28,61 @@ import type {
Secret,
VarSecretReference,
PolicySecretReference,
SecretPath,
DeletedSecretResponse,
DeletedSecretReference,
} from '../types';

import { FleetError } from '../errors';
import { SECRETS_INDEX } from '../constants';
import { SECRETS_ENDPOINT_PATH } from '../constants';

import { retryTransientEsErrors } from './epm/elasticsearch/retry';

import { auditLoggingService } from './audit_logging';

import { appContextService } from './app_context';
import { packagePolicyService } from './package_policy';

interface SecretPath {
path: string;
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[];
}): Promise<Secret[]> {
const { esClient, values } = opts;
const logger = appContextService.getLogger();
const body = values.flatMap((value) => [
{
create: { _index: getSecretsIndex() },
},
{ value },
]);
let res: BulkResponse;
try {
res = await esClient.bulk({
body,
});

const [errorItems, successItems] = partition(res.items, (a) => a.create?.error);
const secretsResponse: Secret[] = await Promise.all(
values.map(async (value) => {
try {
return await retryTransientEsErrors(
() =>
esClient.transport.request({
method: 'POST',
path: SECRETS_ENDPOINT_PATH,
body: { value },
}),
{ logger }
);
} catch (err) {
const msg = `Error creating secrets: ${err}`;
logger.error(msg);
throw new FleetError(msg);
}
})
);

successItems.forEach((item) => {
auditLoggingService.writeCustomAuditLog({
message: `secret created: ${item.create!._id}`,
event: {
action: 'secret_create',
category: ['database'],
type: ['access'],
outcome: 'success',
},
});
secretsResponse.forEach((item) => {
auditLoggingService.writeCustomAuditLog({
message: `secret created: ${item.id}`,
event: {
action: 'secret_create',
category: ['database'],
type: ['access'],
outcome: 'success',
},
});
});

if (errorItems.length) {
throw new Error(JSON.stringify(errorItems));
}

return res.items.map((item, i) => ({
id: item.create!._id as string,
value: values[i],
}));
} catch (e) {
const msg = `Error creating secrets in ${getSecretsIndex()} index: ${e}`;
logger.error(msg);
throw new FleetError(msg);
}
return secretsResponse;
}

export async function deleteSecretsIfNotReferenced(opts: {
Expand Down Expand Up @@ -190,41 +170,41 @@ export async function _deleteSecrets(opts: {
}): Promise<void> {
const { esClient, ids } = opts;
const logger = appContextService.getLogger();
const body = ids.flatMap((id) => [
{
delete: { _index: getSecretsIndex(), _id: id },
},
]);

let res: BulkResponse;

try {
res = await esClient.bulk({
body,
});

const [errorItems, successItems] = partition(res.items, (a) => a.delete?.error);
const deletedRes: DeletedSecretReference[] = await Promise.all(
ids.map(async (id) => {
try {
const getDeleteRes: DeletedSecretResponse = await retryTransientEsErrors(
() =>
esClient.transport.request({
method: 'DELETE',
path: `${SECRETS_ENDPOINT_PATH}/${id}`,
}),
{ logger }
);

return { ...getDeleteRes, id };
} catch (err) {
const msg = `Error deleting secrets: ${err}`;
logger.error(msg);
throw new FleetError(msg);
}
})
);

successItems.forEach((item) => {
deletedRes.forEach((item) => {
if (item.deleted === true) {
auditLoggingService.writeCustomAuditLog({
message: `secret deleted: ${item.delete!._id}`,
message: `secret deleted: ${item.id}`,
event: {
action: 'secret_delete',
category: ['database'],
type: ['access'],
outcome: 'success',
},
});
});

if (errorItems.length) {
throw new Error(JSON.stringify(errorItems));
}
} catch (e) {
const msg = `Error deleting secrets from ${getSecretsIndex()} index: ${e}`;
logger.error(msg);
throw new FleetError(msg);
}
});
}

export async function extractAndWriteSecrets(opts: {
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/fleet/server/types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,11 @@ export type {
ExperimentalDataStreamFeature,
Secret,
SecretElasticDoc,
SecretPath,
VarSecretReference,
PolicySecretReference,
DeletedSecretResponse,
DeletedSecretReference,
PackageListItem,
PackageList,
InstallationInfo,
Expand Down