Skip to content

Commit 59d75f2

Browse files
authored
Add kibana-system service account (#76449)
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 2ddbd62 commit 59d75f2

File tree

10 files changed

+360
-106
lines changed

10 files changed

+360
-106
lines changed

rest-api-spec/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ def v7compatibilityNotSupportedTests = {
8686
'search.aggregation/200_top_hits_metric/top_hits aggregation with sequence numbers', // #42809 the use nested path and filter sort throws an exception
8787
'search/310_match_bool_prefix/multi_match multiple fields with cutoff_frequency throws exception', //#42654 cutoff_frequency, common terms are not supported. Throwing an exception
8888

89-
9089
]
9190
}
9291
tasks.named("yamlRestCompatTest").configure {

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/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ def v7compatibilityNotSupportedTests = {
153153

154154
// a type field was added to cat.ml_trained_models #73660, this is a backwards compatible change.
155155
// still this is a cat api, and we don't support them with rest api compatibility. (the test would be very hard to transform too)
156-
'ml/trained_model_cat_apis/Test cat trained models'
156+
'ml/trained_model_cat_apis/Test cat trained models',
157+
'service_accounts/10_basic/Test get service accounts', //#76449, will remove upon backport
157158
]
158159
}
159160

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",
@@ -434,6 +363,81 @@ private static RoleDescriptor kibanaAdminUser(String name, Map<String, Object> m
434363
null, null, metadata, null);
435364
}
436365

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

1010
import org.elasticsearch.common.Strings;
1111
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
12+
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
1213
import org.elasticsearch.xpack.core.security.user.User;
1314

1415
import java.util.List;
@@ -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)