diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 9b2b440b28f34..f5c2ed37db761 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -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
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index 8e8ee80ff81be..00841c869ef4f 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -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
diff --git a/package.json b/package.json
index bdad42ff86bef..f6c3330adad25 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 6f4f35cd72600..b1b4cb38d3f19 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -5,6 +5,7 @@ pageLoadAssetSize:
aiops: 17680
alerting: 106936
apm: 64385
+ assetInventory: 18478
banners: 17946
bfetch: 22837
canvas: 29355
diff --git a/tsconfig.base.json b/tsconfig.base.json
index ed8b402e2d179..c690ecf1a7f93 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -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"],
diff --git a/x-pack/plugins/asset_inventory/README.md b/x-pack/plugins/asset_inventory/README.md
new file mode 100755
index 0000000000000..e1dd9d4ac8900
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/README.md
@@ -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
diff --git a/x-pack/plugins/asset_inventory/common/index.ts b/x-pack/plugins/asset_inventory/common/index.ts
new file mode 100644
index 0000000000000..9f5311be877cc
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/common/index.ts
@@ -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';
diff --git a/x-pack/plugins/asset_inventory/kibana.jsonc b/x-pack/plugins/asset_inventory/kibana.jsonc
new file mode 100644
index 0000000000000..dbb813be7aa4e
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/kibana.jsonc
@@ -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": []
+ }
+}
diff --git a/x-pack/plugins/asset_inventory/package.json b/x-pack/plugins/asset_inventory/package.json
new file mode 100644
index 0000000000000..7abfb7bf63378
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/package.json
@@ -0,0 +1,7 @@
+{
+ "author": "Elastic",
+ "name": "@kbn/asset-inventory-plugin",
+ "version": "1.0.0",
+ "private": true,
+ "license": "Elastic License 2.0"
+}
diff --git a/x-pack/plugins/asset_inventory/public/application.tsx b/x-pack/plugins/asset_inventory/public/application.tsx
new file mode 100644
index 0000000000000..f442f01d17f7c
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/public/application.tsx
@@ -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(
+ ,
+ element
+ );
+
+ return () => ReactDOM.unmountComponentAtNode(element);
+};
diff --git a/x-pack/plugins/asset_inventory/public/components/app.tsx b/x-pack/plugins/asset_inventory/public/components/app.tsx
new file mode 100644
index 0000000000000..924091034353b
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/public/components/app.tsx
@@ -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 (
+
+
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+
+
+ );
+};
diff --git a/x-pack/plugins/asset_inventory/public/index.ts b/x-pack/plugins/asset_inventory/public/index.ts
new file mode 100644
index 0000000000000..269cab2ad181a
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/public/index.ts
@@ -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';
diff --git a/x-pack/plugins/asset_inventory/public/plugin.ts b/x-pack/plugins/asset_inventory/public/plugin.ts
new file mode 100644
index 0000000000000..fd2841f5b2335
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/public/plugin.ts
@@ -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
+{
+ 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() {}
+}
diff --git a/x-pack/plugins/asset_inventory/public/types.ts b/x-pack/plugins/asset_inventory/public/types.ts
new file mode 100644
index 0000000000000..a551b4d231c3d
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/public/types.ts
@@ -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 {}
diff --git a/x-pack/plugins/asset_inventory/server/create_transforms/create_transforms.ts b/x-pack/plugins/asset_inventory/server/create_transforms/create_transforms.ts
new file mode 100644
index 0000000000000..0d3d0e8f8e0c4
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/server/create_transforms/create_transforms.ts
@@ -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 => {
+ // Deletes old assets from previous versions as part of upgrade process
+ await deletePreviousTransformsVersions(esClient, logger);
+ // TODO initialize transforms here
+ // await initializeTransform(esClient, , 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 => {
+ 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;
+ }
+ }
+};
diff --git a/x-pack/plugins/asset_inventory/server/index.ts b/x-pack/plugins/asset_inventory/server/index.ts
new file mode 100644
index 0000000000000..6306618078898
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/server/index.ts
@@ -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';
diff --git a/x-pack/plugins/asset_inventory/server/plugin.ts b/x-pack/plugins/asset_inventory/server/plugin.ts
new file mode 100644
index 0000000000000..3f35991c379d5
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/server/plugin.ts
@@ -0,0 +1,63 @@
+/*
+ * 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,
+ CoreSetup,
+ CoreStart,
+ Plugin,
+ Logger,
+} from '@kbn/core/server';
+
+import type { AssetInventoryPluginSetup, AssetInventoryPluginStart } from './types';
+import { defineRoutes } from './routes';
+// TODO Uncomment this line when initialize() is enabled
+// import { initializeTransforms } from './create_transforms/create_transforms';
+
+export class AssetInventoryPlugin
+ implements Plugin
+{
+ private readonly logger: Logger;
+
+ // TODO Uncomment this line when initialize() is enabled
+ // private isInitialized: boolean = false;
+
+ constructor(initializerContext: PluginInitializerContext) {
+ this.logger = initializerContext.logger.get();
+ }
+
+ public setup(core: CoreSetup) {
+ this.logger.debug('assetInventory: Setup');
+ const router = core.http.createRouter();
+
+ // Register server side APIs
+ defineRoutes(router);
+
+ return {};
+ }
+
+ public start(core: CoreStart) {
+ this.logger.debug('assetInventory: Started');
+
+ // TODO Invoke initialize() when it's due
+ // this.initialize(core).catch(() => {});
+
+ return {};
+ }
+
+ public stop() {}
+
+ /**
+ * Initialization is idempotent and required for (re)creating indices and transforms.
+ */
+ // TODO Uncomment these lines when initialize() is enabled
+ // async initialize(core: CoreStart): Promise {
+ // this.logger.debug('initialize');
+ // const esClient = core.elasticsearch.client.asInternalUser;
+ // await initializeTransforms(esClient, this.logger);
+ // this.isInitialized = true;
+ // }
+}
diff --git a/x-pack/plugins/asset_inventory/server/routes/index.ts b/x-pack/plugins/asset_inventory/server/routes/index.ts
new file mode 100644
index 0000000000000..f577bfa19f719
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/server/routes/index.ts
@@ -0,0 +1,23 @@
+/*
+ * 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 { IRouter } from '@kbn/core/server';
+
+export function defineRoutes(router: IRouter) {
+ router.get(
+ {
+ path: '/api/asset_inventory/example',
+ validate: false,
+ },
+ async (context, request, response) => {
+ return response.ok({
+ body: {
+ time: new Date().toISOString(),
+ },
+ });
+ }
+ );
+}
diff --git a/x-pack/plugins/asset_inventory/server/types.ts b/x-pack/plugins/asset_inventory/server/types.ts
new file mode 100644
index 0000000000000..1d9380e8514f8
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/server/types.ts
@@ -0,0 +1,10 @@
+/*
+ * 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 {}
diff --git a/x-pack/plugins/asset_inventory/tsconfig.json b/x-pack/plugins/asset_inventory/tsconfig.json
new file mode 100644
index 0000000000000..dc669eb3a6943
--- /dev/null
+++ b/x-pack/plugins/asset_inventory/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "target/types"
+ },
+ "include": [
+ "common/**/*.ts",
+ "common/**/*.json",
+ "public/**/*.ts",
+ "public/**/*.tsx",
+ "public/**/*.json",
+ "server/**/*.ts",
+ "server/**/*.json",
+ "../../../typings/**/*"
+ ],
+ "exclude": ["target/**/*"],
+ "kbn_references": [
+ "@kbn/core",
+ "@kbn/i18n-react",
+ "@kbn/shared-ux-router",
+ "@kbn/securitysolution-es-utils"
+ ]
+}
diff --git a/yarn.lock b/yarn.lock
index eb4fac92cdd26..90429bc21f489 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4000,6 +4000,10 @@
version "0.0.0"
uid ""
+"@kbn/asset-inventory-plugin@link:x-pack/plugins/asset_inventory":
+ version "0.0.0"
+ uid ""
+
"@kbn/audit-log-plugin@link:x-pack/test/security_api_integration/plugins/audit_log":
version "0.0.0"
uid ""