Skip to content

Commit af52f94

Browse files
authored
Fix "unexpected field [remote_cluster]" for CCS (RCS 1.0) when using API key that references remote_cluster (elastic#112226) (elastic#112259)
This commit will remove the "remote_cluster" from the role descriptor of API keys that is sent to elder clusters for RCS 1.0. This will allow API keys created in 8.15.0 that reference "remote_cluster" to work when sent to an elder cluster. The API key could either explicitly reference "remote_cluster" or implicitly reference it via the limited by permissions of the superuser built in role. Note this in reference to the standard API key, not cross cluster API key. The cross cluster API already removes this for elder clusters. fixes: elastic#112222 related: elastic#107493
1 parent 0072286 commit af52f94

File tree

4 files changed

+91
-1
lines changed

4 files changed

+91
-1
lines changed

docs/changelog/112226.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 112226
2+
summary: "Fix \"unexpected field [remote_cluster]\" for CCS (RCS 1.0) when using API\
3+
\ key that references `remote_cluster`"
4+
area: Security
5+
type: bug
6+
issues: []

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import java.util.Set;
5555
import java.util.concurrent.atomic.AtomicBoolean;
5656

57+
import static org.elasticsearch.TransportVersions.ROLE_REMOTE_CLUSTER_PRIVS;
5758
import static org.elasticsearch.transport.RemoteClusterPortSettings.TRANSPORT_VERSION_ADVANCED_REMOTE_CLUSTER_SECURITY;
5859
import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;
5960
import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;
@@ -1319,6 +1320,24 @@ private static Map<String, Object> maybeRewriteMetadataForApiKeyRoleDescriptors(
13191320
)
13201321
);
13211322
}
1323+
1324+
if (authentication.getEffectiveSubject().getTransportVersion().onOrAfter(ROLE_REMOTE_CLUSTER_PRIVS)
1325+
&& streamVersion.before(ROLE_REMOTE_CLUSTER_PRIVS)) {
1326+
metadata = new HashMap<>(metadata);
1327+
metadata.put(
1328+
AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY,
1329+
maybeRemoveRemoteClusterFromRoleDescriptors(
1330+
(BytesReference) metadata.get(AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY)
1331+
)
1332+
);
1333+
metadata.put(
1334+
AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY,
1335+
maybeRemoveRemoteClusterFromRoleDescriptors(
1336+
(BytesReference) metadata.get(AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY)
1337+
)
1338+
);
1339+
}
1340+
13221341
if (authentication.getEffectiveSubject().getTransportVersion().onOrAfter(VERSION_API_KEY_ROLES_AS_BYTES)
13231342
&& streamVersion.before(VERSION_API_KEY_ROLES_AS_BYTES)) {
13241343
metadata = new HashMap<>(metadata);
@@ -1397,7 +1416,15 @@ private static BytesReference convertRoleDescriptorsMapToBytes(Map<String, Objec
13971416
}
13981417
}
13991418

