Skip to content

Commit

Permalink
Create the "Asset Inventory" Kibana Plugin (#202291)
Browse files Browse the repository at this point in the history
## Summary

Closes #201704.

Create an empty "Asset Inventory" plugin with the minimal boilerplate
required to set it up, install it and run it on Kibana with a blank
slate.

I generated the files using the `node scripts/generate_plugin <NAME>`
script as per [this documentation
page](https://docs.elastic.dev/kibana-dev-docs/getting-started/hello-world-app#2-option-2---use-the-automatic-plugin-generator).

### Screenshots


<details><summary>Main page (Sample page)</summary>
<img width="2498" alt="Screenshot 2024-11-29 at 14 20 57"
src="https://github.com/user-attachments/assets/9d8a3751-519b-4661-bc90-cbb1e836b111">
</details> 

### Implementation details

- [x] Generated a new Kibana plugin with minimal boilerplate and zero
dependencies
- [x] Use [Cloud Security
Posture](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture)
plugin as configuration reference
- [x] Render "Inventory" title on the main page
- [x] Ensure the plugin is properly integrated into Kibana's build and
can be loaded without errors
- [x] Place the plugin under the `x-pack/plugins` directory
- [x] Include Readme file
- [x] Introduce placeholders for initialization of pipelines and
transforms following [Cloud Security
Posture](https://github.com/elastic/kibana/blob/main/x-pack/plugins/cloud_security_posture/server/plugin.ts)
plugin initialize function.

### PR Checklist

- [ ] No docs for now
~~[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials~~
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Risks

No risks at all since this is totally green-field and will be hidden by
a feature toggle.

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Paulo Silva <[email protected]>
  • Loading branch information
3 people authored Dec 3, 2024
1 parent a3496c9 commit e5b1773
Show file tree
Hide file tree
Showing 21 changed files with 462 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,7 @@ x-pack/plugins/ai_infra/llm_tasks @elastic/appex-ai-infra
x-pack/plugins/ai_infra/product_doc_base @elastic/appex-ai-infra
x-pack/plugins/aiops @elastic/ml-ui
x-pack/plugins/alerting @elastic/response-ops
x-pack/plugins/asset_inventory @elastic/kibana-cloud-security-posture
x-pack/plugins/banners @elastic/appex-sharedux
x-pack/plugins/canvas @elastic/kibana-presentation
x-pack/plugins/cases @elastic/response-ops
Expand Down
4 changes: 4 additions & 0 deletions docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,10 @@ The plugin exposes the static DefaultEditorController class to consume.
|WARNING: Missing README.
|{kib-repo}blob/{branch}/x-pack/plugins/asset_inventory/README.md[assetInventory]
|Centralized asset inventory experience within the Elastic Security solution. A central place for users to view and manage all their assets from different environments.
|{kib-repo}blob/{branch}/x-pack/plugins/banners/README.md[banners]
|Allow to add a header banner that will be displayed on every page of the Kibana application
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
"@kbn/apm-utils": "link:packages/kbn-apm-utils",
"@kbn/app-link-test-plugin": "link:test/plugin_functional/plugins/app_link_test",
"@kbn/application-usage-test-plugin": "link:x-pack/test/usage_collection/plugins/application_usage_test",
"@kbn/asset-inventory-plugin": "link:x-pack/plugins/asset_inventory",
"@kbn/audit-log-plugin": "link:x-pack/test/security_api_integration/plugins/audit_log",
"@kbn/avc-banner": "link:packages/kbn-avc-banner",
"@kbn/banners-plugin": "link:x-pack/plugins/banners",
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pageLoadAssetSize:
aiops: 17680
alerting: 106936
apm: 64385
assetInventory: 18478
banners: 17946
bfetch: 22837
canvas: 29355
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@
"@kbn/app-link-test-plugin/*": ["test/plugin_functional/plugins/app_link_test/*"],
"@kbn/application-usage-test-plugin": ["x-pack/test/usage_collection/plugins/application_usage_test"],
"@kbn/application-usage-test-plugin/*": ["x-pack/test/usage_collection/plugins/application_usage_test/*"],
"@kbn/asset-inventory-plugin": ["x-pack/plugins/asset_inventory"],
"@kbn/asset-inventory-plugin/*": ["x-pack/plugins/asset_inventory/*"],
"@kbn/audit-log-plugin": ["x-pack/test/security_api_integration/plugins/audit_log"],
"@kbn/audit-log-plugin/*": ["x-pack/test/security_api_integration/plugins/audit_log/*"],
"@kbn/avc-banner": ["packages/kbn-avc-banner"],
Expand Down
13 changes: 13 additions & 0 deletions x-pack/plugins/asset_inventory/README.md
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
8 changes: 8 additions & 0 deletions x-pack/plugins/asset_inventory/common/index.ts
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';
17 changes: 17 additions & 0 deletions x-pack/plugins/asset_inventory/kibana.jsonc
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": []
}
}
7 changes: 7 additions & 0 deletions x-pack/plugins/asset_inventory/package.json
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"
}
24 changes: 24 additions & 0 deletions x-pack/plugins/asset_inventory/public/application.tsx
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);
};
38 changes: 38 additions & 0 deletions x-pack/plugins/asset_inventory/public/components/app.tsx
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>
);
};
14 changes: 14 additions & 0 deletions x-pack/plugins/asset_inventory/public/index.ts
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';
35 changes: 35 additions & 0 deletions x-pack/plugins/asset_inventory/public/plugin.ts
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() {}
}
15 changes: 15 additions & 0 deletions x-pack/plugins/asset_inventory/public/types.ts
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 {}
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);
};

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;
}
}
};
17 changes: 17 additions & 0 deletions x-pack/plugins/asset_inventory/server/index.ts
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';
Loading

0 comments on commit e5b1773

Please sign in to comment.