Skip to content

Commit 8d41248

Browse files
authored
Remove synthetic role names for legacy API keys (elastic#119844)
This PR addresses an issue where legacy API keys fail consistency checks because they include synthetic role names. We removed synthetic role names with elastic#56005. We added consistency checks sometime later to enforce no role names, with elastic#93894 in `8.8.0`. Rather than relaxing our consistency checks, this PR tweaks de-serialization logic to strip out role names when appropriate. This has the advantage that we maintain the invariant the consistency check is meant to enforce. Note that this does not manifest in production: outside of RCS 2.0, we only execute consistency checks with assertions enabled. For RCS 2.0, an API key would require `remote_indices` privileges to ever be sent cross cluster and go through consistency checks. These were introduced after we've stopped including role names in API keys so it's not a real issue either. Closes: elastic#119259 Closes: elastic#119435 Closes: elastic#119434 Closes: elastic#119433 Closes: elastic#119424 Closes: elastic#119423 Closes: elastic#119422 Closes: elastic#119396 Closes: elastic#119395 Closes: elastic#119394 Closes: elastic#119393
1 parent abe8d7f commit 8d41248

File tree

3 files changed

+79
-32
lines changed

3 files changed

+79
-32
lines changed

muted-tests.yml

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -428,24 +428,9 @@ tests:
428428
- class: org.elasticsearch.index.engine.LuceneSyntheticSourceChangesSnapshotTests
429429
method: testSkipNonRootOfNestedDocuments
430430
issue: https://github.com/elastic/elasticsearch/issues/119553
431-
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT
432-
method: testWatcher {cluster=UPGRADED}
433-
issue: https://github.com/elastic/elasticsearch/issues/119259
434-
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT
435-
method: testServiceAccountApiKey {cluster=UPGRADED}
436-
issue: https://github.com/elastic/elasticsearch/issues/119435
437-
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT
438-
method: testSecurityNativeRealm {cluster=UPGRADED}
439-
issue: https://github.com/elastic/elasticsearch/issues/119423
440-
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT
441-
method: testSingleDoc {cluster=UPGRADED}
442-
issue: https://github.com/elastic/elasticsearch/issues/119434
443431
- class: org.elasticsearch.upgrades.SearchStatesIT
444432
method: testCanMatch
445433
issue: https://github.com/elastic/elasticsearch/issues/118718
446-
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT
447-
method: testTransformLegacyTemplateCleanup {cluster=UPGRADED}
448-
issue: https://github.com/elastic/elasticsearch/issues/119395
449434
- class: org.elasticsearch.backwards.MixedClusterClientYamlTestSuiteIT
450435
method: "test {p0=search.vectors/110_knn_query_with_filter/PRE_FILTER: knn query with internal filter as pre-filter}"
451436
issue: https://github.com/elastic/elasticsearch/issues/119804
@@ -469,21 +454,6 @@ tests:
469454
- class: org.elasticsearch.xpack.ml.integration.MlJobIT
470455
method: testGetJob_GivenJobExists
471456
issue: https://github.com/elastic/elasticsearch/issues/119811
472-
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT
473-
method: testSlmPolicyAndStats {cluster=UPGRADED}
474-
issue: https://github.com/elastic/elasticsearch/issues/119393
475-
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT
476-
method: testApiKeySuperuser {cluster=UPGRADED}
477-
issue: https://github.com/elastic/elasticsearch/issues/119424
478-
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT
479-
method: testDataStreams {cluster=UPGRADED}
480-
issue: https://github.com/elastic/elasticsearch/issues/119394
481-
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT
482-
method: testDisableFieldNameField {cluster=UPGRADED}
483-
issue: https://github.com/elastic/elasticsearch/issues/119422
484-
- class: org.elasticsearch.xpack.restart.FullClusterRestartIT
485-
method: testRollupAfterRestart {cluster=UPGRADED}
486-
issue: https://github.com/elastic/elasticsearch/issues/119433
487457
- class: org.elasticsearch.xpack.security.authc.ldap.ADLdapUserSearchSessionFactoryTests
488458
issue: https://github.com/elastic/elasticsearch/issues/119882
489459
- class: org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapperTests

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import java.util.Set;
5656
import java.util.concurrent.atomic.AtomicBoolean;
5757

58+
import static org.elasticsearch.common.Strings.EMPTY_ARRAY;
5859
import static org.elasticsearch.transport.RemoteClusterPortSettings.TRANSPORT_VERSION_ADVANCED_REMOTE_CLUSTER_SECURITY;
5960
import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;
6061
import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;
@@ -170,21 +171,49 @@ public Authentication(StreamInput in) throws IOException {
170171
type = AuthenticationType.REALM;
171172
metadata = Map.of();
172173
}
174+
173175
if (innerUser != null) {
174-
authenticatingSubject = new Subject(innerUser, authenticatedBy, version, metadata);
176+
authenticatingSubject = new Subject(
177+
copyUserWithRolesRemovedForLegacyApiKeys(version, innerUser),
178+
authenticatedBy,
179+
version,
180+
metadata
181+
);
175182
// The lookup user for run-as currently doesn't have authentication metadata associated with them because
176183
// lookupUser only returns the User object. The lookup user for authorization delegation does have
177184
// authentication metadata, but the realm does not expose this difference between authenticatingUser and
178185
// delegateUser so effectively this is handled together with the authenticatingSubject not effectiveSubject.
186+
// Note: we do not call copyUserWithRolesRemovedForLegacyApiKeys here because an API key is never the target of run-as
179187
effectiveSubject = new Subject(outerUser, lookedUpBy, version, Map.of());
180188
} else {
181-
authenticatingSubject = effectiveSubject = new Subject(outerUser, authenticatedBy, version, metadata);
189+
authenticatingSubject = effectiveSubject = new Subject(
190+
copyUserWithRolesRemovedForLegacyApiKeys(version, outerUser),
191+
authenticatedBy,
192+
version,
193+
metadata
194+
);
182195
}
196+
183197
if (Assertions.ENABLED) {
184198
checkConsistency();
185199
}
186200
}
187201

