|
12 | 12 |
|
13 | 13 | import org.apache.logging.log4j.LogManager;
|
14 | 14 | import org.apache.logging.log4j.Logger;
|
| 15 | +import org.apache.lucene.index.DirectoryReader; |
15 | 16 | import org.apache.lucene.util.SetOnce;
|
16 | 17 | import org.elasticsearch.ElasticsearchSecurityException;
|
17 | 18 | import org.elasticsearch.ElasticsearchStatusException;
|
|
49 | 50 | import org.elasticsearch.common.util.concurrent.EsExecutors;
|
50 | 51 | import org.elasticsearch.common.util.concurrent.ThreadContext;
|
51 | 52 | import org.elasticsearch.common.util.set.Sets;
|
| 53 | +import org.elasticsearch.core.CheckedFunction; |
52 | 54 | import org.elasticsearch.core.Nullable;
|
53 | 55 | import org.elasticsearch.env.Environment;
|
54 | 56 | import org.elasticsearch.env.NodeEnvironment;
|
|
58 | 60 | import org.elasticsearch.http.netty4.internal.HttpHeadersAuthenticatorUtils;
|
59 | 61 | import org.elasticsearch.http.netty4.internal.HttpValidator;
|
60 | 62 | import org.elasticsearch.index.IndexModule;
|
| 63 | +import org.elasticsearch.index.IndexService; |
61 | 64 | import org.elasticsearch.indices.ExecutorNames;
|
62 | 65 | import org.elasticsearch.indices.SystemIndexDescriptor;
|
63 | 66 | import org.elasticsearch.indices.breaker.CircuitBreakerService;
|
@@ -1159,33 +1162,71 @@ public List<BootstrapCheck> getBootstrapChecks() {
|
1159 | 1162 | return bootstrapChecks.get();
|
1160 | 1163 | }
|
1161 | 1164 |
|
| 1165 | + /** |
| 1166 | + * Constructs a composed {@link CheckedFunction} chain that wraps a {@link DirectoryReader} |
| 1167 | + * with multiple reader-level security layers, including built-in DLS/FLS support and |
| 1168 | + * pluggable extensions (e.g., field masking). |
| 1169 | + * |
| 1170 | + * <p>This method is called per index and returns a function that applies each |
| 1171 | + * {@link DirectoryReader} wrapper in order, ensuring all registeredregistered security logic |
| 1172 | + * is applied consistently. |
| 1173 | + * |
| 1174 | + * @param indexService the {@link IndexService} associated with the index |
| 1175 | + * @return a composed function that wraps a {@link DirectoryReader} with all applicable security wrappers |
| 1176 | + */ |
| 1177 | + private CheckedFunction<DirectoryReader, DirectoryReader, IOException> buildReaderWrapperChain(IndexService indexService){ |
| 1178 | + // Create the core SecurityIndexReaderWrapper which enforces DLS/FLS |
| 1179 | + SecurityIndexReaderWrapper securityWrapper = new SecurityIndexReaderWrapper( |
| 1180 | + shardId -> indexService.newSearchExecutionContext( |
| 1181 | + shardId.id(), |
| 1182 | + 0, |
| 1183 | + // we pass a null index reader, which is legal and will disable rewrite optimizations |
| 1184 | + // based on index statistics, which is probably safer... |
| 1185 | + null, |
| 1186 | + () -> { |
| 1187 | + throw new IllegalArgumentException("permission filters are not allowed to use the current timestamp"); |
| 1188 | + |
| 1189 | + }, |
| 1190 | + null, |
| 1191 | + // Don't use runtime mappings in the security query |
| 1192 | + emptyMap() |
| 1193 | + ), |
| 1194 | + dlsBitsetCache.get(), |
| 1195 | + securityContext.get(), |
| 1196 | + getLicenseState(), |
| 1197 | + indexService.getScriptService() |
| 1198 | + ); |
| 1199 | + |
| 1200 | + // Initialize wrapper chain with core security logic |
| 1201 | + List<CheckedFunction<DirectoryReader, DirectoryReader, IOException>> wrappers = new ArrayList<>(); |
| 1202 | + wrappers.add(securityWrapper); |
| 1203 | + |
| 1204 | + // Add any additional reader wrappers provided by security extensions (e.g., field masking) |
| 1205 | + for (SecurityExtension securityExtension : securityExtensions) { |
| 1206 | + CheckedFunction<DirectoryReader, DirectoryReader, IOException> wrapper = securityExtension.getIndexReaderWrapper(securityContext.get()); |
| 1207 | + if(wrapper !=null ){ |
| 1208 | + wrappers.add(wrapper); |
| 1209 | + } |
| 1210 | + } |
| 1211 | + |
| 1212 | + // Return a composed function that applies all wrappers in sequence |
| 1213 | + return reader -> { |
| 1214 | + DirectoryReader current = reader; |
| 1215 | + for (CheckedFunction<DirectoryReader, DirectoryReader, IOException> wrapper : wrappers) { |
| 1216 | + current = wrapper.apply(current); |
| 1217 | + } |
| 1218 | + return current; |
| 1219 | + }; |
| 1220 | + } |
| 1221 | + |
1162 | 1222 | @Override
|
1163 | 1223 | public void onIndexModule(IndexModule module) {
|
1164 | 1224 | if (enabled) {
|
1165 | 1225 | assert getLicenseState() != null;
|
1166 | 1226 | if (XPackSettings.DLS_FLS_ENABLED.get(settings)) {
|
1167 | 1227 | assert dlsBitsetCache.get() != null;
|
1168 | 1228 | module.setReaderWrapper(
|
1169 |
| - indexService -> new SecurityIndexReaderWrapper( |
1170 |
| - shardId -> indexService.newSearchExecutionContext( |
1171 |
| - shardId.id(), |
1172 |
| - 0, |
1173 |
| - // we pass a null index reader, which is legal and will disable rewrite optimizations |
1174 |
| - // based on index statistics, which is probably safer... |
1175 |
| - null, |
1176 |
| - () -> { |
1177 |
| - throw new IllegalArgumentException("permission filters are not allowed to use the current timestamp"); |
1178 |
| - |
1179 |
| - }, |
1180 |
| - null, |
1181 |
| - // Don't use runtime mappings in the security query |
1182 |
| - emptyMap() |
1183 |
| - ), |
1184 |
| - dlsBitsetCache.get(), |
1185 |
| - securityContext.get(), |
1186 |
| - getLicenseState(), |
1187 |
| - indexService.getScriptService() |
1188 |
| - ) |
| 1229 | + indexService -> buildReaderWrapperChain(indexService) |
1189 | 1230 | );
|
1190 | 1231 | /*
|
1191 | 1232 | * We need to forcefully overwrite the query cache implementation to use security's opt-out query cache implementation. This
|
|
0 commit comments