Skip to content

Commit

Permalink
[Service Account] Add AutoOps account
Browse files Browse the repository at this point in the history
This adds a `ServiceAccount` for AutoOps usage to collect monitoring
stats from the cluster.
  • Loading branch information
pickypg committed Jul 25, 2024
1 parent 25ad974 commit 1b4f334
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,33 @@ public class ServiceAccountIT extends ESRestTestCase {
}
""";

private static final String ELASTIC_AUTO_OPS_ROLE_DESCRIPTOR = """
{
"cluster": [
"monitor",
"read_ilm",
"read_slm"
],
"indices": [
{
"names": [
"*"
],
"privileges": [
"monitor",
"view_index_metadata"
],
"allow_restricted_indices": true
}
],
"applications": [],
"run_as": [],
"metadata": {},
"transient_metadata": {
"enabled": true
}
}""";

private static final String ELASTIC_FLEET_SERVER_ROLE_DESCRIPTOR = """
{
"cluster": [
Expand Down Expand Up @@ -400,6 +427,10 @@ public void testGetServiceAccount() throws IOException {
assertOK(getServiceAccountResponse3);
assertServiceAccountRoleDescriptor(getServiceAccountResponse3, "elastic/fleet-server", ELASTIC_FLEET_SERVER_ROLE_DESCRIPTOR);

final Request getServiceAccountRequestAutoOps = new Request("GET", "_security/service/elastic/auto-ops");
final Response getServiceAccountResponseAutoOps = client().performRequest(getServiceAccountRequestAutoOps);
assertServiceAccountRoleDescriptor(getServiceAccountResponseAutoOps, "elastic/auto-ops", ELASTIC_AUTO_OPS_ROLE_DESCRIPTOR);

final Request getServiceAccountRequestKibana = new Request("GET", "_security/service/elastic/kibana");
final Response getServiceAccountResponseKibana = client().performRequest(getServiceAccountRequestKibana);
assertOK(getServiceAccountResponseKibana);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ final class ElasticServiceAccounts {

static final String NAMESPACE = "elastic";

private static final ServiceAccount AUTO_OPS_ACCOUNT = new ElasticServiceAccount(
"auto-ops",
new RoleDescriptor(
NAMESPACE + "/auto-ops",
new String[] { "monitor", "read_ilm", "read_slm" },
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder()
.allowRestrictedIndices(true)
.indices("*")
.privileges("monitor", "view_index_metadata")
.build(), },
null,
null,
null,
null,
null
)
);

private static final ServiceAccount ENTERPRISE_SEARCH_ACCOUNT = new ElasticServiceAccount(
"enterprise-search-server",
new RoleDescriptor(
Expand Down Expand Up @@ -173,6 +192,7 @@ final class ElasticServiceAccounts {
);

static final Map<String, ServiceAccount> ACCOUNTS = Stream.of(
AUTO_OPS_ACCOUNT,
ENTERPRISE_SEARCH_ACCOUNT,
FLEET_ACCOUNT,
FLEET_REMOTE_ACCOUNT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Collectors;

import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
Expand All @@ -47,12 +46,16 @@ public void testDoExecute() {
final PlainActionFuture<GetServiceAccountResponse> future1 = new PlainActionFuture<>();
transportGetServiceAccountAction.doExecute(mock(Task.class), request1, future1);
final GetServiceAccountResponse getServiceAccountResponse1 = future1.actionGet();
assertThat(getServiceAccountResponse1.getServiceAccountInfos().length, equalTo(4));
assertThat(getServiceAccountResponse1.getServiceAccountInfos().length, equalTo(5));
assertThat(
Arrays.stream(getServiceAccountResponse1.getServiceAccountInfos())
.map(ServiceAccountInfo::getPrincipal)
.collect(Collectors.toList()),
containsInAnyOrder("elastic/enterprise-search-server", "elastic/fleet-server", "elastic/fleet-server-remote", "elastic/kibana")
Arrays.stream(getServiceAccountResponse1.getServiceAccountInfos()).map(ServiceAccountInfo::getPrincipal).toList(),
containsInAnyOrder(
"elastic/auto-ops",
"elastic/enterprise-search-server",
"elastic/fleet-server",
"elastic/fleet-server-remote",
"elastic/kibana"
)
);

final GetServiceAccountRequest request2 = new GetServiceAccountRequest("elastic", "fleet-server");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,29 @@

import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction;
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction;
import org.elasticsearch.action.admin.cluster.snapshots.create.TransportCreateSnapshotAction;
import org.elasticsearch.action.admin.cluster.snapshots.delete.TransportDeleteSnapshotAction;
import org.elasticsearch.action.admin.cluster.snapshots.get.TransportGetSnapshotsAction;
import org.elasticsearch.action.admin.cluster.snapshots.restore.TransportRestoreSnapshotAction;
import org.elasticsearch.action.admin.indices.create.AutoCreateAction;
import org.elasticsearch.action.admin.indices.create.TransportCreateIndexAction;
import org.elasticsearch.action.admin.indices.delete.TransportDeleteIndexAction;
import org.elasticsearch.action.admin.indices.mapping.put.TransportAutoPutMappingAction;
import org.elasticsearch.action.admin.indices.refresh.RefreshAction;
import org.elasticsearch.action.admin.indices.alias.TransportIndicesAliasesAction;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesAction;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsAction;
import org.elasticsearch.action.admin.indices.settings.put.TransportUpdateSettingsAction;
import org.elasticsearch.action.admin.indices.stats.IndicesStatsAction;
import org.elasticsearch.action.admin.indices.template.delete.TransportDeleteIndexTemplateAction;
import org.elasticsearch.action.admin.indices.template.get.GetComponentTemplateAction;
import org.elasticsearch.action.admin.indices.template.get.GetComposableIndexTemplateAction;
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesAction;
import org.elasticsearch.action.admin.indices.template.put.TransportPutIndexTemplateAction;
import org.elasticsearch.action.admin.cluster.node.stats.TransportNodesStatsAction;
import org.elasticsearch.action.bulk.TransportBulkAction;
import org.elasticsearch.action.datastreams.DataStreamsStatsAction;
import org.elasticsearch.action.datastreams.lifecycle.GetDataStreamLifecycleAction;
import org.elasticsearch.action.delete.TransportDeleteAction;
import org.elasticsearch.action.get.TransportGetAction;
import org.elasticsearch.action.get.TransportMultiGetAction;
Expand Down Expand Up @@ -52,6 +64,11 @@
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.slm.action.DeleteSnapshotLifecycleAction;
import org.elasticsearch.xpack.core.slm.action.ExecuteSnapshotLifecycleAction;
import org.elasticsearch.xpack.core.slm.action.GetSnapshotLifecycleAction;
import org.elasticsearch.xpack.core.slm.action.GetSLMStatusAction;
import org.elasticsearch.xpack.core.slm.action.PutSnapshotLifecycleAction;
import org.elasticsearch.xpack.security.authc.service.ElasticServiceAccounts.ElasticServiceAccount;

import java.util.List;
Expand All @@ -67,6 +84,79 @@

public class ElasticServiceAccountsTests extends ESTestCase {

public void testAutoOpsPrivileges() {
final Role role = Role.buildFromRoleDescriptor(
ElasticServiceAccounts.ACCOUNTS.get("elastic/auto-ops").roleDescriptor(),
new FieldPermissionsCache(Settings.EMPTY),
RESTRICTED_INDICES
);

final Authentication authentication = AuthenticationTestHelper.builder().serviceAccount().build();
final TransportRequest request = mock(TransportRequest.class);

// monitor
assertThat(role.cluster().check(GetComponentTemplateAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetComposableIndexTemplateAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(TransportClusterHealthAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(TransportNodesStatsAction.TYPE.name(), request, authentication), is(true));

assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(TransportPutIndexTemplateAction.TYPE.name(), request, authentication), is(false));
assertThat(role.cluster().check(TransportDeleteIndexTemplateAction.TYPE.name(), request, authentication), is(false));

// read_ilm
assertThat(role.cluster().check(GetLifecycleAction.NAME, request, authentication), is(true));

assertThat(role.cluster().check(ILMActions.STOP.name(), request, authentication), is(false));
assertThat(role.cluster().check(ILMActions.PUT.name(), request, authentication), is(false));

// read_slm
assertThat(role.cluster().check(GetSLMStatusAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetSnapshotLifecycleAction.NAME, request, authentication), is(true));

assertThat(role.cluster().check(DeleteSnapshotLifecycleAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(ExecuteSnapshotLifecycleAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(PutSnapshotLifecycleAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(TransportGetSnapshotsAction.TYPE.name(), request, authentication), is(false));
assertThat(role.cluster().check(TransportCreateSnapshotAction.TYPE.name(), request, authentication), is(false));
assertThat(role.cluster().check(TransportDeleteSnapshotAction.TYPE.name(), request, authentication), is(false));
assertThat(role.cluster().check(TransportRestoreSnapshotAction.TYPE.name(), request, authentication), is(false));

// index monitor
List.of(
"search-" + randomAlphaOfLengthBetween(1, 20),
".kibana-" + randomAlphaOfLengthBetween(1, 20),
".elastic-analytics-collections",
"logs-" + randomAlphaOfLengthBetween(1, 20),
"my-index-" + randomAlphaOfLengthBetween(1, 20),
".internal.alerts-default.alerts-default-" + randomAlphaOfLengthBetween(1, 20)
).forEach(index -> {
final IndexAbstraction anyIndex = mockIndexAbstraction(index);

assertThat(role.indices().allowedIndicesMatcher(IndicesStatsAction.NAME).test(anyIndex), is(true));
assertThat(role.indices().allowedIndicesMatcher(DataStreamsStatsAction.NAME).test(anyIndex), is(true));
assertThat(role.indices().allowedIndicesMatcher(GetAliasesAction.NAME).test(anyIndex), is(true));
assertThat(role.indices().allowedIndicesMatcher(GetSettingsAction.NAME).test(anyIndex), is(true));
assertThat(role.indices().allowedIndicesMatcher(GetDataStreamLifecycleAction.INSTANCE.name()).test(anyIndex), is(true));

assertThat(role.indices().allowedIndicesMatcher(AutoCreateAction.NAME).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(TransportCreateIndexAction.TYPE.name()).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(TransportDeleteAction.NAME).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(TransportDeleteIndexAction.TYPE.name()).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(TransportIndexAction.NAME).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(TransportIndicesAliasesAction.NAME).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(TransportBulkAction.NAME).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(TransportGetAction.TYPE.name()).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(TransportMultiGetAction.NAME).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(TransportMultiSearchAction.TYPE.name()).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(TransportUpdateSettingsAction.TYPE.name()).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher(RefreshAction.NAME).test(anyIndex), is(false));
assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(anyIndex), is(false));
});
}

public void testKibanaSystemPrivileges() {
final RoleDescriptor serviceAccountRoleDescriptor = ElasticServiceAccounts.ACCOUNTS.get("elastic/kibana").roleDescriptor();
final RoleDescriptor reservedRolesStoreRoleDescriptor = ReservedRolesStore.kibanaSystemRoleDescriptor(KibanaSystemUser.ROLE_NAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ public void init() throws UnknownHostException {
indexServiceAccountTokenStore = mock(IndexServiceAccountTokenStore.class);
when(fileServiceAccountTokenStore.getTokenSource()).thenReturn(TokenInfo.TokenSource.FILE);
when(indexServiceAccountTokenStore.getTokenSource()).thenReturn(TokenInfo.TokenSource.INDEX);
final Settings.Builder builder = Settings.builder().put("xpack.security.enabled", true);
client = mock(Client.class);
when(client.threadPool()).thenReturn(threadPool);
serviceAccountService = new ServiceAccountService(client, fileServiceAccountTokenStore, indexServiceAccountTokenStore);
Expand All @@ -96,11 +95,17 @@ public void stopThreadPool() {
public void testGetServiceAccountPrincipals() {
assertThat(
ServiceAccountService.getServiceAccountPrincipals(),
containsInAnyOrder("elastic/enterprise-search-server", "elastic/fleet-server", "elastic/fleet-server-remote", "elastic/kibana")
containsInAnyOrder(
"elastic/auto-ops",
"elastic/enterprise-search-server",
"elastic/fleet-server",
"elastic/fleet-server-remote",
"elastic/kibana"
)
);
}

public void testTryParseToken() throws IOException, IllegalAccessException {
public void testTryParseToken() throws IOException {
// Null for null
assertNull(ServiceAccountService.tryParseToken(null));

Expand Down

0 comments on commit 1b4f334

Please sign in to comment.