202+
private User copyUserWithRolesRemovedForLegacyApiKeys(TransportVersion version, User user) {
203+
// API keys prior to 7.8 had synthetic role names. Strip these out to maintain the invariant that API keys don't have role names
204+
if (type == AuthenticationType.API_KEY && version.onOrBefore(TransportVersions.V_7_8_0) && user.roles().length > 0) {
205+
logger.debug(
206+
"Stripping [{}] roles from API key user [{}] for legacy version [{}]",
207+
user.roles().length,
208+
user.principal(),
209+
version
210+
);
211+
return new User(user.principal(), EMPTY_ARRAY, user.fullName(), user.email(), user.metadata(), user.enabled());
212+
} else {
213+
return user;
214+
}
215+
}
216+
188217
/**
189218
* Get the {@link Subject} that performs the actual authentication. This normally means it provides a credentials.
190219
*/

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,21 @@
1313
import org.elasticsearch.test.ESTestCase;
1414
import org.elasticsearch.test.TransportVersionUtils;
1515
import org.elasticsearch.transport.RemoteClusterPortSettings;
16+
import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer;
1617
import org.elasticsearch.xpack.core.security.user.ElasticUser;
1718
import org.elasticsearch.xpack.core.security.user.InternalUsers;
1819
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
1920
import org.elasticsearch.xpack.core.security.user.KibanaUser;
2021
import org.elasticsearch.xpack.core.security.user.User;
2122

23+
import java.io.IOException;
2224
import java.util.Arrays;
25+
import java.util.Map;
2326

2427
import static org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationSerializationHelper;
28+
import static org.hamcrest.Matchers.arrayContaining;
2529
import static org.hamcrest.Matchers.containsString;
30+
import static org.hamcrest.Matchers.emptyArray;
2631
import static org.hamcrest.Matchers.equalTo;
2732
import static org.hamcrest.Matchers.is;
2833
import static org.hamcrest.Matchers.not;
@@ -171,4 +176,47 @@ public void testReservedUserSerialization() throws Exception {
171176

172177
assertEquals(kibanaSystemUser, readFrom);
173178
}
179+
180+
public void testRolesRemovedFromUserForLegacyApiKeys() throws IOException {
181+
TransportVersion transportVersion = TransportVersionUtils.randomVersionBetween(
182+
random(),
183+
TransportVersions.V_7_0_0,
184+
TransportVersions.V_7_8_0
185+
);
186+
Subject authenticatingSubject = new Subject(
187+
new User("foo", "role"),
188+
new Authentication.RealmRef(AuthenticationField.API_KEY_REALM_NAME, AuthenticationField.API_KEY_REALM_TYPE, "node"),
189+
transportVersion,
190+
Map.of(AuthenticationField.API_KEY_ID_KEY, "abc")
191+
);
192+
Subject effectiveSubject = new Subject(
193+
new User("bar", "role"),
194+
new Authentication.RealmRef("native", "native", "node"),
195+
transportVersion,
196+
Map.of()
197+
);
198+
199+
{
200+
Authentication actual = AuthenticationContextSerializer.decode(
201+
Authentication.doEncode(authenticatingSubject, authenticatingSubject, Authentication.AuthenticationType.API_KEY)
202+
);
203+
assertThat(actual.getAuthenticatingSubject().getUser().roles(), is(emptyArray()));
204+
}
205+
206+
{
207+
Authentication actual = AuthenticationContextSerializer.decode(
208+
Authentication.doEncode(effectiveSubject, authenticatingSubject, Authentication.AuthenticationType.API_KEY)
209+
);
210+
assertThat(actual.getAuthenticatingSubject().getUser().roles(), is(emptyArray()));
211+
assertThat(actual.getEffectiveSubject().getUser().roles(), is(arrayContaining("role")));
212+
}
213+
214+
{
215+
// do not strip roles for authentication methods other than API key
216+
Authentication actual = AuthenticationContextSerializer.decode(
217+
Authentication.doEncode(effectiveSubject, effectiveSubject, Authentication.AuthenticationType.REALM)
218+
);
219+
assertThat(actual.getAuthenticatingSubject().getUser().roles(), is(arrayContaining("role")));
220+
}
221+
}
174222
}

0 commit comments

Comments
 (0)