Skip to content

Commit ba91f26

Browse files
authored
User Profile - Use security origin for BWC cases (#86345)
The security profile action origin and internal user is not available before version 8.3.0. This PR makes all profile actions to use the existing security origin if the cluster has any node that is older than 8.3.0. The change makes it possible to use User Profile features in a mixed cluster as long as the request always hits a newer (newer than 8.3) node first.
1 parent bbd5c84 commit ba91f26

File tree

6 files changed

+131
-32
lines changed

6 files changed

+131
-32
lines changed

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,7 @@ Collection<Object> createComponents(
769769
getClock(),
770770
client,
771771
systemIndices.getProfileIndexManager(),
772+
clusterService,
772773
threadPool
773774
);
774775
components.add(profileService);

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/profile/ProfileService.java

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.elasticsearch.action.update.UpdateRequestBuilder;
3232
import org.elasticsearch.action.update.UpdateResponse;
3333
import org.elasticsearch.client.internal.Client;
34+
import org.elasticsearch.cluster.service.ClusterService;
3435
import org.elasticsearch.common.Strings;
3536
import org.elasticsearch.common.bytes.BytesReference;
3637
import org.elasticsearch.common.settings.Settings;
@@ -75,10 +76,12 @@
7576
import java.util.stream.Collectors;
7677

7778
import static org.elasticsearch.action.bulk.TransportSingleItemBulkWriteAction.toSingleItemBulkRequest;
79+
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
7880
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_PROFILE_ORIGIN;
7981
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
8082
import static org.elasticsearch.xpack.core.security.authc.Authentication.isFileOrNativeRealm;
8183
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_PROFILE_ALIAS;
84+
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.VERSION_SECURITY_PROFILE_ORIGIN;
8285

8386
public class ProfileService {
8487
private static final Logger logger = LogManager.getLogger(ProfileService.class);
@@ -90,13 +93,22 @@ public class ProfileService {
9093
private final Clock clock;
9194
private final Client client;
9295
private final SecurityIndexManager profileIndex;
96+
private final ClusterService clusterService;
9397
private final ThreadPool threadPool;
9498

95-
public ProfileService(Settings settings, Clock clock, Client client, SecurityIndexManager profileIndex, ThreadPool threadPool) {
99+
public ProfileService(
100+
Settings settings,
101+
Clock clock,
102+
Client client,
103+
SecurityIndexManager profileIndex,
104+
ClusterService clusterService,
105+
ThreadPool threadPool
106+
) {
96107
this.settings = settings;
97108
this.clock = clock;
98109
this.client = client;
99110
this.profileIndex = profileIndex;
111+
this.clusterService = clusterService;
100112
this.threadPool = threadPool;
101113
}
102114

@@ -191,7 +203,7 @@ public void suggestProfile(SuggestProfilesRequest request, ActionListener<Sugges
191203
listener::onFailure,
192204
() -> executeAsyncWithOrigin(
193205
client,
194-
SECURITY_PROFILE_ORIGIN,
206+
getActionOrigin(),
195207
SearchAction.INSTANCE,
196208
searchRequest,
197209
ActionListener.wrap(searchResponse -> {
@@ -281,26 +293,20 @@ private void getVersionedDocument(String uid, ActionListener<VersionedDocument>
281293
final GetRequest getRequest = new GetRequest(SECURITY_PROFILE_ALIAS, uidToDocId(uid));
282294
frozenProfileIndex.checkIndexVersionThenExecute(
283295
listener::onFailure,
284-
() -> executeAsyncWithOrigin(
285-
client,
286-
SECURITY_PROFILE_ORIGIN,
287-
GetAction.INSTANCE,
288-
getRequest,
289-
ActionListener.wrap(response -> {
290-
if (false == response.isExists()) {
291-
logger.debug("profile with uid [{}] does not exist", uid);
292-
listener.onResponse(null);
293-
return;
294-
}
295-
listener.onResponse(
296-
new VersionedDocument(
297-
buildProfileDocument(response.getSourceAsBytesRef()),
298-
response.getPrimaryTerm(),
299-
response.getSeqNo()
300-
)
301-
);
302-
}, listener::onFailure)
303-
)
296+
() -> executeAsyncWithOrigin(client, getActionOrigin(), GetAction.INSTANCE, getRequest, ActionListener.wrap(response -> {
297+
if (false == response.isExists()) {
298+
logger.debug("profile with uid [{}] does not exist", uid);
299+
listener.onResponse(null);
300+
return;
301+
}
302+
listener.onResponse(
303+
new VersionedDocument(
304+
buildProfileDocument(response.getSourceAsBytesRef()),
305+
response.getPrimaryTerm(),
306+
response.getSeqNo()
307+
)
308+
);
309+
}, listener::onFailure))
304310
);
305311
});
306312
}
@@ -340,7 +346,7 @@ void searchVersionedDocumentForSubject(Subject subject, ActionListener<Versioned
340346
listener::onFailure,
341347
() -> executeAsyncWithOrigin(
342348
client,
343-
SECURITY_PROFILE_ORIGIN,
349+
getActionOrigin(),
344350
SearchAction.INSTANCE,
345351
searchRequest,
346352
ActionListener.wrap(searchResponse -> {
@@ -412,7 +418,7 @@ private void createNewProfile(Subject subject, String uid, ActionListener<Profil
412418
listener::onFailure,
413419
() -> executeAsyncWithOrigin(
414420
client,
415-
SECURITY_PROFILE_ORIGIN,
421+
getActionOrigin(),
416422
BulkAction.INSTANCE,
417423
bulkRequest,
418424
TransportSingleItemBulkWriteAction.<IndexResponse>wrapBulkResponse(ActionListener.wrap(indexResponse -> {
@@ -564,7 +570,7 @@ void doUpdate(UpdateRequest updateRequest, ActionListener<UpdateResponse> listen
564570
listener::onFailure,
565571
() -> executeAsyncWithOrigin(
566572
client,
567-
SECURITY_PROFILE_ORIGIN,
573+
getActionOrigin(),
568574
UpdateAction.INSTANCE,
569575
updateRequest,
570576
ActionListener.wrap(updateResponse -> {
@@ -576,6 +582,15 @@ void doUpdate(UpdateRequest updateRequest, ActionListener<UpdateResponse> listen
576582
);
577583
}
578584

585+
private String getActionOrigin() {
586+
// profile origin and user is not available before v8.3.0
587+
if (clusterService.state().nodes().getMinNodeVersion().onOrAfter(VERSION_SECURITY_PROFILE_ORIGIN)) {
588+
return SECURITY_PROFILE_ORIGIN;
589+
} else {
590+
return SECURITY_ORIGIN;
591+
}
592+
}
593+
579594
private static String uidToDocId(String uid) {
580595
return DOC_ID_PREFIX + uid;
581596
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecuritySystemIndices.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public class SecuritySystemIndices {
4646

4747
public static final String INTERNAL_SECURITY_PROFILE_INDEX_8 = ".security-profile-8";
4848
public static final String SECURITY_PROFILE_ALIAS = ".security-profile";
49+
public static final Version VERSION_SECURITY_PROFILE_ORIGIN = Version.V_8_3_0;
4950

5051
private final Logger logger = LogManager.getLogger(SecuritySystemIndices.class);
5152

@@ -737,8 +738,25 @@ private SystemIndexDescriptor getSecurityProfileIndexDescriptor() {
737738
.setAliasName(SECURITY_PROFILE_ALIAS)
738739
.setIndexFormat(INTERNAL_PROFILE_INDEX_FORMAT)
739740
.setVersionMetaKey(SECURITY_VERSION_STRING)
740-
.setOrigin(SECURITY_PROFILE_ORIGIN)
741+
.setOrigin(SECURITY_PROFILE_ORIGIN) // new origin since 8.3
741742
.setThreadPools(ExecutorNames.CRITICAL_SYSTEM_INDEX_THREAD_POOLS)
743+
.setMinimumNodeVersion(VERSION_SECURITY_PROFILE_ORIGIN)
744+
.setPriorSystemIndexDescriptors(
745+
List.of(
746+
SystemIndexDescriptor.builder()
747+
.setIndexPattern(".security-profile-[0-9]+*")
748+
.setPrimaryIndex(INTERNAL_SECURITY_PROFILE_INDEX_8)
749+
.setDescription("Contains user profile documents")
750+
.setMappings(getProfileIndexMappings())
751+
.setSettings(getProfileIndexSettings())
752+
.setAliasName(SECURITY_PROFILE_ALIAS)
753+
.setIndexFormat(INTERNAL_PROFILE_INDEX_FORMAT)
754+
.setVersionMetaKey(SECURITY_VERSION_STRING)
755+
.setOrigin(SECURITY_ORIGIN)
756+
.setThreadPools(ExecutorNames.CRITICAL_SYSTEM_INDEX_THREAD_POOLS)
757+
.build()
758+
)
759+
)
742760
.build();
743761
}
744762

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/profile/ProfileServiceTests.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package org.elasticsearch.xpack.security.profile;
99

1010
import org.elasticsearch.ElasticsearchException;
11+
import org.elasticsearch.Version;
1112
import org.elasticsearch.action.ActionListener;
1213
import org.elasticsearch.action.bulk.BulkAction;
1314
import org.elasticsearch.action.bulk.BulkRequest;
@@ -25,6 +26,9 @@
2526
import org.elasticsearch.action.update.UpdateRequest;
2627
import org.elasticsearch.action.update.UpdateResponse;
2728
import org.elasticsearch.client.internal.Client;
29+
import org.elasticsearch.cluster.ClusterState;
30+
import org.elasticsearch.cluster.node.DiscoveryNodes;
31+
import org.elasticsearch.cluster.service.ClusterService;
2832
import org.elasticsearch.common.Strings;
2933
import org.elasticsearch.common.bytes.BytesArray;
3034
import org.elasticsearch.common.settings.Settings;
@@ -39,6 +43,7 @@
3943
import org.elasticsearch.search.sort.ScoreSortBuilder;
4044
import org.elasticsearch.search.sort.SortOrder;
4145
import org.elasticsearch.test.ESTestCase;
46+
import org.elasticsearch.test.VersionUtils;
4247
import org.elasticsearch.threadpool.FixedExecutorBuilder;
4348
import org.elasticsearch.threadpool.TestThreadPool;
4449
import org.elasticsearch.threadpool.ThreadPool;
@@ -68,6 +73,7 @@
6873

6974
import static org.elasticsearch.common.util.concurrent.ThreadContext.ACTION_ORIGIN_TRANSIENT_NAME;
7075
import static org.elasticsearch.test.ActionListenerUtils.anyActionListener;
76+
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
7177
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_PROFILE_ORIGIN;
7278
import static org.elasticsearch.xpack.security.Security.SECURITY_CRYPTO_THREAD_POOL_NAME;
7379
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_PROFILE_ALIAS;
@@ -124,6 +130,7 @@ public class ProfileServiceTests extends ESTestCase {
124130
private Client client;
125131
private SecurityIndexManager profileIndex;
126132
private ProfileService profileService;
133+
private Version minNodeVersion;
127134

128135
@Before
129136
public void prepare() {
@@ -146,7 +153,14 @@ public void prepare() {
146153
new SearchRequestBuilder(client, SearchAction.INSTANCE).setIndices(SECURITY_PROFILE_ALIAS)
147154
);
148155
this.profileIndex = SecurityMocks.mockSecurityIndexManager(SECURITY_PROFILE_ALIAS);
149-
this.profileService = new ProfileService(Settings.EMPTY, Clock.systemUTC(), client, profileIndex, threadPool);
156+
final ClusterService clusterService = mock(ClusterService.class);
157+
final ClusterState clusterState = mock(ClusterState.class);
158+
when(clusterService.state()).thenReturn(clusterState);
159+
final DiscoveryNodes discoveryNodes = mock(DiscoveryNodes.class);
160+
when(clusterState.nodes()).thenReturn(discoveryNodes);
161+
minNodeVersion = VersionUtils.randomVersionBetween(random(), Version.V_7_17_0, Version.CURRENT);
162+
when(discoveryNodes.getMinNodeVersion()).thenReturn(minNodeVersion);
163+
this.profileService = new ProfileService(Settings.EMPTY, Clock.systemUTC(), client, profileIndex, clusterService, threadPool);
150164
}
151165

152166
@After
@@ -157,7 +171,10 @@ public void stopThreadPool() {
157171
public void testGetProfileByUid() {
158172
final String uid = randomAlphaOfLength(20);
159173
doAnswer(invocation -> {
160-
assertThat(threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME), equalTo(SECURITY_PROFILE_ORIGIN));
174+
assertThat(
175+
threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME),
176+
equalTo(minNodeVersion.onOrAfter(Version.V_8_3_0) ? SECURITY_PROFILE_ORIGIN : SECURITY_ORIGIN)
177+
);
161178
final GetRequest getRequest = (GetRequest) invocation.getArguments()[1];
162179
@SuppressWarnings("unchecked")
163180
final ActionListener<GetResponse> listener = (ActionListener<GetResponse>) invocation.getArguments()[2];
@@ -316,7 +333,10 @@ public void testBuildSearchRequest() {
316333
public void testSecurityProfileOrigin() {
317334
// Activate profile
318335
doAnswer(invocation -> {
319-
assertThat(threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME), equalTo(SECURITY_PROFILE_ORIGIN));
336+
assertThat(
337+
threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME),
338+
equalTo(minNodeVersion.onOrAfter(Version.V_8_3_0) ? SECURITY_PROFILE_ORIGIN : SECURITY_ORIGIN)
339+
);
320340
@SuppressWarnings("unchecked")
321341
final ActionListener<SearchResponse> listener = (ActionListener<SearchResponse>) invocation.getArguments()[2];
322342
listener.onResponse(SearchResponse.empty(() -> 1L, SearchResponse.Clusters.EMPTY));
@@ -329,7 +349,10 @@ public void testSecurityProfileOrigin() {
329349

330350
final RuntimeException expectedException = new RuntimeException("expected");
331351
doAnswer(invocation -> {
332-
assertThat(threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME), equalTo(SECURITY_PROFILE_ORIGIN));
352+
assertThat(
353+
threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME),
354+
equalTo(minNodeVersion.onOrAfter(Version.V_8_3_0) ? SECURITY_PROFILE_ORIGIN : SECURITY_ORIGIN)
355+
);
333356
final ActionListener<?> listener = (ActionListener<?>) invocation.getArguments()[2];
334357
listener.onFailure(expectedException);
335358
return null;
@@ -342,7 +365,10 @@ public void testSecurityProfileOrigin() {
342365

343366
// Update
344367
doAnswer(invocation -> {
345-
assertThat(threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME), equalTo(SECURITY_PROFILE_ORIGIN));
368+
assertThat(
369+
threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME),
370+
equalTo(minNodeVersion.onOrAfter(Version.V_8_3_0) ? SECURITY_PROFILE_ORIGIN : SECURITY_ORIGIN)
371+
);
346372
final ActionListener<?> listener = (ActionListener<?>) invocation.getArguments()[2];
347373
listener.onFailure(expectedException);
348374
return null;
@@ -354,7 +380,10 @@ public void testSecurityProfileOrigin() {
354380

355381
// Suggest
356382
doAnswer(invocation -> {
357-
assertThat(threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME), equalTo(SECURITY_PROFILE_ORIGIN));
383+
assertThat(
384+
threadPool.getThreadContext().getTransient(ACTION_ORIGIN_TRANSIENT_NAME),
385+
equalTo(minNodeVersion.onOrAfter(Version.V_8_3_0) ? SECURITY_PROFILE_ORIGIN : SECURITY_ORIGIN)
386+
);
358387
final ActionListener<?> listener = (ActionListener<?>) invocation.getArguments()[2];
359388
listener.onFailure(expectedException);
360389
return null;

x-pack/qa/rolling-upgrade/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import org.elasticsearch.gradle.Version
12
import org.elasticsearch.gradle.VersionProperties
23
import org.elasticsearch.gradle.internal.info.BuildParams
34
import org.elasticsearch.gradle.testclusters.StandaloneRestIntegTestTask
@@ -87,6 +88,8 @@ BuildParams.bwcVersions.withWireCompatible { bwcVersion, baseName ->
8788
keystore 'xpack.watcher.encryption_key', file("${project.projectDir}/src/test/resources/system_key")
8889
setting 'xpack.watcher.encrypt_sensitive_data', 'true'
8990

91+
requiresFeature 'es.user_profile_feature_flag_enabled', Version.fromString("8.1.0")
92+
9093
// Old versions of the code contain an invalid assertion that trips
9194
// during tests. Versions 5.6.9 and 6.2.4 have been fixed by removing
9295
// the assertion, but this is impossible for released versions.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
"Test User Profile feature will work in a mixed cluster":
3+
4+
- skip:
5+
features: node_selector
6+
version: " - 7.99.99"
7+
reason: "https://github.com/elastic/elasticsearch/issues/86373"
8+
9+
- do:
10+
node_selector:
11+
version: " 8.3.0 - "
12+
security.activate_user_profile:
13+
body: >
14+
{
15+
"grant_type": "password",
16+
"username": "test_user",
17+
"password" : "x-pack-test-password"
18+
}
19+
- is_true: uid
20+
- match: { "user.username" : "test_user" }
21+
- set: { uid: profile_uid }
22+
23+
- do:
24+
node_selector:
25+
version: " 8.3.0 - "
26+
security.get_user_profile:
27+
uid: "$profile_uid"
28+
29+
- length: { $body: 1 }
30+
- is_true: "$profile_uid"
31+
- set: { $profile_uid: profile }
32+
- match: { $profile.uid : "$profile_uid" }
33+
- match: { $profile.user.username : "test_user" }

0 commit comments

Comments
 (0)