Skip to content

Commit e24a7c1

Browse files
authored
[Service Account] Add AutoOps account (#111316)
This adds a `ServiceAccount` for AutoOps usage to collect monitoring stats from the cluster.
1 parent 80d539d commit e24a7c1

File tree

7 files changed

+167
-12
lines changed

7 files changed

+167
-12
lines changed

docs/changelog/111316.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 111316
2+
summary: "[Service Account] Add `AutoOps` account"
3+
area: Security
4+
type: enhancement
5+
issues: []

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,33 @@ public class ServiceAccountIT extends ESRestTestCase {
8080
}
8181
""";
8282

83+
private static final String ELASTIC_AUTO_OPS_ROLE_DESCRIPTOR = """
84+
{
85+
"cluster": [
86+
"monitor",
87+
"read_ilm",
88+
"read_slm"
89+
],
90+
"indices": [
91+
{
92+
"names": [
93+
"*"
94+
],
95+
"privileges": [
96+
"monitor",
97+
"view_index_metadata"
98+
],
99+
"allow_restricted_indices": true
100+
}
101+
],
102+
"applications": [],
103+
"run_as": [],
104+
"metadata": {},
105+
"transient_metadata": {
106+
"enabled": true
107+
}
108+
}""";
109+
83110
private static final String ELASTIC_FLEET_SERVER_ROLE_DESCRIPTOR = """
84111
{
85112
"cluster": [
@@ -400,6 +427,10 @@ public void testGetServiceAccount() throws IOException {
400427
assertOK(getServiceAccountResponse3);
401428
assertServiceAccountRoleDescriptor(getServiceAccountResponse3, "elastic/fleet-server", ELASTIC_FLEET_SERVER_ROLE_DESCRIPTOR);
402429

430+
final Request getServiceAccountRequestAutoOps = new Request("GET", "_security/service/elastic/auto-ops");
431+
final Response getServiceAccountResponseAutoOps = client().performRequest(getServiceAccountRequestAutoOps);
432+
assertServiceAccountRoleDescriptor(getServiceAccountResponseAutoOps, "elastic/auto-ops", ELASTIC_AUTO_OPS_ROLE_DESCRIPTOR);
433+
403434
final Request getServiceAccountRequestKibana = new Request("GET", "_security/service/elastic/kibana");
404435
final Response getServiceAccountResponseKibana = client().performRequest(getServiceAccountRequestKibana);
405436
assertOK(getServiceAccountResponseKibana);

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,25 @@ final class ElasticServiceAccounts {
2222

2323
static final String NAMESPACE = "elastic";
2424

25+
private static final ServiceAccount AUTO_OPS_ACCOUNT = new ElasticServiceAccount(
26+
"auto-ops",
27+
new RoleDescriptor(
28+
NAMESPACE + "/auto-ops",
29+
new String[] { "monitor", "read_ilm", "read_slm" },
30+
new RoleDescriptor.IndicesPrivileges[] {
31+
RoleDescriptor.IndicesPrivileges.builder()
32+
.allowRestrictedIndices(true)
33+
.indices("*")
34+
.privileges("monitor", "view_index_metadata")
35+
.build(), },
36+
null,
37+
null,
38+
null,
39+
null,
40+
null
41+
)
42+
);
43+
2544
private static final ServiceAccount ENTERPRISE_SEARCH_ACCOUNT = new ElasticServiceAccount(
2645
"enterprise-search-server",
2746
new RoleDescriptor(
@@ -173,6 +192,7 @@ final class ElasticServiceAccounts {
173192
);
174193

175194
static final Map<String, ServiceAccount> ACCOUNTS = Stream.of(
195+
AUTO_OPS_ACCOUNT,
176196
ENTERPRISE_SEARCH_ACCOUNT,
177197
FLEET_ACCOUNT,
178198
FLEET_REMOTE_ACCOUNT,

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/service/TransportGetServiceAccountActionTests.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import java.util.Arrays;
2222
import java.util.Collections;
23-
import java.util.stream.Collectors;
2423

2524
import static org.hamcrest.Matchers.containsInAnyOrder;
2625
import static org.hamcrest.Matchers.equalTo;
@@ -47,12 +46,16 @@ public void testDoExecute() {
4746
final PlainActionFuture<GetServiceAccountResponse> future1 = new PlainActionFuture<>();
4847
transportGetServiceAccountAction.doExecute(mock(Task.class), request1, future1);
4948
final GetServiceAccountResponse getServiceAccountResponse1 = future1.actionGet();
50-
assertThat(getServiceAccountResponse1.getServiceAccountInfos().length, equalTo(4));
49+
assertThat(getServiceAccountResponse1.getServiceAccountInfos().length, equalTo(5));
5150
assertThat(
52-
Arrays.stream(getServiceAccountResponse1.getServiceAccountInfos())
53-
.map(ServiceAccountInfo::getPrincipal)
54-
.collect(Collectors.toList()),
55-
containsInAnyOrder("elastic/enterprise-search-server", "elastic/fleet-server", "elastic/fleet-server-remote", "elastic/kibana")
51+
Arrays.stream(getServiceAccountResponse1.getServiceAccountInfos()).map(ServiceAccountInfo::getPrincipal).toList(),
52+
containsInAnyOrder(
53+
"elastic/auto-ops",
54+
"elastic/enterprise-search-server",
55+
"elastic/fleet-server",
56+
"elastic/fleet-server-remote",
57+
"elastic/kibana"
58+
)
5659
);
5760

5861
final GetServiceAccountRequest request2 = new GetServiceAccountRequest("elastic", "fleet-server");

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccountsTests.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,30 @@
88
package org.elasticsearch.xpack.security.authc.service;
99

1010
import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction;
11+
import org.elasticsearch.action.admin.cluster.node.stats.TransportNodesStatsAction;
1112
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction;
13+
import org.elasticsearch.action.admin.cluster.snapshots.create.TransportCreateSnapshotAction;
14+
import org.elasticsearch.action.admin.cluster.snapshots.delete.TransportDeleteSnapshotAction;
15+
import org.elasticsearch.action.admin.cluster.snapshots.get.TransportGetSnapshotsAction;
16+
import org.elasticsearch.action.admin.cluster.snapshots.restore.TransportRestoreSnapshotAction;
17+
import org.elasticsearch.action.admin.indices.alias.TransportIndicesAliasesAction;
18+
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesAction;
1219
import org.elasticsearch.action.admin.indices.create.AutoCreateAction;
1320
import org.elasticsearch.action.admin.indices.create.TransportCreateIndexAction;
1421
import org.elasticsearch.action.admin.indices.delete.TransportDeleteIndexAction;
1522
import org.elasticsearch.action.admin.indices.mapping.put.TransportAutoPutMappingAction;
1623
import org.elasticsearch.action.admin.indices.refresh.RefreshAction;
24+
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsAction;
1725
import org.elasticsearch.action.admin.indices.settings.put.TransportUpdateSettingsAction;
1826
import org.elasticsearch.action.admin.indices.stats.IndicesStatsAction;
1927
import org.elasticsearch.action.admin.indices.template.delete.TransportDeleteIndexTemplateAction;
28+
import org.elasticsearch.action.admin.indices.template.get.GetComponentTemplateAction;
29+
import org.elasticsearch.action.admin.indices.template.get.GetComposableIndexTemplateAction;
2030
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesAction;
2131
import org.elasticsearch.action.admin.indices.template.put.TransportPutIndexTemplateAction;
2232
import org.elasticsearch.action.bulk.TransportBulkAction;
33+
import org.elasticsearch.action.datastreams.DataStreamsStatsAction;
34+
import org.elasticsearch.action.datastreams.lifecycle.GetDataStreamLifecycleAction;
2335
import org.elasticsearch.action.delete.TransportDeleteAction;
2436
import org.elasticsearch.action.get.TransportGetAction;
2537
import org.elasticsearch.action.get.TransportMultiGetAction;
@@ -52,6 +64,11 @@
5264
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
5365
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
5466
import org.elasticsearch.xpack.core.security.user.User;
67+
import org.elasticsearch.xpack.core.slm.action.DeleteSnapshotLifecycleAction;
68+
import org.elasticsearch.xpack.core.slm.action.ExecuteSnapshotLifecycleAction;
69+
import org.elasticsearch.xpack.core.slm.action.GetSLMStatusAction;
70+
import org.elasticsearch.xpack.core.slm.action.GetSnapshotLifecycleAction;
71+
import org.elasticsearch.xpack.core.slm.action.PutSnapshotLifecycleAction;
5572
import org.elasticsearch.xpack.security.authc.service.ElasticServiceAccounts.ElasticServiceAccount;
5673

5774
import java.util.List;
@@ -67,6 +84,79 @@
6784

6885
public class ElasticServiceAccountsTests extends ESTestCase {
6986

87+
public void testAutoOpsPrivileges() {
88+
final Role role = Role.buildFromRoleDescriptor(
89+
ElasticServiceAccounts.ACCOUNTS.get("elastic/auto-ops").roleDescriptor(),
90+
new FieldPermissionsCache(Settings.EMPTY),
91+
RESTRICTED_INDICES
92+
);
93+
94+
final Authentication authentication = AuthenticationTestHelper.builder().serviceAccount().build();
95+
final TransportRequest request = mock(TransportRequest.class);
96+
97+
// monitor
98+
assertThat(role.cluster().check(GetComponentTemplateAction.NAME, request, authentication), is(true));
99+
assertThat(role.cluster().check(GetComposableIndexTemplateAction.NAME, request, authentication), is(true));
100+
assertThat(role.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true));
101+
assertThat(role.cluster().check(TransportClusterHealthAction.NAME, request, authentication), is(true));
102+
assertThat(role.cluster().check(TransportNodesStatsAction.TYPE.name(), request, authentication), is(true));
103+
104+
assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
105+
assertThat(role.cluster().check(TransportPutIndexTemplateAction.TYPE.name(), request, authentication), is(false));
106+
assertThat(role.cluster().check(TransportDeleteIndexTemplateAction.TYPE.name(), request, authentication), is(false));
107+
108+
// read_ilm
109+
assertThat(role.cluster().check(GetLifecycleAction.NAME, request, authentication), is(true));
110+
111+
assertThat(role.cluster().check(ILMActions.STOP.name(), request, authentication), is(false));
112+
assertThat(role.cluster().check(ILMActions.PUT.name(), request, authentication), is(false));
113+
114+
// read_slm
115+
assertThat(role.cluster().check(GetSLMStatusAction.NAME, request, authentication), is(true));
116+
assertThat(role.cluster().check(GetSnapshotLifecycleAction.NAME, request, authentication), is(true));
117+
118+
assertThat(role.cluster().check(DeleteSnapshotLifecycleAction.NAME, request, authentication), is(false));
119+
assertThat(role.cluster().check(ExecuteSnapshotLifecycleAction.NAME, request, authentication), is(false));
120+
assertThat(role.cluster().check(PutSnapshotLifecycleAction.NAME, request, authentication), is(false));
121+
assertThat(role.cluster().check(TransportGetSnapshotsAction.TYPE.name(), request, authentication), is(false));
122+
assertThat(role.cluster().check(TransportCreateSnapshotAction.TYPE.name(), request, authentication), is(false));
123+
assertThat(role.cluster().check(TransportDeleteSnapshotAction.TYPE.name(), request, authentication), is(false));
124+
assertThat(role.cluster().check(TransportRestoreSnapshotAction.TYPE.name(), request, authentication), is(false));
125+
126+
// index monitor
127+
List.of(
128+
"search-" + randomAlphaOfLengthBetween(1, 20),
129+
".kibana-" + randomAlphaOfLengthBetween(1, 20),
130+
".elastic-analytics-collections",
131+
"logs-" + randomAlphaOfLengthBetween(1, 20),
132+
"my-index-" + randomAlphaOfLengthBetween(1, 20),
133+
".internal.alerts-default.alerts-default-" + randomAlphaOfLengthBetween(1, 20)
134+
).forEach(index -> {
135+
final IndexAbstraction anyIndex = mockIndexAbstraction(index);
136+
137+
assertThat(role.indices().allowedIndicesMatcher(IndicesStatsAction.NAME).test(anyIndex), is(true));
138+
assertThat(role.indices().allowedIndicesMatcher(DataStreamsStatsAction.NAME).test(anyIndex), is(true));
139+
assertThat(role.indices().allowedIndicesMatcher(GetAliasesAction.NAME).test(anyIndex), is(true));
140+
assertThat(role.indices().allowedIndicesMatcher(GetSettingsAction.NAME).test(anyIndex), is(true));
141+
assertThat(role.indices().allowedIndicesMatcher(GetDataStreamLifecycleAction.INSTANCE.name()).test(anyIndex), is(true));
142+
143+
assertThat(role.indices().allowedIndicesMatcher(AutoCreateAction.NAME).test(anyIndex), is(false));
144+
assertThat(role.indices().allowedIndicesMatcher(TransportCreateIndexAction.TYPE.name()).test(anyIndex), is(false));
145+
assertThat(role.indices().allowedIndicesMatcher(TransportDeleteAction.NAME).test(anyIndex), is(false));
146+
assertThat(role.indices().allowedIndicesMatcher(TransportDeleteIndexAction.TYPE.name()).test(anyIndex), is(false));
147+
assertThat(role.indices().allowedIndicesMatcher(TransportIndexAction.NAME).test(anyIndex), is(false));
148+
assertThat(role.indices().allowedIndicesMatcher(TransportIndicesAliasesAction.NAME).test(anyIndex), is(false));
149+
assertThat(role.indices().allowedIndicesMatcher(TransportBulkAction.NAME).test(anyIndex), is(false));
150+
assertThat(role.indices().allowedIndicesMatcher(TransportGetAction.TYPE.name()).test(anyIndex), is(false));
151+
assertThat(role.indices().allowedIndicesMatcher(TransportMultiGetAction.NAME).test(anyIndex), is(false));
152+
assertThat(role.indices().allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(anyIndex), is(false));
153+
assertThat(role.indices().allowedIndicesMatcher(TransportMultiSearchAction.TYPE.name()).test(anyIndex), is(false));
154+
assertThat(role.indices().allowedIndicesMatcher(TransportUpdateSettingsAction.TYPE.name()).test(anyIndex), is(false));
155+
assertThat(role.indices().allowedIndicesMatcher(RefreshAction.NAME).test(anyIndex), is(false));
156+
assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(anyIndex), is(false));
157+
});
158+
}
159+
70160
public void testKibanaSystemPrivileges() {
71161
final RoleDescriptor serviceAccountRoleDescriptor = ElasticServiceAccounts.ACCOUNTS.get("elastic/kibana").roleDescriptor();
72162
final RoleDescriptor reservedRolesStoreRoleDescriptor = ReservedRolesStore.kibanaSystemRoleDescriptor(KibanaSystemUser.ROLE_NAME);

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountServiceTests.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import org.elasticsearch.common.Strings;
1818
import org.elasticsearch.common.logging.Loggers;
1919
import org.elasticsearch.common.settings.SecureString;
20-
import org.elasticsearch.common.settings.Settings;
2120
import org.elasticsearch.core.SuppressForbidden;
2221
import org.elasticsearch.test.ESTestCase;
2322
import org.elasticsearch.test.MockLog;
@@ -82,7 +81,6 @@ public void init() throws UnknownHostException {
8281
indexServiceAccountTokenStore = mock(IndexServiceAccountTokenStore.class);
8382
when(fileServiceAccountTokenStore.getTokenSource()).thenReturn(TokenInfo.TokenSource.FILE);
8483
when(indexServiceAccountTokenStore.getTokenSource()).thenReturn(TokenInfo.TokenSource.INDEX);
85-
final Settings.Builder builder = Settings.builder().put("xpack.security.enabled", true);
8684
client = mock(Client.class);
8785
when(client.threadPool()).thenReturn(threadPool);
8886
serviceAccountService = new ServiceAccountService(client, fileServiceAccountTokenStore, indexServiceAccountTokenStore);
@@ -96,11 +94,17 @@ public void stopThreadPool() {
9694
public void testGetServiceAccountPrincipals() {
9795
assertThat(
9896
ServiceAccountService.getServiceAccountPrincipals(),
99-
containsInAnyOrder("elastic/enterprise-search-server", "elastic/fleet-server", "elastic/fleet-server-remote", "elastic/kibana")
97+
containsInAnyOrder(
98+
"elastic/auto-ops",
99+
"elastic/enterprise-search-server",
100+
"elastic/fleet-server",
101+
"elastic/fleet-server-remote",
102+
"elastic/kibana"
103+
)
100104
);
101105
}
102106

103-
public void testTryParseToken() throws IOException, IllegalAccessException {
107+
public void testTryParseToken() throws IOException {
104108
// Null for null
105109
assertNull(ServiceAccountService.tryParseToken(null));
106110

x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/service_accounts/10_basic.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ teardown:
3131
"Test get service accounts":
3232
- do:
3333
security.get_service_accounts: {}
34-
- length: { '': 4 }
34+
- length: { '': 5 }
35+
- is_true: "elastic/auto-ops"
3536
- is_true: "elastic/enterprise-search-server"
3637
- is_true: "elastic/fleet-server"
3738
- is_true: "elastic/fleet-server-remote"
@@ -40,7 +41,8 @@ teardown:
4041
- do:
4142
security.get_service_accounts:
4243
namespace: elastic
43-
- length: { '': 4 }
44+
- length: { '': 5 }
45+
- is_true: "elastic/auto-ops"
4446
- is_true: "elastic/enterprise-search-server"
4547
- is_true: "elastic/fleet-server"
4648
- is_true: "elastic/fleet-server-remote"

0 commit comments

Comments
 (0)