Skip to content

Conversation

@cwperks
Copy link
Member

@cwperks cwperks commented Nov 6, 2025

Description

This is another optimization for GET _mapping calls when running with the security plugin. This PR is similar to #5752 and avoids calling dlsFlsValve.isFieldAllowed(index, field, ctx) for indices without restrictions. With the changes in this PR, security will only return a predicate to filter fields on indices that have FLS restrictions for the current user.

  • Category (Enhancement, New feature, Bug fix, Test fix, Refactoring, Maintenance, Documentation)

Enhancement

Check List

  • New functionality includes testing
  • New functionality has been documented
  • New Roles/Permissions have a corresponding security dashboards plugin PR
  • API changes companion pull request created
  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

… restrictions for user

Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
@codecov
Copy link

codecov bot commented Nov 6, 2025

Codecov Report

❌ Patch coverage is 69.23077% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.74%. Comparing base (46e5937) to head (f68f89a).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
.../opensearch/security/OpenSearchSecurityPlugin.java 62.50% 2 Missing and 1 partial ⚠️
...rch/security/configuration/DlsFlsRequestValve.java 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #5777      +/-   ##
==========================================
+ Coverage   73.66%   73.74%   +0.08%     
==========================================
  Files         438      438              
  Lines       26660    26654       -6     
  Branches     3939     3939              
==========================================
+ Hits        19638    19655      +17     
+ Misses       5148     5126      -22     
+ Partials     1874     1873       -1     
Files with missing lines Coverage Δ
...search/security/configuration/DlsFlsValveImpl.java 65.52% <100.00%> (+0.74%) ⬆️
...rch/security/configuration/DlsFlsRequestValve.java 0.00% <0.00%> (ø)
.../opensearch/security/OpenSearchSecurityPlugin.java 85.26% <62.50%> (-0.12%) ⬇️

... and 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@nibix
Copy link
Collaborator

nibix commented Nov 7, 2025

Are there any hot thread dumps or profiling data available for cases where performance issues were observed? Would be curious to have a look at it.

nibix
nibix previously approved these changes Nov 7, 2025
Copy link
Collaborator

@nibix nibix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Yet, this only covers the case when no FLS protection is present at all. If there is only a single protected field, we get the old performance profile.

It might be worthwhile to have a closer look at the cause of the issues in order to be able to improve these scenarios as well.

I could take a look at that.

@cwperks
Copy link
Member Author

cwperks commented Nov 7, 2025

Approving. Yet, this only covers the case when no FLS protection is present at all. If there is only a single protected field, we get the old performance profile.

It might be worthwhile to have a closer look at the cause of the issues in order to be able to improve these scenarios as well.

I could take a look at that.

The main cause is lack of resiliency measures to the GET _mapping API so any optimization in security is only of marginal gain IMO.

I believe I've identified an opportunity to de-dup indices with the same mappings in the cluster state (for example rolled over indices).

FYI In core I tried also adding some resiliency measures for GET _mapping API:

I'm looking into a 3rd improvement in the core as well to cut the in-memory size of the cluster state by keeping a mapping pool.

@cwperks
Copy link
Member Author

cwperks commented Nov 7, 2025

Example hot threads dump:

100.6% (502.7ms out of 500ms) cpu usage by thread 'opensearch[2f72d051cfbbf133def1d23e6fef7768][transport_worker][T#3]'
     2/10 snapshots sharing following 113 elements
       app//org.opensearch.core.xcontent.AbstractXContentParser.readMapEntries(AbstractXContentParser.java:336)
       app//org.opensearch.core.xcontent.AbstractXContentParser.readValueUnsafe(AbstractXContentParser.java:415)
       app//org.opensearch.core.xcontent.AbstractXContentParser.readMapEntries(AbstractXContentParser.java:336)
       app//org.opensearch.core.xcontent.AbstractXContentParser.readMapSafe(AbstractXContentParser.java:322)
       app//org.opensearch.core.xcontent.AbstractXContentParser.mapOrdered(AbstractXContentParser.java:277)
       app//org.opensearch.common.xcontent.XContentHelper.convertToMap(XContentHelper.java:224)
       app//org.opensearch.common.xcontent.XContentHelper.convertToMap(XContentHelper.java:185)
       app//org.opensearch.common.xcontent.XContentHelper.convertToMap(XContentHelper.java:155)
       app//org.opensearch.common.xcontent.XContentHelper.convertToMap(XContentHelper.java:140)
       app//org.opensearch.cluster.metadata.Metadata.filterFields(Metadata.java:568)
       app//org.opensearch.cluster.metadata.Metadata.lambda$findMappings$1(Metadata.java:537)
       app//org.opensearch.cluster.metadata.Metadata$$Lambda/0x000000d8039aafa8.accept(Unknown Source)
       java.base@21.0.8/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
       java.base@21.0.8/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
       java.base@21.0.8/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024)
       java.base@21.0.8/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
       java.base@21.0.8/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
       java.base@21.0.8/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
       java.base@21.0.8/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
       java.base@21.0.8/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
       java.base@21.0.8/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
       app//org.opensearch.cluster.metadata.Metadata.findMappings(Metadata.java:537)
       app//org.opensearch.action.admin.indices.mapping.get.TransportGetMappingsAction.doClusterManagerOperation(TransportGetMappingsAction.java:99)
       app//org.opensearch.action.admin.indices.mapping.get.TransportGetMappingsAction.doClusterManagerOperation(TransportGetMappingsAction.java:58)
       app//org.opensearch.action.support.clustermanager.info.TransportClusterInfoAction.clusterManagerOperation(TransportClusterInfoAction.java:83)
       app//org.opensearch.action.support.clustermanager.info.TransportClusterInfoAction.clusterManagerOperation(TransportClusterInfoAction.java:52)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction.clusterManagerOperation(TransportClusterManagerNodeAction.java:173)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction$AsyncSingleAction.lambda$doStart$0(TransportClusterManagerNodeAction.java:262)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction$AsyncSingleAction$$Lambda/0x000000d80371ba48.accept(Unknown Source)
       app//org.opensearch.action.ActionRunnable$2.doRun(ActionRunnable.java:89)
       app//org.opensearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:52)
       app//org.opensearch.common.util.concurrent.OpenSearchExecutors$DirectExecutorService.execute(OpenSearchExecutors.java:399)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction$AsyncSingleAction.doStart(TransportClusterManagerNodeAction.java:259)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction$AsyncSingleAction.tryAction(TransportClusterManagerNodeAction.java:226)
       app//org.opensearch.action.support.RetryableAction$1.doRun(RetryableAction.java:139)
       app//org.opensearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:52)
       app//org.opensearch.common.util.concurrent.OpenSearchExecutors$DirectExecutorService.execute(OpenSearchExecutors.java:399)
       app//org.opensearch.action.support.RetryableAction.run(RetryableAction.java:117)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction.doExecute(TransportClusterManagerNodeAction.java:187)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction.doExecute(TransportClusterManagerNodeAction.java:92)
       app//org.opensearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:220)
       org.opensearch.indexmanagement.controlcenter.notification.filter.IndexOperationActionFilter.apply(IndexOperationActionFilter.kt:39)
       app//org.opensearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:218)
       org.opensearch.plugin.wlm.AutoTaggingActionFilter.apply(AutoTaggingActionFilter.java:60)
       app//org.opensearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:218)
       org.opensearch.knn.plugin.transport.actionfilters.ResizeExclusionActionFilterForS3Engine.apply(ResizeExclusionActionFilterForS3Engine.java:74)
       app//org.opensearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:218)
       org.opensearch.indexmanagement.rollup.actionfilter.FieldCapsFilter.apply(FieldCapsFilter.kt:118)
       app//org.opensearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:218)
       org.opensearch.performanceanalyzer.action.PerformanceAnalyzerActionFilter.apply(PerformanceAnalyzerActionFilter.java:81)
       app//org.opensearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:218)
       org.opensearch.security.filter.SecurityFilter.apply0(SecurityFilter.java:399)
       org.opensearch.security.filter.SecurityFilter.apply(SecurityFilter.java:170)
       app//org.opensearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:218)
       app//org.opensearch.action.support.TransportAction.execute(TransportAction.java:190)
       app//org.opensearch.action.support.HandledTransportAction$TransportHandler.messageReceived(HandledTransportAction.java:133)
       app//org.opensearch.action.support.HandledTransportAction$TransportHandler.messageReceived(HandledTransportAction.java:129)
       com.amazonaws.elasticsearch.ccs.CrossClusterRequestInterceptor$CrossClusterRequestHandler.messageReceived(CrossClusterRequestInterceptor.java:155)
       org.opensearch.indexmanagement.rollup.interceptor.RollupInterceptor$interceptHandler$1.messageReceived(RollupInterceptor.kt:118)
       org.opensearch.performanceanalyzer.transport.RTFPerformanceAnalyzerTransportRequestHandler.messageReceived(RTFPerformanceAnalyzerTransportRequestHandler.java:92)
       org.opensearch.security.ssl.transport.SecuritySSLRequestHandler.messageReceivedDecorate(SecuritySSLRequestHandler.java:202)
       org.opensearch.security.transport.SecurityRequestHandler.messageReceivedDecorate(SecurityRequestHandler.java:329)
       org.opensearch.security.ssl.transport.SecuritySSLRequestHandler.messageReceived(SecuritySSLRequestHandler.java:150)
       org.opensearch.security.OpenSearchSecurityPlugin$6$1.messageReceived(OpenSearchSecurityPlugin.java:925)
       com.amazonaws.elasticsearch.iam.IamTransportRequestHandler.messageReceived(IamTransportRequestHandler.java:69)
       app//org.opensearch.ratelimitting.admissioncontrol.transport.AdmissionControlTransportHandler.messageReceived(AdmissionControlTransportHandler.java:68)
       app//org.opensearch.wlm.WorkloadManagementTransportInterceptor$RequestHandler.messageReceived(WorkloadManagementTransportInterceptor.java:63)
       app//org.opensearch.transport.RequestHandlerRegistry.processMessageReceived(RequestHandlerRegistry.java:108)
       app//org.opensearch.transport.NativeMessageHandler.handleRequest(NativeMessageHandler.java:278)
       app//org.opensearch.transport.NativeMessageHandler.handleMessage(NativeMessageHandler.java:146)
       app//org.opensearch.transport.NativeMessageHandler.messageReceived(NativeMessageHandler.java:126)
       app//org.opensearch.transport.InboundHandler.messageReceivedFromPipeline(InboundHandler.java:120)
       app//org.opensearch.transport.InboundHandler.inboundMessage(InboundHandler.java:112)
       app//org.opensearch.transport.TcpTransport.inboundMessage(TcpTransport.java:768)
       org.opensearch.transport.netty4.Netty4MessageChannelHandler$$Lambda/0x000000d803664910.accept(Unknown Source)
       app//org.opensearch.transport.InboundBytesHandler.forwardFragments(InboundBytesHandler.java:137)
       app//org.opensearch.transport.InboundBytesHandler.doHandleBytes(InboundBytesHandler.java:77)
       app//org.opensearch.transport.InboundPipeline.doHandleBytes(InboundPipeline.java:124)
       app//org.opensearch.transport.InboundPipeline.handleBytes(InboundPipeline.java:113)
       org.opensearch.transport.netty4.Netty4MessageChannelHandler.channelRead(Netty4MessageChannelHandler.java:95)
       io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
       io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
       io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
       io.netty.handler.logging.LoggingHandler.channelRead(LoggingHandler.java:280)
       io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
       io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
       io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
       io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:107)
       io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
       io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
       io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
       io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1519)
       io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1377)
       io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1428)
       io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530)
       io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469)
       io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
       io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
       io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
       io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
       io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357)
       io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
       io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
       io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868)
       io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
       io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:796)
       io.netty.channel.nio.NioEventLoop.processSelectedKeysPlain(NioEventLoop.java:697)
       io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:660)
       io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
       io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
       io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
       java.base@21.0.8/java.lang.Thread.runWith(Thread.java:1596)
       java.base@21.0.8/java.lang.Thread.run(Thread.java:1583)
     3/10 snapshots sharing following 121 elements
       java.base@21.0.8/java.lang.Throwable.fillInStackTrace(Native Method)
       java.base@21.0.8/java.lang.Throwable.fillInStackTrace(Throwable.java:820)
       java.base@21.0.8/java.lang.Throwable.<init>(Throwable.java:273)
       java.base@21.0.8/java.lang.Exception.<init>(Exception.java:67)
       java.naming@21.0.8/javax.naming.NamingException.<init>(NamingException.java:127)
       java.naming@21.0.8/javax.naming.InvalidNameException.<init>(InvalidNameException.java:57)
       java.naming@21.0.8/javax.naming.ldap.Rfc2253Parser.doParse(Rfc2253Parser.java:111)
       java.naming@21.0.8/javax.naming.ldap.Rfc2253Parser.parseDn(Rfc2253Parser.java:70)
       java.naming@21.0.8/javax.naming.ldap.LdapName.parse(LdapName.java:806)
       java.naming@21.0.8/javax.naming.ldap.LdapName.<init>(LdapName.java:125)
       org.opensearch.security.configuration.AdminDNs.isAdminDN(AdminDNs.java:139)
       org.opensearch.security.configuration.AdminDNs.isAdmin(AdminDNs.java:123)
       org.opensearch.security.privileges.dlsfls.DlsFlsBaseContext.getPrivilegesEvaluationContext(DlsFlsBaseContext.java:42)
       org.opensearch.security.configuration.DlsFlsValveImpl.isFieldAllowed(DlsFlsValveImpl.java:491)
       org.opensearch.security.OpenSearchSecurityPlugin.lambda$getFieldFilter$12(OpenSearchSecurityPlugin.java:2231)
       org.opensearch.security.OpenSearchSecurityPlugin$$Lambda/0x000000d803956a78.test(Unknown Source)
       app//org.opensearch.cluster.metadata.Metadata.filterFields(Metadata.java:615)
       app//org.opensearch.cluster.metadata.Metadata.filterFields(Metadata.java:581)
       app//org.opensearch.cluster.metadata.Metadata.lambda$findMappings$1(Metadata.java:537)
       app//org.opensearch.cluster.metadata.Metadata$$Lambda/0x000000d8039aafa8.accept(Unknown Source)
       java.base@21.0.8/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
       java.base@21.0.8/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
       java.base@21.0.8/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024)
       java.base@21.0.8/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
       java.base@21.0.8/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
       java.base@21.0.8/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
       java.base@21.0.8/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
       java.base@21.0.8/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
       java.base@21.0.8/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
       app//org.opensearch.cluster.metadata.Metadata.findMappings(Metadata.java:537)
       app//org.opensearch.action.admin.indices.mapping.get.TransportGetMappingsAction.doClusterManagerOperation(TransportGetMappingsAction.java:99)
       app//org.opensearch.action.admin.indices.mapping.get.TransportGetMappingsAction.doClusterManagerOperation(TransportGetMappingsAction.java:58)
       app//org.opensearch.action.support.clustermanager.info.TransportClusterInfoAction.clusterManagerOperation(TransportClusterInfoAction.java:83)
       app//org.opensearch.action.support.clustermanager.info.TransportClusterInfoAction.clusterManagerOperation(TransportClusterInfoAction.java:52)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction.clusterManagerOperation(TransportClusterManagerNodeAction.java:173)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction$AsyncSingleAction.lambda$doStart$0(TransportClusterManagerNodeAction.java:262)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction$AsyncSingleAction$$Lambda/0x000000d80371ba48.accept(Unknown Source)
       app//org.opensearch.action.ActionRunnable$2.doRun(ActionRunnable.java:89)
       app//org.opensearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:52)
       app//org.opensearch.common.util.concurrent.OpenSearchExecutors$DirectExecutorService.execute(OpenSearchExecutors.java:399)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction$AsyncSingleAction.doStart(TransportClusterManagerNodeAction.java:259)
       app//org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction$AsyncSingleAction.tryAction(TransportClusterManagerNodeAction.java:226)

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@DarshitChanpura DarshitChanpura dismissed stale reviews from nibix and themself via 2a3965b November 26, 2025 18:02
@DarshitChanpura
Copy link
Member

opensearch-project/OpenSearch#19803
Blocks the CI.

@cwperks
Copy link
Member Author

cwperks commented Nov 26, 2025

opensearch-project/OpenSearch#19803 Blocks the CI.

Planning to fix that in #5815

Signed-off-by: Craig Perkins <cwperx@amazon.com>
try {
indexHasRestrictions = dlsFlsValve.indexHasFlsRestrictions(index, ctx);
} catch (PrivilegesEvaluationException e) {
log.error("Error while evaluating FLS restrictions for {}", index, e);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the exception be re-thrown?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this block into the try-catch

if (!indexHasRestrictions) {
    return NOOP_FIELD_PREDICATE;
}

In the event of an error, it will use the existing field-level check which I think is sensible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants