Skip to content

Commit 7b3a9c7

Browse files
authored
Do not refresh realm cache unless required (#42212)
If there are no realms that depend on the native role mapping store, then changes should it should not perform any cache refresh. A refresh with an empty realm array will refresh all realms. This also fixes a spurious log warning that could occur if the role mapping store was notified that the security index was recovered before any realm were attached. Backport of: #42169
1 parent 7abeaba commit 7b3a9c7

File tree

2 files changed

+45
-20
lines changed

2 files changed

+45
-20
lines changed

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.elasticsearch.action.support.ContextPreservingActionListener;
1616
import org.elasticsearch.client.Client;
1717
import org.elasticsearch.common.CheckedBiConsumer;
18+
import org.elasticsearch.common.Strings;
1819
import org.elasticsearch.common.bytes.BytesReference;
1920
import org.elasticsearch.common.settings.Settings;
2021
import org.elasticsearch.common.util.concurrent.ThreadContext;
@@ -329,7 +330,12 @@ public void onSecurityIndexStateChange(SecurityIndexManager.State previousState,
329330
}
330331

331332
private <Result> void refreshRealms(ActionListener<Result> listener, Result result) {
332-
String[] realmNames = this.realmsToRefresh.toArray(new String[realmsToRefresh.size()]);
333+
if (realmsToRefresh.isEmpty()) {
334+
listener.onResponse(result);
335+
return;
336+
}
337+
338+
final String[] realmNames = this.realmsToRefresh.toArray(Strings.EMPTY_ARRAY);
333339
final SecurityClient securityClient = new SecurityClient(client);
334340
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN,
335341
securityClient.prepareClearRealmCache().realms(realmNames).request(),
@@ -340,7 +346,7 @@ private <Result> void refreshRealms(ActionListener<Result> listener, Result resu
340346
listener.onResponse(result);
341347
},
342348
ex -> {
343-
logger.warn("Failed to clear cache for realms [{}]", Arrays.toString(realmNames));
349+
logger.warn(new ParameterizedMessage("Failed to clear cache for realms [{}]", Arrays.toString(realmNames)), ex);
344350
listener.onFailure(ex);
345351
}),
346352
securityClient::clearRealmCache);

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ private SecurityIndexManager.State dummyState(ClusterHealthStatus indexStatus) {
143143

144144
public void testCacheClearOnIndexHealthChange() {
145145
final AtomicInteger numInvalidation = new AtomicInteger(0);
146-
final NativeRoleMappingStore store = buildRoleMappingStoreForInvalidationTesting(numInvalidation);
146+
final NativeRoleMappingStore store = buildRoleMappingStoreForInvalidationTesting(numInvalidation, true);
147147

148148
int expectedInvalidation = 0;
149149
// existing to no longer present
@@ -180,7 +180,7 @@ public void testCacheClearOnIndexHealthChange() {
180180

181181
public void testCacheClearOnIndexOutOfDateChange() {
182182
final AtomicInteger numInvalidation = new AtomicInteger(0);
183-
final NativeRoleMappingStore store = buildRoleMappingStoreForInvalidationTesting(numInvalidation);
183+
final NativeRoleMappingStore store = buildRoleMappingStoreForInvalidationTesting(numInvalidation, true);
184184

185185
store.onSecurityIndexStateChange(
186186
new SecurityIndexManager.State(Instant.now(), false, true, true, null, concreteSecurityIndexName, null),
@@ -193,40 +193,59 @@ public void testCacheClearOnIndexOutOfDateChange() {
193193
assertEquals(2, numInvalidation.get());
194194
}
195195

196-
private NativeRoleMappingStore buildRoleMappingStoreForInvalidationTesting(AtomicInteger invalidationCounter) {
196+
public void testCacheIsNotClearedIfNoRealmsAreAttached() {
197+
final AtomicInteger numInvalidation = new AtomicInteger(0);
198+
final NativeRoleMappingStore store = buildRoleMappingStoreForInvalidationTesting(numInvalidation, false);
199+
200+
final SecurityIndexManager.State noIndexState = dummyState(null);
201+
final SecurityIndexManager.State greenIndexState = dummyState(ClusterHealthStatus.GREEN);
202+
store.onSecurityIndexStateChange(noIndexState, greenIndexState);
203+
assertEquals(0, numInvalidation.get());
204+
}
205+
206+
private NativeRoleMappingStore buildRoleMappingStoreForInvalidationTesting(AtomicInteger invalidationCounter, boolean attachRealm) {
197207
final Settings settings = Settings.builder().put("path.home", createTempDir()).build();
198208

199209
final ThreadPool threadPool = mock(ThreadPool.class);
200210
final ThreadContext threadContext = new ThreadContext(settings);
201211
when(threadPool.getThreadContext()).thenReturn(threadContext);
202212

213+
final String realmName = randomAlphaOfLengthBetween(4, 8);
214+
203215
final Client client = mock(Client.class);
204216
when(client.threadPool()).thenReturn(threadPool);
205217
when(client.settings()).thenReturn(settings);
206218
doAnswer(invocationOnMock -> {
219+
assertThat(invocationOnMock.getArguments(), Matchers.arrayWithSize(3));
220+
final ClearRealmCacheRequest request = (ClearRealmCacheRequest) invocationOnMock.getArguments()[1];
221+
assertThat(request.realms(), Matchers.arrayContaining(realmName));
222+
207223
ActionListener<ClearRealmCacheResponse> listener = (ActionListener<ClearRealmCacheResponse>) invocationOnMock.getArguments()[2];
208224
invalidationCounter.incrementAndGet();
209225
listener.onResponse(new ClearRealmCacheResponse(new ClusterName("cluster"), Collections.emptyList(), Collections.emptyList()));
210226
return null;
211227
}).when(client).execute(eq(ClearRealmCacheAction.INSTANCE), any(ClearRealmCacheRequest.class), any(ActionListener.class));
212228

213-
final Environment env = TestEnvironment.newEnvironment(settings);
214-
final RealmConfig realmConfig = new RealmConfig(new RealmConfig.RealmIdentifier("ldap", getTestName()),
215-
settings, env, threadContext);
216-
final CachingUsernamePasswordRealm mockRealm = new CachingUsernamePasswordRealm(realmConfig, threadPool) {
217-
@Override
218-
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener) {
219-
listener.onResponse(AuthenticationResult.notHandled());
220-
}
221-
222-
@Override
223-
protected void doLookupUser(String username, ActionListener<User> listener) {
224-
listener.onResponse(null);
225-
}
226-
};
227229
final NativeRoleMappingStore store = new NativeRoleMappingStore(Settings.EMPTY, client, mock(SecurityIndexManager.class),
228230
mock(ScriptService.class));
229-
store.refreshRealmOnChange(mockRealm);
231+
232+
if (attachRealm) {
233+
final Environment env = TestEnvironment.newEnvironment(settings);
234+
final RealmConfig.RealmIdentifier identifier = new RealmConfig.RealmIdentifier("ldap", realmName);
235+
final RealmConfig realmConfig = new RealmConfig(identifier, settings, env, threadContext);
236+
final CachingUsernamePasswordRealm mockRealm = new CachingUsernamePasswordRealm(realmConfig, threadPool) {
237+
@Override
238+
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener) {
239+
listener.onResponse(AuthenticationResult.notHandled());
240+
}
241+
242+
@Override
243+
protected void doLookupUser(String username, ActionListener<User> listener) {
244+
listener.onResponse(null);
245+
}
246+
};
247+
store.refreshRealmOnChange(mockRealm);
248+
}
230249
return store;
231250
}
232251
}

0 commit comments

Comments
 (0)