-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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][EPM] Save installed package assets in ES #83391
Changes from all commits
f367687
592487a
c39b825
e2c1002
885539c
5ae363a
be56e11
def0310
9108e23
c4f27ab
bf06873
4baccd5
98dfeb5
e1665d1
4d42ee7
3a16056
1974324
37f7b6d
78d2483
f16e050
338745f
ec00e42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ import { | |
AGENT_POLICY_SAVED_OBJECT_TYPE, | ||
PACKAGE_POLICY_SAVED_OBJECT_TYPE, | ||
PACKAGES_SAVED_OBJECT_TYPE, | ||
ASSETS_SAVED_OBJECT_TYPE, | ||
AGENT_SAVED_OBJECT_TYPE, | ||
AGENT_EVENT_SAVED_OBJECT_TYPE, | ||
AGENT_ACTION_SAVED_OBJECT_TYPE, | ||
|
@@ -304,13 +305,39 @@ const getSavedObjectTypes = ( | |
type: { type: 'keyword' }, | ||
}, | ||
}, | ||
package_assets: { | ||
type: 'nested', | ||
properties: { | ||
id: { type: 'keyword' }, | ||
type: { type: 'keyword' }, | ||
}, | ||
}, | ||
install_started_at: { type: 'date' }, | ||
install_version: { type: 'keyword' }, | ||
install_status: { type: 'keyword' }, | ||
install_source: { type: 'keyword' }, | ||
}, | ||
}, | ||
}, | ||
[ASSETS_SAVED_OBJECT_TYPE]: { | ||
name: ASSETS_SAVED_OBJECT_TYPE, | ||
hidden: false, | ||
namespaceType: 'agnostic', | ||
management: { | ||
importableAndExportable: false, | ||
}, | ||
mappings: { | ||
properties: { | ||
package_name: { type: 'keyword' }, | ||
package_version: { type: 'keyword' }, | ||
install_source: { type: 'keyword' }, | ||
asset_path: { type: 'keyword' }, | ||
media_type: { type: 'keyword' }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also considered |
||
data_utf8: { type: 'text', index: false }, | ||
data_base64: { type: 'binary' }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Went with two fields because I thought we wanted |
||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
export function registerSavedObjects( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { extname } from 'path'; | ||
import { isBinaryFile } from 'isbinaryfile'; | ||
import mime from 'mime-types'; | ||
import uuidv5 from 'uuid/v5'; | ||
import { SavedObjectsClientContract, SavedObjectsBulkCreateObject } from 'src/core/server'; | ||
import { | ||
ASSETS_SAVED_OBJECT_TYPE, | ||
InstallablePackage, | ||
InstallSource, | ||
PackageAssetReference, | ||
} from '../../../../common'; | ||
import { getArchiveEntry } from './index'; | ||
|
||
// uuid v5 requires a SHA-1 UUID as a namespace | ||
// used to ensure same input produces the same id | ||
const ID_NAMESPACE = '71403015-cdd5-404b-a5da-6c43f35cad84'; | ||
|
||
// could be anything, picked this from https://github.com/elastic/elastic-agent-client/issues/17 | ||
const MAX_ES_ASSET_BYTES = 4 * 1024 * 1024; | ||
|
||
export interface PackageAsset { | ||
package_name: string; | ||
package_version: string; | ||
install_source: string; | ||
asset_path: string; | ||
media_type: string; | ||
data_utf8: string; | ||
data_base64: string; | ||
} | ||
|
||
export async function archiveEntryToESDocument(opts: { | ||
path: string; | ||
buffer: Buffer; | ||
name: string; | ||
version: string; | ||
installSource: InstallSource; | ||
}): Promise<PackageAsset> { | ||
const { path, buffer, name, version, installSource } = opts; | ||
const fileExt = extname(path); | ||
const contentType = mime.lookup(fileExt); | ||
const mediaType = mime.contentType(contentType || fileExt); | ||
// can use to create a data URL like `data:${mediaType};base64,${base64Data}` | ||
|
||
const bufferIsBinary = await isBinaryFile(buffer); | ||
const dataUtf8 = bufferIsBinary ? '' : buffer.toString('utf8'); | ||
const dataBase64 = bufferIsBinary ? buffer.toString('base64') : ''; | ||
|
||
// validation: filesize? asset type? anything else | ||
if (dataUtf8.length > MAX_ES_ASSET_BYTES) { | ||
throw new Error(`File at ${path} is larger than maximum allowed size of ${MAX_ES_ASSET_BYTES}`); | ||
} | ||
|
||
if (dataBase64.length > MAX_ES_ASSET_BYTES) { | ||
throw new Error( | ||
`After base64 encoding file at ${path} is larger than maximum allowed size of ${MAX_ES_ASSET_BYTES}` | ||
); | ||
} | ||
|
||
return { | ||
package_name: name, | ||
package_version: version, | ||
install_source: installSource, | ||
asset_path: path, | ||
media_type: mediaType || '', | ||
data_utf8: dataUtf8, | ||
data_base64: dataBase64, | ||
}; | ||
} | ||
|
||
export async function removeArchiveEntries(opts: { | ||
savedObjectsClient: SavedObjectsClientContract; | ||
refs: PackageAssetReference[]; | ||
}) { | ||
const { savedObjectsClient, refs } = opts; | ||
const results = await Promise.all( | ||
refs.map((ref) => savedObjectsClient.delete(ASSETS_SAVED_OBJECT_TYPE, ref.id)) | ||
); | ||
return results; | ||
} | ||
|
||
export async function saveArchiveEntries(opts: { | ||
savedObjectsClient: SavedObjectsClientContract; | ||
paths: string[]; | ||
packageInfo: InstallablePackage; | ||
installSource: InstallSource; | ||
}) { | ||
const { savedObjectsClient, paths, packageInfo, installSource } = opts; | ||
const bulkBody = await Promise.all( | ||
paths.map((path) => { | ||
const buffer = getArchiveEntry(path); | ||
if (!buffer) throw new Error(`Could not find ArchiveEntry at ${path}`); | ||
const { name, version } = packageInfo; | ||
return archiveEntryToBulkCreateObject({ path, buffer, name, version, installSource }); | ||
}) | ||
); | ||
|
||
const results = await savedObjectsClient.bulkCreate<PackageAsset>(bulkBody); | ||
return results; | ||
} | ||
|
||
export async function archiveEntryToBulkCreateObject(opts: { | ||
path: string; | ||
buffer: Buffer; | ||
name: string; | ||
version: string; | ||
installSource: InstallSource; | ||
}): Promise<SavedObjectsBulkCreateObject<PackageAsset>> { | ||
const { path, buffer, name, version, installSource } = opts; | ||
const doc = await archiveEntryToESDocument({ path, buffer, name, version, installSource }); | ||
return { | ||
id: uuidv5(doc.asset_path, ID_NAMESPACE), | ||
type: ASSETS_SAVED_OBJECT_TYPE, | ||
attributes: doc, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need to add this new type to
allSavedObjectTypes
so that your users are granted access to these new saved objects? It's probably not strictly necessary since you still requiresuperuser
privileges for Fleet, and superusers can do everything regardless of what you specify herekibana/x-pack/plugins/fleet/server/plugin.ts
Lines 104 to 112 in aa07f5c