Skip to content

Commit 211a958

Browse files
authored
[7.x] Add kibana-system service account (#76449) (#76502)
This change introduces a Service Account for Kibana to use when authenticating to Elasticsearch. The Service Account with kibana service name under the elastic namespace, uses the same RoleDescriptor as the existing kibana_system built-in user and is its functional equivalent when it comes to AuthZ.
1 parent 7e4fc3d commit 211a958

File tree

9 files changed

+358
-105
lines changed

9 files changed

+358
-105
lines changed

rest-api-spec/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ testClusters.all {
2323
module ':modules:mapper-extras'
2424
}
2525

26-
2726
tasks.named("test").configure {enabled = false }
2827
tasks.named("jarHell").configure {enabled = false }
2928

x-pack/docs/en/security/authentication/service-accounts.asciidoc

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ prevents credential sharing between multiple instances of the same
1818
external service. Each instance can assume the same identity while using
1919
their own distinct service token for authentication.
2020

21-
Service accounts provide flexibility over <<built-in-users,built-in users>>
21+
Service accounts provide flexibility over <<built-in-users,built-in users>>
2222
because they:
2323

2424
* Do not rely on the <<native-realm,internal `native` realm>>, and aren't
2525
always required to rely on the `.security` index
26-
* Use a role descriptor named after the service account principal instead of
26+
* Use a role descriptor named after the service account principal instead of
2727
traditional roles
2828
* Support multiple credentials through service account tokens
2929

@@ -40,11 +40,15 @@ the format of `<namespace>/<service>`, where the `namespace` is a top-level
4040
grouping of service accounts, and `service` is the name of the service and
4141
must be unique within its namespace.
4242

43-
Service accounts are predefined in code. Currently, only one service account is available:
43+
Service accounts are predefined in code. The following service accounts are
44+
available:
4445

4546
`elastic/fleet-server`:: The service account used by the {fleet} server to
4647
communicate with {es}.
4748

49+
`elastic/kibana`:: The service account used by {kib} to communicate with
50+
{es}.
51+
4852
// tag::service-accounts-usage[]
4953
IMPORTANT: Do not attempt to use service accounts for authenticating individual
5054
users. Service accounts can only be authenticated with service tokens, which are
@@ -83,11 +87,11 @@ the bearer token in the HTTP response
8387

8488
Both of these methods create a service token with a guaranteed secret string
8589
length of `22`. The minimal, acceptable length of a secret string for a service
86-
token is `10`. If the secret string doesn't meet this minimal length,
90+
token is `10`. If the secret string doesn't meet this minimal length,
8791
authentication with {es} will fail without even checking the value of the
8892
service token.
8993

90-
Service tokens never expire. You must actively
94+
Service tokens never expire. You must actively
9195
<<security-api-delete-service-token,delete>> them if they are no longer needed.
9296

9397
[discrete]

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java

Lines changed: 77 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege;
2222
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivileges.ManageApplicationPrivileges;
2323
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
24-
import org.elasticsearch.xpack.core.security.user.KibanaUser;
24+
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
2525
import org.elasticsearch.xpack.core.security.user.UsernamesField;
2626
import org.elasticsearch.xpack.core.transform.transforms.persistence.TransformInternalIndexConstants;
2727
import org.elasticsearch.xpack.core.watcher.execution.TriggeredWatchStoreField;
@@ -125,78 +125,7 @@ private static Map<String, RoleDescriptor> initializeReservedRoles() {
125125
null, null,
126126
MetadataUtils.getDeprecatedReservedMetadata("Please use Kibana feature privileges instead"),
127127
null))
128-
.put(KibanaUser.ROLE_NAME, new RoleDescriptor(KibanaUser.ROLE_NAME,
129-
new String[] {
130-
"monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc",
131-
InvalidateApiKeyAction.NAME, "grant_api_key",
132-
GetBuiltinPrivilegesAction.NAME, "delegate_pki", GetLifecycleAction.NAME, PutLifecycleAction.NAME,
133-
// To facilitate ML UI functionality being controlled using Kibana security privileges
134-
"manage_ml",
135-
// The symbolic constant for this one is in SecurityActionMapper, so not accessible from X-Pack core
136-
"cluster:admin/analyze",
137-
// To facilitate using the file uploader functionality
138-
"monitor_text_structure",
139-
// To cancel tasks and delete async searches
140-
"cancel_task"
141-
},
142-
new RoleDescriptor.IndicesPrivileges[] {
143-
RoleDescriptor.IndicesPrivileges.builder()
144-
.indices(".kibana*", ".reporting-*").privileges("all").build(),
145-
RoleDescriptor.IndicesPrivileges.builder()
146-
.indices(".monitoring-*").privileges("read", "read_cross_cluster").build(),
147-
RoleDescriptor.IndicesPrivileges.builder()
148-
.indices(".management-beats").privileges("create_index", "read", "write").build(),
149-
// To facilitate ML UI functionality being controlled using Kibana security privileges
150-
RoleDescriptor.IndicesPrivileges.builder()
151-
.indices(".ml-anomalies*", ".ml-stats-*")
152-
.privileges("read").build(),
153-
RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*", ".ml-notifications*")
154-
.privileges("read", "write").build(),
155-
// APM agent configuration
156-
RoleDescriptor.IndicesPrivileges.builder()
157-
.indices(".apm-agent-configuration").privileges("all").build(),
158-
// APM custom link index creation
159-
RoleDescriptor.IndicesPrivileges.builder()
160-
.indices(".apm-custom-link").privileges("all").build(),
161-
// APM telemetry queries APM indices in kibana task runner
162-
RoleDescriptor.IndicesPrivileges.builder()
163-
.indices("apm-*")
164-
.privileges("read", "read_cross_cluster").build(),
165-
// Data telemetry reads mappings, metadata and stats of indices
166-
RoleDescriptor.IndicesPrivileges.builder()
167-
.indices("*")
168-
.privileges("view_index_metadata", "monitor").build(),
169-
// Endpoint diagnostic information. Kibana reads from these indices to send telemetry
170-
RoleDescriptor.IndicesPrivileges.builder()
171-
.indices(".logs-endpoint.diagnostic.collection-*")
172-
.privileges("read").build(),
173-
// Fleet Server indices. Kibana create this indice before Fleet Server use them.
174-
// Fleet Server indices. Kibana read and write to this indice to manage Elastic Agents
175-
RoleDescriptor.IndicesPrivileges.builder()
176-
.indices(".fleet*")
177-
.privileges("all").build(),
178-
// Legacy "Alerts as data" index. Kibana user will create this index.
179-
// Kibana user will read / write to these indices
180-
RoleDescriptor.IndicesPrivileges.builder()
181-
.indices(ReservedRolesStore.LEGACY_ALERTS_INDEX)
182-
.privileges("all").build(),
183-
// "Alerts as data" index. Kibana user will create this index.
184-
// Kibana user will read / write to these indices
185-
RoleDescriptor.IndicesPrivileges.builder()
186-
.indices(ReservedRolesStore.ALERTS_INDEX)
187-
.privileges("all").build(),
188-
// Endpoint / Fleet policy responses. Kibana requires read access to send telemetry
189-
RoleDescriptor.IndicesPrivileges.builder()
190-
.indices("metrics-endpoint.policy-*")
191-
.privileges("read").build(),
192-
// Endpoint metrics. Kibana requires read access to send telemetry
193-
RoleDescriptor.IndicesPrivileges.builder()
194-
.indices("metrics-endpoint.metrics-*")
195-
.privileges("read").build()
196-
},
197-
null,
198-
new ConfigurableClusterPrivilege[] { new ManageApplicationPrivileges(Collections.singleton("kibana-*")) },
199-
null, MetadataUtils.DEFAULT_RESERVED_METADATA, null))
128+
.put(KibanaSystemUser.ROLE_NAME, kibanaSystemRoleDescriptor(KibanaSystemUser.ROLE_NAME))
200129
.put("logstash_system", new RoleDescriptor("logstash_system", new String[] { "monitor", MonitoringBulkAction.NAME},
201130
null, null, MetadataUtils.DEFAULT_RESERVED_METADATA))
202131
.put("beats_admin", new RoleDescriptor("beats_admin",
@@ -442,6 +371,81 @@ private static RoleDescriptor kibanaAdminUser(String name, Map<String, Object> m
442371
null, null, metadata, null);
443372
}
444373

374+
public static RoleDescriptor kibanaSystemRoleDescriptor(String name) {
375+
return new RoleDescriptor(name,
376+
new String[] {
377+
"monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc",
378+
InvalidateApiKeyAction.NAME, "grant_api_key",
379+
GetBuiltinPrivilegesAction.NAME, "delegate_pki", GetLifecycleAction.NAME, PutLifecycleAction.NAME,
380+
// To facilitate ML UI functionality being controlled using Kibana security privileges
381+
"manage_ml",
382+
// The symbolic constant for this one is in SecurityActionMapper, so not accessible from X-Pack core
383+
"cluster:admin/analyze",
384+
// To facilitate using the file uploader functionality
385+
"monitor_text_structure",
386+
// To cancel tasks and delete async searches
387+
"cancel_task"
388+
},
389+
new RoleDescriptor.IndicesPrivileges[] {
390+
RoleDescriptor.IndicesPrivileges.builder()
391+
.indices(".kibana*", ".reporting-*").privileges("all").build(),
392+
RoleDescriptor.IndicesPrivileges.builder()
393+
.indices(".monitoring-*").privileges("read", "read_cross_cluster").build(),
394+
RoleDescriptor.IndicesPrivileges.builder()
395+
.indices(".management-beats").privileges("create_index", "read", "write").build(),
396+
// To facilitate ML UI functionality being controlled using Kibana security privileges
397+
RoleDescriptor.IndicesPrivileges.builder()
398+
.indices(".ml-anomalies*", ".ml-stats-*")
399+
.privileges("read").build(),
400+
RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*", ".ml-notifications*")
401+
.privileges("read", "write").build(),
402+
// APM agent configuration
403+
RoleDescriptor.IndicesPrivileges.builder()
404+
.indices(".apm-agent-configuration").privileges("all").build(),
405+
// APM custom link index creation
406+
RoleDescriptor.IndicesPrivileges.builder()
407+
.indices(".apm-custom-link").privileges("all").build(),
408+
// APM telemetry queries APM indices in kibana task runner
409+
RoleDescriptor.IndicesPrivileges.builder()
410+
.indices("apm-*")
411+
.privileges("read", "read_cross_cluster").build(),
412+
// Data telemetry reads mappings, metadata and stats of indices
413+
RoleDescriptor.IndicesPrivileges.builder()
414+
.indices("*")
415+
.privileges("view_index_metadata", "monitor").build(),
416+
// Endpoint diagnostic information. Kibana reads from these indices to send telemetry
417+
RoleDescriptor.IndicesPrivileges.builder()
418+
.indices(".logs-endpoint.diagnostic.collection-*")
419+
.privileges("read").build(),
420+
// Fleet Server indices. Kibana create this indice before Fleet Server use them.
421+
// Fleet Server indices. Kibana read and write to this indice to manage Elastic Agents
422+
RoleDescriptor.IndicesPrivileges.builder()
423+
.indices(".fleet*")
424+
.privileges("all").build(),
425+
// Legacy "Alerts as data" index. Kibana user will create this index.
426+
// Kibana user will read / write to these indices
427+
RoleDescriptor.IndicesPrivileges.builder()
428+
.indices(ReservedRolesStore.LEGACY_ALERTS_INDEX)
429+
.privileges("all").build(),
430+
// "Alerts as data" index. Kibana user will create this index.
431+
// Kibana user will read / write to these indices
432+
RoleDescriptor.IndicesPrivileges.builder()
433+
.indices(ReservedRolesStore.ALERTS_INDEX)
434+
.privileges("all").build(),
435+
// Endpoint / Fleet policy responses. Kibana requires read access to send telemetry
436+
RoleDescriptor.IndicesPrivileges.builder()
437+
.indices("metrics-endpoint.policy-*")
438+
.privileges("read").build(),
439+
// Endpoint metrics. Kibana requires read access to send telemetry
440+
RoleDescriptor.IndicesPrivileges.builder()
441+
.indices("metrics-endpoint.metrics-*")
442+
.privileges("read").build()
443+
},
444+
null,
445+
new ConfigurableClusterPrivilege[] { new ManageApplicationPrivileges(Collections.singleton("kibana-*")) },
446+
null, MetadataUtils.DEFAULT_RESERVED_METADATA, null);
447+
}
448+
445449
public static boolean isReserved(String role) {
446450
return RESERVED_ROLES.containsKey(role) || UsernamesField.SYSTEM_ROLE.equals(role) ||
447451
UsernamesField.XPACK_ROLE.equals(role) || UsernamesField.ASYNC_SEARCH_ROLE.equals(role);

x-pack/plugin/security/qa/service-account/src/javaRestTest/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountIT.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,20 @@
1212
import org.elasticsearch.client.RequestOptions;
1313
import org.elasticsearch.client.Response;
1414
import org.elasticsearch.client.ResponseException;
15+
import org.elasticsearch.common.Strings;
1516
import org.elasticsearch.common.bytes.BytesArray;
17+
import org.elasticsearch.common.xcontent.ToXContent;
18+
import org.elasticsearch.common.xcontent.XContentBuilder;
19+
import org.elasticsearch.common.xcontent.json.JsonXContent;
1620
import org.elasticsearch.core.PathUtils;
1721
import org.elasticsearch.common.settings.SecureString;
1822
import org.elasticsearch.common.settings.Settings;
1923
import org.elasticsearch.common.util.concurrent.ThreadContext;
2024
import org.elasticsearch.common.xcontent.XContentHelper;
2125
import org.elasticsearch.common.xcontent.XContentType;
2226
import org.elasticsearch.test.rest.ESRestTestCase;
27+
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
28+
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
2329
import org.junit.BeforeClass;
2430

2531
import java.io.FileNotFoundException;
@@ -165,6 +171,18 @@ public void testGetServiceAccount() throws IOException {
165171
assertServiceAccountRoleDescriptor(getServiceAccountResponse3,
166172
"elastic/fleet-server", ELASTIC_FLEET_SERVER_ROLE_DESCRIPTOR);
167173

174+
final Request getServiceAccountRequestKibana = new Request("GET", "_security/service/elastic/kibana");
175+
final Response getServiceAccountResponseKibana = client().performRequest(getServiceAccountRequestKibana);
176+
assertOK(getServiceAccountResponseKibana);
177+
assertServiceAccountRoleDescriptor(
178+
getServiceAccountResponseKibana,
179+
"elastic/kibana",
180+
Strings.toString(
181+
ReservedRolesStore.kibanaSystemRoleDescriptor(KibanaSystemUser.ROLE_NAME)
182+
.toXContent(JsonXContent.contentBuilder(), ToXContent.EMPTY_PARAMS)
183+
)
184+
);
185+
168186
final String requestPath = "_security/service/" + randomFrom("foo", "elastic/foo", "foo/bar");
169187
final Request getServiceAccountRequest4 = new Request("GET", requestPath);
170188
final Response getServiceAccountResponse4 = client().performRequest(getServiceAccountRequest4);

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccounts.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.elasticsearch.common.Strings;
1111
import org.elasticsearch.core.List;
1212
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
13+
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
1314
import org.elasticsearch.xpack.core.security.user.User;
1415

1516
import java.util.Map;
@@ -43,8 +44,10 @@ final class ElasticServiceAccounts {
4344
null,
4445
null
4546
));
47+
private static final ServiceAccount KIBANA_SYSTEM_ACCOUNT =
48+
new ElasticServiceAccount("kibana", ReservedRolesStore.kibanaSystemRoleDescriptor(NAMESPACE + "/kibana"));
4649

47-
static final Map<String, ServiceAccount> ACCOUNTS = List.of(FLEET_ACCOUNT).stream()
50+
static final Map<String, ServiceAccount> ACCOUNTS = List.of(FLEET_ACCOUNT, KIBANA_SYSTEM_ACCOUNT).stream()
4851
.collect(Collectors.toMap(a -> a.id().asPrincipal(), Function.identity()));;
4952

5053
private ElasticServiceAccounts() {}

0 commit comments

Comments
 (0)