Repository: elastic/kibana
Pull request #214002: Fix checkPrivilege to query with indicies
Author: login: bryce-b
Labels: name: release_note:fix
name: Team:obs-ux-infra_services - DEPRECATED
description: DEPRECATED - Use Team:obs-presentation.
name: v8.18.0
name: v9.1.0
name: v8.19.0
Created: 2025-03-11T19:59:46Z | Merged: 2025-03-14T16:10:06Z

## Summary
`apmIndices` has the form: 
```
  "transaction": "traces-apm*,apm-*,traces-*.otel-*",
  "span": "traces-apm*,apm-*,traces-*.otel-*",
  "error": "logs-apm*,apm-*,logs-*.otel-*",
  "metric": "metrics-apm*,apm-*,metrics-*.otel-*",
  "onboarding": "apm-*",
  "sourcemap": "apm-*"
```

`mapValues(apmIndices, () => ['read']),` was producing an object that looked like : 
```
{
  "transaction": [
    "read"
  ],
  "span": [
    "read"
  ],
  "error": [
    "read"
  ],
  "metric": [
    "read"
  ],
  "onboarding": [
    "read"
  ],
  "sourcemap": [
    "read"
  ]
}
```

It seems this ought to check privileges on the actual indices, so this PR restructures the `index` parameter to this :
 
```
{
  "traces-apm*": [
    "read"
  ],
  "apm-*": [
    "read"
  ],
  "traces-*.otel-*": [
    "read"
  ],
  "logs-apm*": [
    "read"
  ],
  "logs-*.otel-*": [
    "read"
  ],
  "metrics-apm*": [
    "read"
  ],
  "metrics-*.otel-*": [
    "read"
  ]
```

### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.



- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios

- [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)




<!--ONMERGE {"backportTargets":["8.16","8.17","8.18","8.x"]} ONMERGE-->


Diff:
diff --git a/x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.test.ts b/x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.test.ts
new file mode 100644
index 0000000000000..2ff09bdfd66ff
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.test.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 { convertIndiciesToPrivilege } from './check_privileges';
+
+describe('test CheckPrivileges', () => {
+  it('shourp properly convert apmIndices to privilege format', () => {
+    const apmIndices = {
+      transaction: 'traces-apm*,apm-*,traces-*.otel-*',
+      span: 'traces-apm*,apm-*,traces-*.otel-*',
+      error: 'logs-apm*,apm-*,logs-*.otel-*',
+      metric: 'metrics-apm*,apm-*,metrics-*.otel-*',
+      onboarding: 'apm-*',
+      sourcemap: 'apm-*',
+    };
+    const expectedFormat = {
+      'traces-apm*': ['read'],
+      'apm-*': ['read'],
+      'traces-*.otel-*': ['read'],
+      'logs-apm*': ['read'],
+      'logs-*.otel-*': ['read'],
+      'metrics-apm*': ['read'],
+      'metrics-*.otel-*': ['read'],
+    };
+    const result = convertIndiciesToPrivilege(apmIndices);
+    expect(expectedFormat).toEqual(result);
+  });
+});
diff --git a/x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.ts b/x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.ts
index 4a665693b463b..be5ef6d8dcbfc 100644
--- a/x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.ts
+++ b/x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.ts
@@ -7,13 +7,21 @@
 
 import type { KibanaRequest } from '@kbn/core-http-server';
 import type { SecurityPluginStart } from '@kbn/security-plugin-types-server';
-import { mapValues } from 'lodash';
 import type { APMIndices } from '..';
