From ba84602455671f0f6175bbc0fd2e8f302c60bbe6 Mon Sep 17 00:00:00 2001
From: Tomasz Ciecierski <ciecierskitomek@gmail.com>
Date: Mon, 23 May 2022 13:33:20 +0200
Subject: [PATCH] [Osquery] Change prebuilt saved queries to include prebuilt
 flag (#132651)

---
 .../routes/saved_queries/edit/index.tsx       |  2 +-
 .../saved_query/delete_saved_query_route.ts   |  9 +++-
 .../saved_query/find_saved_query_route.ts     |  9 +++-
 .../server/routes/saved_query/index.ts        |  6 +--
 .../saved_query/read_saved_query_route.ts     |  7 ++-
 .../saved_query/update_saved_query_route.ts   |  7 +++
 .../server/routes/saved_query/utils.ts        | 54 +++++++++++++++++++
 7 files changed, 87 insertions(+), 7 deletions(-)
 create mode 100644 x-pack/plugins/osquery/server/routes/saved_query/utils.ts

diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx
index 94b1f092e1ede..cb7a95b4271e7 100644
--- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx
+++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx
@@ -44,7 +44,7 @@ const EditSavedQueryPageComponent = () => {
   useBreadcrumbs('saved_query_edit', { savedQueryName: savedQueryDetails?.attributes?.id ?? '' });
 
   const elasticPrebuiltQuery = useMemo(
-    () => savedQueryDetails?.attributes?.version,
+    () => savedQueryDetails?.attributes?.prebuilt,
     [savedQueryDetails]
   );
   const viewMode = useMemo(
diff --git a/x-pack/plugins/osquery/server/routes/saved_query/delete_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/delete_saved_query_route.ts
index c2a2ad7fa8619..a27c4a0953098 100644
--- a/x-pack/plugins/osquery/server/routes/saved_query/delete_saved_query_route.ts
+++ b/x-pack/plugins/osquery/server/routes/saved_query/delete_saved_query_route.ts
@@ -9,8 +9,10 @@ import { schema } from '@kbn/config-schema';
 import { IRouter } from '@kbn/core/server';
 import { PLUGIN_ID } from '../../../common';
 import { savedQuerySavedObjectType } from '../../../common/types';
+import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
+import { isSavedQueryPrebuilt } from './utils';
 
-export const deleteSavedQueryRoute = (router: IRouter) => {
+export const deleteSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
   router.delete(
     {
       path: '/internal/osquery/saved_query/{id}',
@@ -25,6 +27,11 @@ export const deleteSavedQueryRoute = (router: IRouter) => {
       const coreContext = await context.core;
       const savedObjectsClient = coreContext.savedObjects.client;
 
+      const isPrebuilt = await isSavedQueryPrebuilt(osqueryContext, request.params.id);
+      if (isPrebuilt) {
+        return response.conflict({ body: `Elastic prebuilt Saved query cannot be deleted.` });
+      }
+
       await savedObjectsClient.delete(savedQuerySavedObjectType, request.params.id, {
         refresh: 'wait_for',
       });
diff --git a/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts
index a2b85dbf539d9..abf62ca782daa 100644
--- a/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts
+++ b/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts
@@ -7,11 +7,14 @@
 
 import { schema } from '@kbn/config-schema';
 import { IRouter } from '@kbn/core/server';
+
+import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
 import { PLUGIN_ID } from '../../../common';
 import { savedQuerySavedObjectType } from '../../../common/types';
 import { convertECSMappingToObject } from '../utils';
+import { getInstalledSavedQueriesMap } from './utils';
 
-export const findSavedQueryRoute = (router: IRouter) => {
+export const findSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
   router.get(
     {
       path: '/internal/osquery/saved_query',
@@ -34,6 +37,7 @@ export const findSavedQueryRoute = (router: IRouter) => {
 
       const savedQueries = await savedObjectsClient.find<{
         ecs_mapping: Array<{ field: string; value: string }>;
+        prebuilt: boolean;
       }>({
         type: savedQuerySavedObjectType,
         page: parseInt(request.query.pageIndex ?? '0', 10) + 1,
@@ -43,10 +47,13 @@ export const findSavedQueryRoute = (router: IRouter) => {
         sortOrder: request.query.sortDirection ?? 'desc',
       });
 
+      const prebuiltSavedQueriesMap = await getInstalledSavedQueriesMap(osqueryContext);
       const savedObjects = savedQueries.saved_objects.map((savedObject) => {
         // eslint-disable-next-line @typescript-eslint/naming-convention
         const ecs_mapping = savedObject.attributes.ecs_mapping;
 
+        savedObject.attributes.prebuilt = !!prebuiltSavedQueriesMap[savedObject.id];
+
         if (ecs_mapping) {
           // @ts-expect-error update types
           savedObject.attributes.ecs_mapping = convertECSMappingToObject(ecs_mapping);
diff --git a/x-pack/plugins/osquery/server/routes/saved_query/index.ts b/x-pack/plugins/osquery/server/routes/saved_query/index.ts
index e0bf4f622c42c..025199dcba6b6 100644
--- a/x-pack/plugins/osquery/server/routes/saved_query/index.ts
+++ b/x-pack/plugins/osquery/server/routes/saved_query/index.ts
@@ -16,8 +16,8 @@ import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
 
 export const initSavedQueryRoutes = (router: IRouter, context: OsqueryAppContext) => {
   createSavedQueryRoute(router, context);
-  deleteSavedQueryRoute(router);
-  findSavedQueryRoute(router);
-  readSavedQueryRoute(router);
+  deleteSavedQueryRoute(router, context);
+  findSavedQueryRoute(router, context);
+  readSavedQueryRoute(router, context);
   updateSavedQueryRoute(router, context);
 };
diff --git a/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts
index 1c206464d1f65..d1627d220682a 100644
--- a/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts
+++ b/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts
@@ -7,11 +7,13 @@
 
 import { schema } from '@kbn/config-schema';
 import { IRouter } from '@kbn/core/server';
+import { isSavedQueryPrebuilt } from './utils';
+import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
 import { PLUGIN_ID } from '../../../common';
 import { savedQuerySavedObjectType } from '../../../common/types';
 import { convertECSMappingToObject } from '../utils';
 
-export const readSavedQueryRoute = (router: IRouter) => {
+export const readSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
   router.get(
     {
       path: '/internal/osquery/saved_query/{id}',
@@ -28,6 +30,7 @@ export const readSavedQueryRoute = (router: IRouter) => {
 
       const savedQuery = await savedObjectsClient.get<{
         ecs_mapping: Array<{ key: string; value: Record<string, object> }>;
+        prebuilt: boolean;
       }>(savedQuerySavedObjectType, request.params.id);
 
       if (savedQuery.attributes.ecs_mapping) {
@@ -37,6 +40,8 @@ export const readSavedQueryRoute = (router: IRouter) => {
         );
       }
 
+      savedQuery.attributes.prebuilt = await isSavedQueryPrebuilt(osqueryContext, savedQuery.id);
+
       return response.ok({
         body: savedQuery,
       });
diff --git a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts
index 1d2bf153afd7f..e2686868b7eff 100644
--- a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts
+++ b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts
@@ -9,6 +9,7 @@ import { filter } from 'lodash';
 import { schema } from '@kbn/config-schema';
 
 import { IRouter } from '@kbn/core/server';
+import { isSavedQueryPrebuilt } from './utils';
 import { PLUGIN_ID } from '../../../common';
 import { savedQuerySavedObjectType } from '../../../common/types';
 import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
@@ -63,6 +64,12 @@ export const updateSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp
         ecs_mapping,
       } = request.body;
 
+      const isPrebuilt = await isSavedQueryPrebuilt(osqueryContext, request.params.id);
+
+      if (isPrebuilt) {
+        return response.conflict({ body: `Elastic prebuilt Saved query cannot be updated.` });
+      }
+
       const conflictingEntries = await savedObjectsClient.find<{ id: string }>({
         type: savedQuerySavedObjectType,
         filter: `${savedQuerySavedObjectType}.attributes.id: "${id}"`,
diff --git a/x-pack/plugins/osquery/server/routes/saved_query/utils.ts b/x-pack/plugins/osquery/server/routes/saved_query/utils.ts
new file mode 100644
index 0000000000000..d99d5b70f0dab
--- /dev/null
+++ b/x-pack/plugins/osquery/server/routes/saved_query/utils.ts
@@ -0,0 +1,54 @@
+/*
+ * 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 { find, reduce } from 'lodash';
+import { KibanaAssetReference } from '@kbn/fleet-plugin/common';
+
+import { OSQUERY_INTEGRATION_NAME } from '../../../common';
+import { savedQuerySavedObjectType } from '../../../common/types';
+import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
+
+const getInstallation = async (osqueryContext: OsqueryAppContext) =>
+  await osqueryContext.service
+    .getPackageService()
+    ?.asInternalUser?.getInstallation(OSQUERY_INTEGRATION_NAME);
+
+export const getInstalledSavedQueriesMap = async (osqueryContext: OsqueryAppContext) => {
+  const installation = await getInstallation(osqueryContext);
+  if (installation) {
+    return reduce(
+      installation.installed_kibana,
+      // @ts-expect-error not sure why it shouts, but still it's properly typed
+      (acc: Record<string, KibanaAssetReference>, item: KibanaAssetReference) => {
+        if (item.type === savedQuerySavedObjectType) {
+          return { ...acc, [item.id]: item };
+        }
+      },
+      {}
+    );
+  }
+
+  return {};
+};
+
+export const isSavedQueryPrebuilt = async (
+  osqueryContext: OsqueryAppContext,
+  savedQueryId: string
+) => {
+  const installation = await getInstallation(osqueryContext);
+
+  if (installation) {
+    const installationSavedQueries = find(
+      installation.installed_kibana,
+      (item) => item.type === savedQuerySavedObjectType && item.id === savedQueryId
+    );
+
+    return !!installationSavedQueries;
+  }
+
+  return false;
+};