-
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
Create the "Asset Inventory" Kibana Plugin #202291
Changes from all commits
810cd88
dec7691
5cf1eec
58926ec
1d34c57
9fe76f2
37a5e55
be8e5be
6866096
5154d27
5fe347c
b20f8e1
c084342
211b7a9
c6f043c
3f8e548
f107440
b93cc55
ed9dc25
9a79958
bc0b505
b575463
d8b7c24
b8aee1b
15dcb5a
4c1cf24
1d03d3f
2a4bb6a
2d45a8e
08b0be5
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 |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Asset Inventory Kibana Plugin | ||
|
||
Centralized asset inventory experience within the Elastic Security solution. A central place for users to view and manage all their assets from different environments. | ||
|
||
--- | ||
|
||
## Development | ||
|
||
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. | ||
|
||
## Testing | ||
|
||
For general guidelines, read [Kibana Testing Guide](https://www.elastic.co/guide/en/kibana/current/development-tests.html) for more details |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
* 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 PLUGIN_ID = 'assetInventory'; | ||
export const PLUGIN_NAME = 'assetInventory'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"type": "plugin", | ||
"id": "@kbn/asset-inventory-plugin", | ||
"owner": ["@elastic/kibana-cloud-security-posture"], | ||
"group": "security", | ||
"visibility": "private", | ||
"description": "Centralized asset inventory experience within the Elastic Security solution. A central place for users to view and manage all their assets from different environments", | ||
"plugin": { | ||
"id": "assetInventory", | ||
"browser": true, | ||
"server": true, | ||
"configPath": ["xpack", "assetInventory"], | ||
"requiredPlugins": [], | ||
"requiredBundles": [], | ||
"optionalPlugins": [] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"author": "Elastic", | ||
"name": "@kbn/asset-inventory-plugin", | ||
"version": "1.0.0", | ||
"private": true, | ||
"license": "Elastic License 2.0" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* 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 React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import type { AppMountParameters, CoreStart } from '@kbn/core/public'; | ||
import type { AppPluginStartDependencies } from './types'; | ||
import { AssetInventoryApp } from './components/app'; | ||
|
||
export const renderApp = ( | ||
{ notifications, http }: CoreStart, | ||
{}: AppPluginStartDependencies, | ||
{ appBasePath, element }: AppMountParameters | ||
) => { | ||
ReactDOM.render( | ||
<AssetInventoryApp basename={appBasePath} notifications={notifications} http={http} />, | ||
element | ||
); | ||
|
||
return () => ReactDOM.unmountComponentAtNode(element); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
* 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 React from 'react'; | ||
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react'; | ||
import { BrowserRouter as Router } from '@kbn/shared-ux-router'; | ||
import { EuiPageTemplate, EuiTitle } from '@elastic/eui'; | ||
import type { CoreStart } from '@kbn/core/public'; | ||
|
||
interface AssetInventoryAppDeps { | ||
basename: string; | ||
notifications: CoreStart['notifications']; | ||
http: CoreStart['http']; | ||
} | ||
|
||
export const AssetInventoryApp = ({ basename }: AssetInventoryAppDeps) => { | ||
return ( | ||
<Router basename={basename}> | ||
<I18nProvider> | ||
<> | ||
<EuiPageTemplate restrictWidth="1000px"> | ||
<EuiPageTemplate.Header> | ||
<EuiTitle size="l"> | ||
<h1> | ||
<FormattedMessage id="assetInventory.helloWorldText" defaultMessage="Inventory" /> | ||
</h1> | ||
</EuiTitle> | ||
</EuiPageTemplate.Header> | ||
<EuiPageTemplate.Section /> | ||
</EuiPageTemplate> | ||
</> | ||
</I18nProvider> | ||
</Router> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
* 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 { AssetInventoryPlugin } from './plugin'; | ||
|
||
// This exports static code and TypeScript types, | ||
// as well as, Kibana Platform `plugin()` initializer. | ||
export function plugin() { | ||
return new AssetInventoryPlugin(); | ||
} | ||
export type { AssetInventoryPluginSetup, AssetInventoryPluginStart } from './types'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* 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 { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; | ||
import type { | ||
AssetInventoryPluginSetup, | ||
AssetInventoryPluginStart, | ||
AppPluginStartDependencies, | ||
} from './types'; | ||
|
||
export class AssetInventoryPlugin | ||
implements Plugin<AssetInventoryPluginSetup, AssetInventoryPluginStart> | ||
{ | ||
public setup(core: CoreSetup): AssetInventoryPluginSetup { | ||
return {}; | ||
} | ||
public start( | ||
coreStart: CoreStart, | ||
depsStart: AppPluginStartDependencies | ||
): AssetInventoryPluginStart { | ||
return { | ||
getAssetInventoryPage: async (params: AppMountParameters) => { | ||
// Load application bundle | ||
const { renderApp } = await import('./application'); | ||
// Render the application | ||
return renderApp(coreStart, depsStart as AppPluginStartDependencies, params); | ||
}, | ||
}; | ||
} | ||
|
||
public stop() {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
export interface AssetInventoryPluginSetup {} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
export interface AssetInventoryPluginStart {} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
export interface AppPluginStartDependencies {} |
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. This file was copy-pasted from 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. IMO it would be better to add this when you know you need it so it is not forgotten. 🤔 Asset Inventory likely needs transformers, however, are the changes for asset inventory minimal? Maybe we should consider creating a shared transformer package 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.
I think that's a great idea, the transform methods can be adapted to be more general, and we can also leverage our team package where we see fit. If you prefer that can be done on a follow-up PR, we can keep this file for now and create a follow-up Github issue to move to the shared package. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/* | ||
* 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 { transformError } from '@kbn/securitysolution-es-utils'; | ||
import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; | ||
import type { ElasticsearchClient, Logger } from '@kbn/core/server'; | ||
import { errors } from '@elastic/elasticsearch'; | ||
|
||
// TODO: Move transforms to integration package | ||
export const initializeTransforms = async ( | ||
esClient: ElasticsearchClient, | ||
logger: Logger | ||
): Promise<void> => { | ||
// Deletes old assets from previous versions as part of upgrade process | ||
await deletePreviousTransformsVersions(esClient, logger); | ||
// TODO initialize transforms here | ||
// await initializeTransform(esClient, <TRANSFORM_HERE>, logger); | ||
Comment on lines
+19
to
+20
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. NOTE: Placeholder for transforms initialization. Please let me know if this is enough or if I should uncomment the line and add some specific transforms |
||
}; | ||
|
||
export const initializeTransform = async ( | ||
esClient: ElasticsearchClient, | ||
transform: TransformPutTransformRequest, | ||
logger: Logger | ||
) => { | ||
const success = await createTransformIfNotExists(esClient, transform, logger); | ||
|
||
if (success) { | ||
await startTransformIfNotStarted(esClient, transform.transform_id, logger); | ||
} | ||
}; | ||
|
||
/** | ||
* Checks if a transform exists, And if not creates it | ||
* | ||
* @param transform - the transform to create. If a transform with the same transform_id already exists, nothing is created or updated. | ||
* | ||
* @return true if the transform exits or created, false otherwise. | ||
*/ | ||
export const createTransformIfNotExists = async ( | ||
esClient: ElasticsearchClient, | ||
transform: TransformPutTransformRequest, | ||
logger: Logger | ||
) => { | ||
try { | ||
await esClient.transform.getTransform({ | ||
transform_id: transform.transform_id, | ||
}); | ||
return true; | ||
} catch (existErr) { | ||
const existError = transformError(existErr); | ||
if (existError.statusCode === 404) { | ||
try { | ||
await esClient.transform.putTransform(transform); | ||
return true; | ||
} catch (createErr) { | ||
const createError = transformError(createErr); | ||
logger.error( | ||
`Failed to create transform ${transform.transform_id}: ${createError.message}` | ||
); | ||
} | ||
} else { | ||
logger.error( | ||
`Failed to check if transform ${transform.transform_id} exists: ${existError.message}` | ||
); | ||
} | ||
} | ||
return false; | ||
}; | ||
|
||
export const startTransformIfNotStarted = async ( | ||
esClient: ElasticsearchClient, | ||
transformId: string, | ||
logger: Logger | ||
) => { | ||
try { | ||
const transformStats = await esClient.transform.getTransformStats({ | ||
transform_id: transformId, | ||
}); | ||
|
||
if (transformStats.count <= 0) { | ||
logger.error(`Failed starting transform ${transformId}: couldn't find transform`); | ||
return; | ||
} | ||
|
||
const fetchedTransformStats = transformStats.transforms[0]; | ||
|
||
// trying to restart the transform in case it comes to a full stop or failure | ||
if (fetchedTransformStats.state === 'stopped' || fetchedTransformStats.state === 'failed') { | ||
try { | ||
return await esClient.transform.startTransform({ transform_id: transformId }); | ||
} catch (startErr) { | ||
const startError = transformError(startErr); | ||
logger.error( | ||
`Failed to start transform ${transformId}. Transform State: Transform State: ${fetchedTransformStats.state}. Error: ${startError.message}` | ||
); | ||
} | ||
} | ||
|
||
if (fetchedTransformStats.state === 'stopping' || fetchedTransformStats.state === 'aborting') { | ||
logger.error( | ||
`Not starting transform ${transformId} since it's state is: ${fetchedTransformStats.state}` | ||
); | ||
} | ||
} catch (statsErr) { | ||
const statsError = transformError(statsErr); | ||
logger.error(`Failed to check if transform ${transformId} is started: ${statsError.message}`); | ||
} | ||
}; | ||
|
||
const deletePreviousTransformsVersions = async (esClient: ElasticsearchClient, logger: Logger) => { | ||
// TODO Concat all deprecated transforms versions | ||
const deprecatedTransforms: string[] = []; | ||
|
||
for (const transform of deprecatedTransforms) { | ||
const response = await deleteTransformSafe(esClient, logger, transform); | ||
if (response) return; | ||
} | ||
}; | ||
|
||
const deleteTransformSafe = async ( | ||
esClient: ElasticsearchClient, | ||
logger: Logger, | ||
name: string | ||
): Promise<boolean> => { | ||
try { | ||
await esClient.transform.deleteTransform({ transform_id: name, force: true }); | ||
logger.info(`Deleted transform successfully [Name: ${name}]`); | ||
return true; | ||
} catch (e) { | ||
if (e instanceof errors.ResponseError && e.statusCode === 404) { | ||
logger.trace(`Transform not exists [Name: ${name}]`); | ||
return false; | ||
} else { | ||
logger.error(`Failed to delete transform [Name: ${name}]`); | ||
logger.error(e); | ||
return false; | ||
} | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* 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 { PluginInitializerContext } from '@kbn/core/server'; | ||
|
||
// This exports static code and TypeScript types, | ||
// as well as, Kibana Platform `plugin()` initializer. | ||
|
||
export async function plugin(initializerContext: PluginInitializerContext) { | ||
const { AssetInventoryPlugin } = await import('./plugin'); | ||
return new AssetInventoryPlugin(initializerContext); | ||
} | ||
|
||
export type { AssetInventoryPluginSetup, AssetInventoryPluginStart } from './types'; |
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.
Why are we restricting the width to
1000px
?🤔
Should we use break points here?https://eui.elastic.co/#/theming/breakpoints/values
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.
This was auto-generated by the script. I agree we should change it but the UI is not relevant at this point. We just wanted to have an empty plugin to start working upon it