-
 export interface ApmDataAccessPrivilegesCheck {
   request: KibanaRequest;
   security?: SecurityPluginStart;
-  getApmIndices: () => Promise<APMIndices>;
+  getApmIndices: (request: KibanaRequest) => Promise<APMIndices>;
+}
+
+export function convertIndiciesToPrivilege(apmIndices: APMIndices) {
+  return Object.values(apmIndices)
+    .map((value) => value.split(','))
+    .flat()
+    .reduce<Record<string, string[]>>((obj, item, index) => {
+      obj[item] = ['read'];
+      return obj;
+    }, {});
 }
 
 export async function checkPrivileges({
@@ -27,16 +35,15 @@ export async function checkPrivileges({
   }
 
   const [apmIndices, checkPrivilegesFn] = await Promise.all([
-    getApmIndices(),
+    getApmIndices(request),
     authorization.checkPrivilegesDynamicallyWithRequest(request),
   ]);
 
   const { hasAllRequested } = await checkPrivilegesFn({
     elasticsearch: {
       cluster: [],
-      index: mapValues(apmIndices, () => ['read']),
+      index: convertIndiciesToPrivilege(apmIndices),
     },
   });
-
   return hasAllRequested;
 }
diff --git a/x-pack/solutions/observability/plugins/apm_data_access/server/plugin.ts b/x-pack/solutions/observability/plugins/apm_data_access/server/plugin.ts
index ce449ea085aa0..d792f893e23d6 100644
--- a/x-pack/solutions/observability/plugins/apm_data_access/server/plugin.ts
+++ b/x-pack/solutions/observability/plugins/apm_data_access/server/plugin.ts
@@ -12,6 +12,7 @@ import type {
   Plugin,
   SavedObjectsClientContract,
   Logger,
+  KibanaRequest,
 } from '@kbn/core/server';
 import type { APMDataAccessConfig } from '.';
 import type {
@@ -64,8 +65,8 @@ export class ApmDataAccessPlugin
       this.logger.error(e);
     });
 
-    const getApmIndicesWithInternalUserFn = async () => {
-      const soClient = core.savedObjects.createInternalRepository();
+    const getApmIndicesWithInternalUserFn = async (request: KibanaRequest) => {
+      const soClient = core.savedObjects.getScopedClient(request);
       return this.getApmIndices(soClient);
     };
 


Reviews:
user: login: cauemarcondes
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T12:12:17Z
user: login: cauemarcondes
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T12:12:52Z
user: login: cauemarcondes
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T12:14:18Z
user: login: crespocarlos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T14:59:57Z
user: login: bryce-b
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T18:36:23Z
user: login: cauemarcondes
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T11:59:46Z
user: login: dgieselaar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T13:07:42Z
user: login: crespocarlos
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T13:40:50Z

Review comments:
diff_hunk: @@ -34,9 +32,14 @@ export async function checkPrivileges({
   const { hasAllRequested } = await checkPrivilegesFn({
     elasticsearch: {
       cluster: [],
-      index: mapValues(apmIndices, () => ['read']),
+      index: Object.values(apmIndices)
+        .map((value) => value.split(','))
+        .flat()
+        .reduce((obj, item, index) => {
+          obj[item] = ['read'];
+          return obj;
+        }, {} as Record<string, string[]>),
path: x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.ts
user: login: cauemarcondes
body: ```suggestion
      index: Object.values(apmIndices)
        .map((value) => value.split(','))
        .flat()
        .reduce<Record<string, string[]>>((obj, item, index) => {
          obj[item] = ['read'];
          return obj;
        }, {}),
```
created_at: 2025-03-12T12:12:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,9 +32,14 @@ export async function checkPrivileges({
   const { hasAllRequested } = await checkPrivilegesFn({
     elasticsearch: {
       cluster: [],
-      index: mapValues(apmIndices, () => ['read']),
+      index: Object.values(apmIndices)
path: x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.ts
user: login: cauemarcondes
body: @bryce-b can you extract this part to a function and unit test it, please?
created_at: 2025-03-12T12:12:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,9 +32,14 @@ export async function checkPrivileges({
   const { hasAllRequested } = await checkPrivilegesFn({
path: x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.ts
user: login: cauemarcondes
body: Do you know if behind the scene the `checkPrivilegesFn` calls `GET /_security/user/_has_privileges`? It's worth checking it before moving forward with this change.
created_at: 2025-03-12T12:14:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,9 +32,14 @@ export async function checkPrivileges({
   const { hasAllRequested } = await checkPrivilegesFn({
path: x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.ts
user: login: crespocarlos
body: It does. amongst other calls

<img width="574" alt="image" src="https://github.com/user-attachments/assets/a1d28f2b-4ccb-477b-840c-d589e13e3891" />

created_at: 2025-03-12T14:59:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,9 +32,14 @@ export async function checkPrivileges({
   const { hasAllRequested } = await checkPrivilegesFn({
path: x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.ts
user: login: bryce-b
body: what are the implications of this? 
created_at: 2025-03-12T18:36:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,9 +32,14 @@ export async function checkPrivileges({
   const { hasAllRequested } = await checkPrivilegesFn({
path: x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.ts
user: login: cauemarcondes
body: check this up:
<img width="1021" alt="Screenshot 2025-03-13 at 08 58 26" src="https://github.com/user-attachments/assets/264c8157-9413-4632-83ba-fd343dde5a81" />

It returns true for any index name we pass to this API. 🤷🏻‍♂️
created_at: 2025-03-13T11:59:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,9 +32,14 @@ export async function checkPrivileges({
   const { hasAllRequested } = await checkPrivilegesFn({
path: x-pack/solutions/observability/plugins/apm_data_access/server/lib/check_privileges.ts
user: login: dgieselaar
body: It returns true for any (potential) index or data stream name that the current user has access to
created_at: 2025-03-13T13:07:42Z
author_association: CONTRIBUTOR

PR comments:
user: login: crespocarlos
created_at: 2025-03-12T08:44:47Z
body: Hey @bryce-b , there is another problem that we could fix in this PR. 

This function https://github.com/elastic/kibana/blob/main/x-pack/solutions/observability/plugins/apm_data_access/server/plugin.ts#L67 needs to be changed so that the SO is space aware. e.g:

```ts
 const getApmIndicesWithInternalUserFn = async (request: KibanaRequest) => {
  const soClient = core.savedObjects.getScopedClient(request);
  return this.getApmIndices(soClient);
};
```


author_association: CONTRIBUTOR
user: login: elasticmachine
created_at: 2025-03-12T18:36:39Z
body: Pinging @elastic/obs-ux-infra_services-team (Team:obs-ux-infra_services)
author_association: CONTRIBUTOR
user: login: kibanamachine
created_at: 2025-03-14T16:10:24Z
body: Starting backport for target branches: 8.16, 8.17, 8.18, 8.x

https://github.com/elastic/kibana/actions/runs/13860690062
author_association: CONTRIBUTOR
user: login: kibanamachine
created_at: 2025-03-14T16:22:41Z
body: ## 💔 Some backports could not be created

| Status | Branch | Result |
|:------:|:------:|:------|
|❌|8.16|Backport failed because of merge conflicts|
|❌|8.17|Backport failed because of merge conflicts|
|✅|8.18|[<img src="https://img.shields.io/github/pulls/detail/state/elastic/kibana/214610">](https://github.com/elastic/kibana/pull/214610)|
|✅|8.x|[<img src="https://img.shields.io/github/pulls/detail/state/elastic/kibana/214611">](https://github.com/elastic/kibana/pull/214611)|

Note: Successful backport PRs will be merged automatically after passing CI.

### Manual backport
To create the backport manually run:
```
node scripts/backport --pr 214002
```

### Questions ?
Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport)

<!--- Backport version: 9.6.6 -->
author_association: CONTRIBUTOR