From fb698c35e8f9c7f4406ab9c6ae98c03b81403d05 Mon Sep 17 00:00:00 2001 From: Szymon Bialkowski Date: Tue, 16 Sep 2025 20:06:31 +0100 Subject: [PATCH 1/7] Add new `/_security/stats` endpoint --- .../rest-api-spec/api/security.get_stats.json | 29 +++++++ .../core/src/main/java/module-info.java | 1 + .../action/stats/GetSecurityStatsAction.java | 19 +++++ .../stats/GetSecurityStatsNodeRequest.java | 28 +++++++ .../stats/GetSecurityStatsNodeResponse.java | 38 ++++++++++ .../stats/GetSecurityStatsNodesRequest.java | 16 ++++ .../stats/GetSecurityStatsNodesResponse.java | 54 +++++++++++++ .../security/src/main/java/module-info.java | 1 + .../xpack/security/Security.java | 7 +- .../xpack/security/SecurityFeatures.java | 4 +- .../stats/TransportSecurityStatsAction.java | 75 +++++++++++++++++++ .../action/stats/RestSecurityStatsAction.java | 47 ++++++++++++ .../test/security/stats/10_skeleton.yml | 12 +++ 13 files changed, 329 insertions(+), 2 deletions(-) create mode 100644 rest-api-spec/src/main/resources/rest-api-spec/api/security.get_stats.json create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsAction.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeResponse.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodesRequest.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodesResponse.java create mode 100644 x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/stats/TransportSecurityStatsAction.java create mode 100644 x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java create mode 100644 x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/security/stats/10_skeleton.yml diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/security.get_stats.json b/rest-api-spec/src/main/resources/rest-api-spec/api/security.get_stats.json new file mode 100644 index 0000000000000..6162d71f3019a --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/security.get_stats.json @@ -0,0 +1,29 @@ +{ + "security.get_stats": { + "documentation": { + "url": "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-get-stats", + "description": "Get security statistics for all nodes" + }, + "stability": "stable", + "visibility": "public", + "headers": { + "accept": [ + "application/json" + ], + "content_type": [ + "application/json" + ] + }, + "url": { + "paths": [ + { + "path": "/_security/stats", + "methods": [ + "GET" + ] + } + ] + }, + "params": {} + } +} diff --git a/x-pack/plugin/core/src/main/java/module-info.java b/x-pack/plugin/core/src/main/java/module-info.java index 352dbf2813aca..162fa9fe58c59 100644 --- a/x-pack/plugin/core/src/main/java/module-info.java +++ b/x-pack/plugin/core/src/main/java/module-info.java @@ -155,6 +155,7 @@ exports org.elasticsearch.xpack.core.security.action.token; exports org.elasticsearch.xpack.core.security.action.user; exports org.elasticsearch.xpack.core.security.action.settings; + exports org.elasticsearch.xpack.core.security.action.stats; exports org.elasticsearch.xpack.core.security.action; exports org.elasticsearch.xpack.core.security.authc.esnative; exports org.elasticsearch.xpack.core.security.authc.file; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsAction.java new file mode 100644 index 0000000000000..127e968629759 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsAction.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +package org.elasticsearch.xpack.core.security.action.stats; + +import org.elasticsearch.action.ActionType; + +public class GetSecurityStatsAction extends ActionType { + + public static final GetSecurityStatsAction INSTANCE = new GetSecurityStatsAction(); + public static final String NAME = "cluster:monitor/xpack/security/stats"; + + private GetSecurityStatsAction() { + super(NAME); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java new file mode 100644 index 0000000000000..827c6e0b4c533 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +package org.elasticsearch.xpack.core.security.action.stats; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.transport.AbstractTransportRequest; + +import java.io.IOException; + +public class GetSecurityStatsNodeRequest extends AbstractTransportRequest { + + public GetSecurityStatsNodeRequest() {} + + public GetSecurityStatsNodeRequest(final StreamInput in) throws IOException { + super(in); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + super.writeTo(out); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeResponse.java new file mode 100644 index 0000000000000..7956ec2eabf87 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeResponse.java @@ -0,0 +1,38 @@ +/* + * 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. + */ + +package org.elasticsearch.xpack.core.security.action.stats; + +import org.elasticsearch.action.support.nodes.BaseNodeResponse; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xcontent.ToXContentObject; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; + +public class GetSecurityStatsNodeResponse extends BaseNodeResponse implements ToXContentObject { + + public GetSecurityStatsNodeResponse(final StreamInput in) throws IOException { + super(in); + } + + public GetSecurityStatsNodeResponse(final DiscoveryNode node) { + super(node); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + super.writeTo(out); + } + + @Override + public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { + return builder; + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodesRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodesRequest.java new file mode 100644 index 0000000000000..f206f1e34241a --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodesRequest.java @@ -0,0 +1,16 @@ +/* + * 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. + */ + +package org.elasticsearch.xpack.core.security.action.stats; + +import org.elasticsearch.action.support.nodes.BaseNodesRequest; + +public class GetSecurityStatsNodesRequest extends BaseNodesRequest { + public GetSecurityStatsNodesRequest() { + super((String[]) null); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodesResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodesResponse.java new file mode 100644 index 0000000000000..fcb7157c52d9c --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodesResponse.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package org.elasticsearch.xpack.core.security.action.stats; + +import org.elasticsearch.action.FailedNodeException; +import org.elasticsearch.action.support.nodes.BaseNodesResponse; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xcontent.ToXContentObject; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.List; + +public class GetSecurityStatsNodesResponse extends BaseNodesResponse implements ToXContentObject { + + public GetSecurityStatsNodesResponse( + final ClusterName clusterName, + final List nodes, + final List failures + ) { + super(clusterName, nodes, failures); + } + + @Override + protected List readNodesFrom(final StreamInput in) throws IOException { + return in.readCollectionAsList(GetSecurityStatsNodeResponse::new); + } + + @Override + protected void writeNodesTo(final StreamOutput out, final List nodes) throws IOException { + out.writeCollection(nodes); + } + + @Override + public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { + builder.startObject(); + builder.startObject("nodes"); + for (GetSecurityStatsNodeResponse response : getNodes()) { + builder.startObject(response.getNode().getId()); + response.toXContent(builder, params); + builder.endObject(); + } + builder.endObject(); + builder.endObject(); + return builder; + } +} diff --git a/x-pack/plugin/security/src/main/java/module-info.java b/x-pack/plugin/security/src/main/java/module-info.java index 88b421e1efe31..1e4b54148234a 100644 --- a/x-pack/plugin/security/src/main/java/module-info.java +++ b/x-pack/plugin/security/src/main/java/module-info.java @@ -66,6 +66,7 @@ exports org.elasticsearch.xpack.security.action.token to org.elasticsearch.server; exports org.elasticsearch.xpack.security.action.user to org.elasticsearch.server; exports org.elasticsearch.xpack.security.action.settings to org.elasticsearch.server; + exports org.elasticsearch.xpack.security.action.stats to org.elasticsearch.server; exports org.elasticsearch.xpack.security.operator to org.elasticsearch.internal.operator, org.elasticsearch.internal.security; exports org.elasticsearch.xpack.security.authz to org.elasticsearch.internal.security; exports org.elasticsearch.xpack.security.authc to org.elasticsearch.xcontent, org.elasticsearch.internal.security; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index c9f0771e93068..4c5123a2488b6 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -181,6 +181,7 @@ import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountNodesCredentialsAction; import org.elasticsearch.xpack.core.security.action.settings.GetSecuritySettingsAction; import org.elasticsearch.xpack.core.security.action.settings.UpdateSecuritySettingsAction; +import org.elasticsearch.xpack.core.security.action.stats.GetSecurityStatsAction; import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction; import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction; import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction; @@ -281,6 +282,7 @@ import org.elasticsearch.xpack.security.action.settings.TransportGetSecuritySettingsAction; import org.elasticsearch.xpack.security.action.settings.TransportReloadRemoteClusterCredentialsAction; import org.elasticsearch.xpack.security.action.settings.TransportUpdateSecuritySettingsAction; +import org.elasticsearch.xpack.security.action.stats.TransportSecurityStatsAction; import org.elasticsearch.xpack.security.action.token.TransportCreateTokenAction; import org.elasticsearch.xpack.security.action.token.TransportInvalidateTokenAction; import org.elasticsearch.xpack.security.action.token.TransportRefreshTokenAction; @@ -401,6 +403,7 @@ import org.elasticsearch.xpack.security.rest.action.service.RestGetServiceAccountCredentialsAction; import org.elasticsearch.xpack.security.rest.action.settings.RestGetSecuritySettingsAction; import org.elasticsearch.xpack.security.rest.action.settings.RestUpdateSecuritySettingsAction; +import org.elasticsearch.xpack.security.rest.action.stats.RestSecurityStatsAction; import org.elasticsearch.xpack.security.rest.action.user.RestChangePasswordAction; import org.elasticsearch.xpack.security.rest.action.user.RestDeleteUserAction; import org.elasticsearch.xpack.security.rest.action.user.RestGetUserPrivilegesAction; @@ -1712,6 +1715,7 @@ public List getActions() { new ActionHandler(UpdateSecuritySettingsAction.INSTANCE, TransportUpdateSecuritySettingsAction.class), new ActionHandler(ActionTypes.RELOAD_REMOTE_CLUSTER_CREDENTIALS_ACTION, TransportReloadRemoteClusterCredentialsAction.class), new ActionHandler(UpdateIndexMigrationVersionAction.INSTANCE, UpdateIndexMigrationVersionAction.TransportAction.class), + new ActionHandler(GetSecurityStatsAction.INSTANCE, TransportSecurityStatsAction.class), usageAction, infoAction ).filter(Objects::nonNull).toList(); @@ -1804,7 +1808,8 @@ public List getRestHandlers( new RestEnableProfileAction(settings, getLicenseState()), new RestDisableProfileAction(settings, getLicenseState()), new RestGetSecuritySettingsAction(settings, getLicenseState()), - new RestUpdateSecuritySettingsAction(settings, getLicenseState()) + new RestUpdateSecuritySettingsAction(settings, getLicenseState()), + new RestSecurityStatsAction(settings, getLicenseState()) ).filter(Objects::nonNull).toList(); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java index 409ab62ae3e70..215a5ca5dd359 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java @@ -16,8 +16,10 @@ public class SecurityFeatures implements FeatureSpecification { + private static final NodeFeature SECURITY_STATS_ENDPOINT = new NodeFeature("security_stats_endpoint"); + @Override public Set getFeatures() { - return Set.of(QUERYABLE_BUILT_IN_ROLES_FEATURE); + return Set.of(QUERYABLE_BUILT_IN_ROLES_FEATURE, SECURITY_STATS_ENDPOINT); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/stats/TransportSecurityStatsAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/stats/TransportSecurityStatsAction.java new file mode 100644 index 0000000000000..96cd95a800c3c --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/stats/TransportSecurityStatsAction.java @@ -0,0 +1,75 @@ +/* + * 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. + */ +package org.elasticsearch.xpack.security.action.stats; + +import org.elasticsearch.action.FailedNodeException; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.nodes.TransportNodesAction; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.injection.guice.Inject; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.security.action.stats.GetSecurityStatsAction; +import org.elasticsearch.xpack.core.security.action.stats.GetSecurityStatsNodeRequest; +import org.elasticsearch.xpack.core.security.action.stats.GetSecurityStatsNodeResponse; +import org.elasticsearch.xpack.core.security.action.stats.GetSecurityStatsNodesRequest; +import org.elasticsearch.xpack.core.security.action.stats.GetSecurityStatsNodesResponse; + +import java.io.IOException; +import java.util.List; + +public class TransportSecurityStatsAction extends TransportNodesAction< + GetSecurityStatsNodesRequest, + GetSecurityStatsNodesResponse, + GetSecurityStatsNodeRequest, + GetSecurityStatsNodeResponse, + Void> { + + @Inject + public TransportSecurityStatsAction( + ThreadPool threadPool, + ClusterService clusterService, + TransportService transportService, + ActionFilters actionFilters + ) { + super( + GetSecurityStatsAction.INSTANCE.name(), + clusterService, + transportService, + actionFilters, + GetSecurityStatsNodeRequest::new, + threadPool.executor(ThreadPool.Names.MANAGEMENT) + ); + } + + @Override + protected GetSecurityStatsNodesResponse newResponse( + final GetSecurityStatsNodesRequest request, + final List responses, + final List failures + ) { + return new GetSecurityStatsNodesResponse(clusterService.getClusterName(), responses, failures); + } + + @Override + protected GetSecurityStatsNodeRequest newNodeRequest(final GetSecurityStatsNodesRequest request) { + return new GetSecurityStatsNodeRequest(); + } + + @Override + protected GetSecurityStatsNodeResponse newNodeResponse(final StreamInput in, final DiscoveryNode node) throws IOException { + return new GetSecurityStatsNodeResponse(in); + } + + @Override + protected GetSecurityStatsNodeResponse nodeOperation(final GetSecurityStatsNodeRequest request, final Task task) { + return new GetSecurityStatsNodeResponse(clusterService.localNode()); + } +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java new file mode 100644 index 0000000000000..61877422f045c --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +package org.elasticsearch.xpack.security.rest.action.stats; + +import org.elasticsearch.client.internal.node.NodeClient; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.Scope; +import org.elasticsearch.rest.ServerlessScope; +import org.elasticsearch.rest.action.RestToXContentListener; +import org.elasticsearch.xpack.core.security.action.stats.GetSecurityStatsAction; +import org.elasticsearch.xpack.core.security.action.stats.GetSecurityStatsNodesRequest; +import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; + +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; + +@ServerlessScope(Scope.INTERNAL) +public class RestSecurityStatsAction extends SecurityBaseRestHandler { + + public RestSecurityStatsAction(final Settings settings, final XPackLicenseState licenseState) { + super(settings, licenseState); + } + + @Override + public List routes() { + return List.of(new Route(GET, "/_security/stats")); + } + + @Override + public String getName() { + return "security_stats_action"; + } + + @Override + public RestChannelConsumer innerPrepareRequest(final RestRequest request, final NodeClient client) { + final var req = new GetSecurityStatsNodesRequest(); + return channel -> client.execute(GetSecurityStatsAction.INSTANCE, req, new RestToXContentListener<>(channel)); + } + +} diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/security/stats/10_skeleton.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/security/stats/10_skeleton.yml new file mode 100644 index 0000000000000..512e7c1f6e474 --- /dev/null +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/security/stats/10_skeleton.yml @@ -0,0 +1,12 @@ +--- +"Security stats returns empty response": + - requires: + cluster_features: [ "security_stats_endpoint" ] + reason: Introduced in 9.2 + + - do: + security.get_stats: {} + + - set: + nodes._arbitrary_key_: node_id + - length: { nodes.$node_id: 0 } From c53988314e65ca07b53d045feaae8a0354cf7a7e Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 16 Sep 2025 19:17:57 +0000 Subject: [PATCH 2/7] [CI] Update transport version definitions --- server/src/main/resources/transport/upper_bounds/9.2.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/resources/transport/upper_bounds/9.2.csv b/server/src/main/resources/transport/upper_bounds/9.2.csv index 49360d5e62d69..e24f914a1d1ca 100644 --- a/server/src/main/resources/transport/upper_bounds/9.2.csv +++ b/server/src/main/resources/transport/upper_bounds/9.2.csv @@ -1 +1 @@ -inference_api_eis_diagnostics,9156000 +ml_inference_endpoint_cache,9157000 From c9828ab1e3dd1f2aad450b8dd03a36b1db2c7969 Mon Sep 17 00:00:00 2001 From: Szymon Bialkowski Date: Wed, 17 Sep 2025 13:27:06 +0100 Subject: [PATCH 3/7] add action as non-operator --- .../org/elasticsearch/xpack/security/operator/Constants.java | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java index f2634a19d1068..d992f1b028a3c 100644 --- a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java +++ b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java @@ -452,6 +452,7 @@ public class Constants { "cluster:monitor/xpack/rollup/get/caps", "cluster:monitor/xpack/searchable_snapshots/stats", "cluster:monitor/xpack/security/saml/metadata", + "cluster:monitor/xpack/security/stats", "cluster:monitor/xpack/spatial/stats", "cluster:monitor/xpack/sql/async/status", // org.elasticsearch.xpack.core.sql.SqlAsyncActionNames.SQL_ASYNC_GET_STATUS_ACTION_NAME "cluster:monitor/xpack/sql/stats/dist", From 9b302900eb2d64ca72b8a71d0737182211775304 Mon Sep 17 00:00:00 2001 From: Szymon Bialkowski Date: Wed, 17 Sep 2025 13:48:42 +0100 Subject: [PATCH 4/7] Update docs/changelog/134835.yaml --- docs/changelog/134835.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/134835.yaml diff --git a/docs/changelog/134835.yaml b/docs/changelog/134835.yaml new file mode 100644 index 0000000000000..1f1b08d07a33c --- /dev/null +++ b/docs/changelog/134835.yaml @@ -0,0 +1,5 @@ +pr: 134835 +summary: Add new `/_security/stats` endpoint +area: Authorization +type: enhancement +issues: [] From f1d7291bd2b0623d1077dc32d9c6ac56da257ba8 Mon Sep 17 00:00:00 2001 From: Szymon Bialkowski Date: Fri, 19 Sep 2025 18:02:02 +0100 Subject: [PATCH 5/7] joe comments --- .../java/org/elasticsearch/TransportVersions.java | 1 + .../action/stats/GetSecurityStatsNodeRequest.java | 4 ++++ .../org/elasticsearch/xpack/security/Security.java | 2 +- .../xpack/security/SecurityFeatures.java | 2 +- .../rest/action/stats/RestSecurityStatsAction.java | 13 ++++++++++++- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index e3b97c13686a3..88ae83cfe2850 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -334,6 +334,7 @@ static TransportVersion def(int id) { public static final TransportVersion INFERENCE_REQUEST_ADAPTIVE_RATE_LIMITING_REMOVED = def(9_164_0_00); public static final TransportVersion SEARCH_SOURCE_EXCLUDE_INFERENCE_FIELDS_PARAM = def(9_165_0_00); public static final TransportVersion INFERENCE_RESULTS_MAP_WITH_CLUSTER_ALIAS = def(9_166_0_00); + public static final TransportVersion SECURITY_STATS_ENDPOINT = def(9_167_0_00); /* * STOP! READ THIS FIRST! No, really, diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java index 827c6e0b4c533..03978ed3e3da9 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.core.security.action.stats; +import org.elasticsearch.TransportVersions; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.transport.AbstractTransportRequest; @@ -23,6 +24,9 @@ public GetSecurityStatsNodeRequest(final StreamInput in) throws IOException { @Override public void writeTo(final StreamOutput out) throws IOException { + if (out.getTransportVersion().before(TransportVersions.SECURITY_STATS_ENDPOINT)) { // shouldn't happen + throw new UnsupportedOperationException("node doesn't support security stats endpoint"); + } super.writeTo(out); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index aa03a62879502..c36c9b3b350fe 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -1828,7 +1828,7 @@ public List getRestHandlers( new RestDisableProfileAction(settings, getLicenseState()), new RestGetSecuritySettingsAction(settings, getLicenseState()), new RestUpdateSecuritySettingsAction(settings, getLicenseState()), - new RestSecurityStatsAction(settings, getLicenseState()) + new RestSecurityStatsAction(settings, getLicenseState(), clusterSupportsFeature) ).filter(Objects::nonNull).toList(); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java index 215a5ca5dd359..8c93003def79f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java @@ -16,7 +16,7 @@ public class SecurityFeatures implements FeatureSpecification { - private static final NodeFeature SECURITY_STATS_ENDPOINT = new NodeFeature("security_stats_endpoint"); + public static final NodeFeature SECURITY_STATS_ENDPOINT = new NodeFeature("security_stats_endpoint"); @Override public Set getFeatures() { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java index 61877422f045c..de0a70ed6f15a 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java @@ -8,6 +8,7 @@ import org.elasticsearch.client.internal.node.NodeClient; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.features.NodeFeature; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.Scope; @@ -15,17 +16,24 @@ import org.elasticsearch.rest.action.RestToXContentListener; import org.elasticsearch.xpack.core.security.action.stats.GetSecurityStatsAction; import org.elasticsearch.xpack.core.security.action.stats.GetSecurityStatsNodesRequest; +import org.elasticsearch.xpack.security.SecurityFeatures; import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; import java.util.List; +import java.util.function.Predicate; import static org.elasticsearch.rest.RestRequest.Method.GET; @ServerlessScope(Scope.INTERNAL) public class RestSecurityStatsAction extends SecurityBaseRestHandler { - public RestSecurityStatsAction(final Settings settings, final XPackLicenseState licenseState) { + private final Predicate clusterSupportsFeature; + + public RestSecurityStatsAction(final Settings settings, + final XPackLicenseState licenseState, + final Predicate clusterSupportsFeature) { super(settings, licenseState); + this.clusterSupportsFeature = clusterSupportsFeature; } @Override @@ -40,6 +48,9 @@ public String getName() { @Override public RestChannelConsumer innerPrepareRequest(final RestRequest request, final NodeClient client) { + if (clusterSupportsFeature.test(SecurityFeatures.SECURITY_STATS_ENDPOINT) == false) { + throw new IllegalArgumentException("endpoint not supported on all nodes in the cluster"); + } final var req = new GetSecurityStatsNodesRequest(); return channel -> client.execute(GetSecurityStatsAction.INSTANCE, req, new RestToXContentListener<>(channel)); } From b795b2a0376cd5d3d3ffbb9331ce3317557506a0 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 19 Sep 2025 18:28:42 +0000 Subject: [PATCH 6/7] [CI] Auto commit changes from spotless --- .../rest/action/stats/RestSecurityStatsAction.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java index de0a70ed6f15a..435b6a01b6faa 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/stats/RestSecurityStatsAction.java @@ -29,9 +29,11 @@ public class RestSecurityStatsAction extends SecurityBaseRestHandler { private final Predicate clusterSupportsFeature; - public RestSecurityStatsAction(final Settings settings, - final XPackLicenseState licenseState, - final Predicate clusterSupportsFeature) { + public RestSecurityStatsAction( + final Settings settings, + final XPackLicenseState licenseState, + final Predicate clusterSupportsFeature + ) { super(settings, licenseState); this.clusterSupportsFeature = clusterSupportsFeature; } From 45db05c0095aa0fa39f478f8fbe939fd99c67f71 Mon Sep 17 00:00:00 2001 From: Szymon Bialkowski Date: Mon, 22 Sep 2025 11:30:19 +0100 Subject: [PATCH 7/7] use new transport version --- .../definitions/referable/security_stats_endpoint.csv | 1 + server/src/main/resources/transport/upper_bounds/9.2.csv | 2 +- .../security/action/stats/GetSecurityStatsNodeRequest.java | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 server/src/main/resources/transport/definitions/referable/security_stats_endpoint.csv diff --git a/server/src/main/resources/transport/definitions/referable/security_stats_endpoint.csv b/server/src/main/resources/transport/definitions/referable/security_stats_endpoint.csv new file mode 100644 index 0000000000000..40081d05c7097 --- /dev/null +++ b/server/src/main/resources/transport/definitions/referable/security_stats_endpoint.csv @@ -0,0 +1 @@ +9168000 diff --git a/server/src/main/resources/transport/upper_bounds/9.2.csv b/server/src/main/resources/transport/upper_bounds/9.2.csv index bf1a90e5be4e9..6e7d51d3d3020 100644 --- a/server/src/main/resources/transport/upper_bounds/9.2.csv +++ b/server/src/main/resources/transport/upper_bounds/9.2.csv @@ -1 +1 @@ -index_request_include_tsid,9167000 +security_stats_endpoint,9168000 diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java index 03978ed3e3da9..19ed2331531a0 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/stats/GetSecurityStatsNodeRequest.java @@ -7,7 +7,7 @@ package org.elasticsearch.xpack.core.security.action.stats; -import org.elasticsearch.TransportVersions; +import org.elasticsearch.TransportVersion; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.transport.AbstractTransportRequest; @@ -16,6 +16,8 @@ public class GetSecurityStatsNodeRequest extends AbstractTransportRequest { + private static final TransportVersion SECURITY_STATS_ENDPOINT = TransportVersion.fromName("security_stats_endpoint"); + public GetSecurityStatsNodeRequest() {} public GetSecurityStatsNodeRequest(final StreamInput in) throws IOException { @@ -24,7 +26,7 @@ public GetSecurityStatsNodeRequest(final StreamInput in) throws IOException { @Override public void writeTo(final StreamOutput out) throws IOException { - if (out.getTransportVersion().before(TransportVersions.SECURITY_STATS_ENDPOINT)) { // shouldn't happen + if (out.getTransportVersion().supports(SECURITY_STATS_ENDPOINT) == false) { // shouldn't happen, blocked at RestAction throw new UnsupportedOperationException("node doesn't support security stats endpoint"); } super.writeTo(out);