1419+
static BytesReference maybeRemoveRemoteClusterFromRoleDescriptors(BytesReference roleDescriptorsBytes) {
1420+
return maybeRemoveTopLevelFromRoleDescriptors(roleDescriptorsBytes, RoleDescriptor.Fields.REMOTE_CLUSTER.getPreferredName());
1421+
}
1422+
14001423
static BytesReference maybeRemoveRemoteIndicesFromRoleDescriptors(BytesReference roleDescriptorsBytes) {
1424+
return maybeRemoveTopLevelFromRoleDescriptors(roleDescriptorsBytes, RoleDescriptor.Fields.REMOTE_INDICES.getPreferredName());
1425+
}
1426+
1427+
static BytesReference maybeRemoveTopLevelFromRoleDescriptors(BytesReference roleDescriptorsBytes, String topLevelField) {
14011428
if (roleDescriptorsBytes == null || roleDescriptorsBytes.length() == 0) {
14021429
return roleDescriptorsBytes;
14031430
}
@@ -1408,7 +1435,7 @@ static BytesReference maybeRemoveRemoteIndicesFromRoleDescriptors(BytesReference
14081435
if (value instanceof Map) {
14091436
@SuppressWarnings("unchecked")
14101437
Map<String, Object> roleDescriptor = (Map<String, Object>) value;
1411-
boolean removed = roleDescriptor.remove(RoleDescriptor.Fields.REMOTE_INDICES.getPreferredName()) != null;
1438+
boolean removed = roleDescriptor.remove(topLevelField) != null;
14121439
if (removed) {
14131440
removedAtLeastOne.set(true);
14141441
}

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/AuthenticationTests.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,51 @@ public void testMaybeRewriteMetadataForApiKeyRoleDescriptorsWithRemoteIndices()
10891089
);
10901090
}
10911091

1092+
public void testMaybeRewriteMetadataForApiKeyRoleDescriptorsWithRemoteCluster() {
1093+
final String apiKeyId = randomAlphaOfLengthBetween(1, 10);
1094+
final String apiKeyName = randomAlphaOfLengthBetween(1, 10);
1095+
final Map<String, Object> metadata = Map.ofEntries(
1096+
entry(AuthenticationField.API_KEY_ID_KEY, apiKeyId),
1097+
entry(AuthenticationField.API_KEY_NAME_KEY, apiKeyName),
1098+
entry(AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY, new BytesArray("""
1099+
{"base_role":{"cluster":["all"],
1100+
"remote_cluster":[{"privileges":["monitor_enrich"],"clusters":["*"]}]
1101+
}}""")),
1102+
entry(AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY, new BytesArray("""
1103+
{"limited_by_role":{"cluster":["*"],
1104+
"remote_cluster":[{"privileges":["monitor_enrich"],"clusters":["*"]}]
1105+
}}"""))
1106+
);
1107+
1108+
final Authentication original = AuthenticationTestHelper.builder()
1109+
.apiKey()
1110+
.metadata(metadata)
1111+
.transportVersion(TransportVersions.ROLE_REMOTE_CLUSTER_PRIVS)
1112+
.build();
1113+
1114+
// pick a version before that of the authentication instance to force a rewrite
1115+
final TransportVersion olderVersion = TransportVersionUtils.randomVersionBetween(
1116+
random(),
1117+
Authentication.VERSION_API_KEY_ROLES_AS_BYTES,
1118+
TransportVersionUtils.getPreviousVersion(original.getEffectiveSubject().getTransportVersion())
1119+
);
1120+
1121+
final Map<String, Object> rewrittenMetadata = original.maybeRewriteForOlderVersion(olderVersion)
1122+
.getEffectiveSubject()
1123+
.getMetadata();
1124+
assertThat(rewrittenMetadata.keySet(), equalTo(original.getAuthenticatingSubject().getMetadata().keySet()));
1125+
assertThat(
1126+
((BytesReference) rewrittenMetadata.get(AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY)).toBytesRef(),
1127+
equalTo(new BytesArray("""
1128+
{"base_role":{"cluster":["all"]}}""").toBytesRef())
1129+
);
1130+
assertThat(
1131+
((BytesReference) rewrittenMetadata.get(AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY)).toBytesRef(),
1132+
equalTo(new BytesArray("""
1133+
{"limited_by_role":{"cluster":["*"]}}""").toBytesRef())
1134+
);
1135+
}
1136+
10921137
public void testMaybeRemoveRemoteIndicesFromRoleDescriptors() {
10931138
final boolean includeClusterPrivileges = randomBoolean();
10941139
final BytesReference roleWithoutRemoteIndices = new BytesArray(Strings.format("""

x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityBwcRestIT.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ public void testBwcWithLegacyCrossClusterSearch() throws Exception {
113113
"privileges": ["read", "read_cross_cluster"],
114114
"clusters": ["my_remote_cluster"]
115115
}
116+
],
117+
"remote_cluster": [
118+
{
119+
"privileges": ["monitor_enrich"],
120+
"clusters": ["*"]
121+
}
116122
]
117123
}""");
118124
assertOK(adminClient().performRequest(putRoleRequest));
@@ -157,6 +163,12 @@ public void testBwcWithLegacyCrossClusterSearch() throws Exception {
157163
"privileges": ["read", "read_cross_cluster"],
158164
"clusters": ["my_remote_*", "non_existing_remote_cluster"]
159165
}
166+
],
167+
"remote_cluster": [
168+
{
169+
"privileges": ["monitor_enrich"],
170+
"clusters": ["*"]
171+
}
160172
]
161173
}
162174
}

0 commit comments

Comments
 (0)