From b0b1b133115bb9c55264d82226e869c4a31dda03 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Tue, 11 Feb 2020 20:59:06 +1100 Subject: [PATCH] Extract class to store Authentication in context (#52183) This change extracts the code that previously existed in the "Authentication" class that was responsible for reading and writing authentication objects to/from the ThreadContext. This is needed to support multiple authentication objects under separate keys. This refactoring highlighted that there were a large number of places where we extracted the Authentication/User objects from the thread context, in a variety of ways. These have been consolidated to rely on the SecurityContext object. Backport of: #52032 --- .../xpack/core/security/SecurityContext.java | 18 ++-- .../xpack/core/security/UserSettings.java | 46 ---------- .../core/security/authc/Authentication.java | 50 +---------- .../AuthenticationContextSerializer.java | 90 +++++++++++++++++++ .../SecurityIndexReaderWrapper.java | 17 ++-- .../execution/WatchExecutionContext.java | 3 +- ...ityIndexReaderWrapperIntegrationTests.java | 22 +++-- .../SecurityIndexReaderWrapperUnitTests.java | 11 +-- .../xpack/security/Security.java | 11 +-- ...nsportDelegatePkiAuthenticationAction.java | 10 ++- ...nsportOpenIdConnectAuthenticateAction.java | 7 +- .../saml/TransportSamlAuthenticateAction.java | 7 +- .../token/TransportCreateTokenAction.java | 10 ++- .../TransportGetUserPrivilegesAction.java | 14 ++- .../user/TransportHasPrivilegesAction.java | 3 +- .../user/TransportSetEnabledAction.java | 8 +- .../security/authc/AuthenticationService.java | 7 +- .../xpack/security/authc/TokenService.java | 7 +- .../SecuritySearchOperationListener.java | 17 ++-- .../ingest/SetSecurityUserProcessor.java | 21 +++-- .../transport/ServerTransportFilter.java | 2 +- .../xpack/security/SecurityContextTests.java | 2 +- ...ansportOpenIdConnectLogoutActionTests.java | 5 +- ...sportSamlInvalidateSessionActionTests.java | 5 +- .../saml/TransportSamlLogoutActionTests.java | 5 +- .../TransportCreateTokenActionTests.java | 16 ++-- .../user/TransportSetEnabledActionTests.java | 59 +++++++----- .../authc/AuthenticationServiceTests.java | 5 +- .../security/authc/TokenServiceTests.java | 8 +- .../SecuritySearchOperationListenerTests.java | 10 ++- .../SetSecurityUserProcessorFactoryTests.java | 29 +++++- .../ingest/SetSecurityUserProcessorTests.java | 68 +++++++------- 32 files changed, 351 insertions(+), 242 deletions(-) delete mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/UserSettings.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/AuthenticationContextSerializer.java diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java index e1922ca87ea..514bf49719d 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext.StoredContext; import org.elasticsearch.node.Node; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType; +import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.user.User; import java.io.IOException; @@ -29,17 +30,12 @@ public class SecurityContext { private final Logger logger = LogManager.getLogger(SecurityContext.class); private final ThreadContext threadContext; - private final UserSettings userSettings; + private final AuthenticationContextSerializer authenticationSerializer; private final String nodeName; - /** - * Creates a new security context. - * If cryptoService is null, security is disabled and {@link UserSettings#getUser()} - * and {@link UserSettings#getAuthentication()} will always return null. - */ public SecurityContext(Settings settings, ThreadContext threadContext) { this.threadContext = threadContext; - this.userSettings = new UserSettings(threadContext); + this.authenticationSerializer = new AuthenticationContextSerializer(); this.nodeName = Node.NODE_NAME_SETTING.get(settings); } @@ -52,13 +48,17 @@ public class SecurityContext { /** Returns the authentication information, or null if the current request has no authentication info. */ public Authentication getAuthentication() { try { - return Authentication.readFromContext(threadContext); + return authenticationSerializer.readFromContext(threadContext); } catch (IOException e) { logger.error("failed to read authentication", e); throw new UncheckedIOException(e); } } + public ThreadContext getThreadContext() { + return threadContext; + } + /** * Sets the user forcefully to the provided user. There must not be an existing user in the ThreadContext otherwise an exception * will be thrown. This method is package private for testing. @@ -103,7 +103,7 @@ public class SecurityContext { */ public void executeAfterRewritingAuthentication(Consumer consumer, Version version) { final StoredContext original = threadContext.newStoredContext(true); - final Authentication authentication = Objects.requireNonNull(userSettings.getAuthentication()); + final Authentication authentication = getAuthentication(); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { setAuthentication(new Authentication(authentication.getUser(), authentication.getAuthenticatedBy(), authentication.getLookedUpBy(), version, authentication.getAuthenticationType(), authentication.getMetadata())); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/UserSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/UserSettings.java deleted file mode 100644 index c7f22e8742e..00000000000 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/UserSettings.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.core.security; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; -import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.xpack.core.security.authc.Authentication; -import org.elasticsearch.xpack.core.security.user.User; - -import java.io.IOException; - -public final class UserSettings { - private final Logger logger = LogManager.getLogger(UserSettings.class); - - private final ThreadContext threadContext; - - UserSettings(ThreadContext threadContext) { - this.threadContext = threadContext; - } - - /** - * Returns the current user information, or null if the current request has no authentication info. - */ - public User getUser() { - Authentication authentication = getAuthentication(); - return authentication == null ? null : authentication.getUser(); - } - - /** - * Returns the authentication information, or null if the current request has no authentication info. - */ - public Authentication getAuthentication() { - try { - return Authentication.readFromContext(threadContext); - } catch (IOException e) { - // TODO: this seems bogus, the only way to get an ioexception here is from a corrupt or tampered - // auth header, which should be be audited? - logger.error("failed to read authentication", e); - return null; - } - } -} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index de086735dc7..e755e188503 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -13,6 +13,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.user.InternalUserSerializationHelper; import org.elasticsearch.xpack.core.security.user.User; @@ -93,59 +94,12 @@ public class Authentication implements ToXContentObject { return metadata; } - public static Authentication readFromContext(ThreadContext ctx) throws IOException, IllegalArgumentException { - Authentication authentication = ctx.getTransient(AuthenticationField.AUTHENTICATION_KEY); - if (authentication != null) { - assert ctx.getHeader(AuthenticationField.AUTHENTICATION_KEY) != null; - return authentication; - } - - String authenticationHeader = ctx.getHeader(AuthenticationField.AUTHENTICATION_KEY); - if (authenticationHeader == null) { - return null; - } - return deserializeHeaderAndPutInContext(authenticationHeader, ctx); - } - - public static Authentication getAuthentication(ThreadContext context) { - return context.getTransient(AuthenticationField.AUTHENTICATION_KEY); - } - - static Authentication deserializeHeaderAndPutInContext(String header, ThreadContext ctx) - throws IOException, IllegalArgumentException { - assert ctx.getTransient(AuthenticationField.AUTHENTICATION_KEY) == null; - - Authentication authentication = decode(header); - ctx.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); - return authentication; - } - - public static Authentication decode(String header) throws IOException { - byte[] bytes = Base64.getDecoder().decode(header); - StreamInput input = StreamInput.wrap(bytes); - Version version = Version.readVersion(input); - input.setVersion(version); - return new Authentication(input); - } - /** * Writes the authentication to the context. There must not be an existing authentication in the context and if there is an * {@link IllegalStateException} will be thrown */ public void writeToContext(ThreadContext ctx) throws IOException, IllegalArgumentException { - ensureContextDoesNotContainAuthentication(ctx); - String header = encode(); - ctx.putTransient(AuthenticationField.AUTHENTICATION_KEY, this); - ctx.putHeader(AuthenticationField.AUTHENTICATION_KEY, header); - } - - void ensureContextDoesNotContainAuthentication(ThreadContext ctx) { - if (ctx.getTransient(AuthenticationField.AUTHENTICATION_KEY) != null) { - if (ctx.getHeader(AuthenticationField.AUTHENTICATION_KEY) == null) { - throw new IllegalStateException("authentication present as a transient but not a header"); - } - throw new IllegalStateException("authentication is already present in the context"); - } + new AuthenticationContextSerializer().writeToContext(this, ctx); } public String encode() throws IOException { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/AuthenticationContextSerializer.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/AuthenticationContextSerializer.java new file mode 100644 index 00000000000..34c5f9160ec --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/AuthenticationContextSerializer.java @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.core.security.authc.support; + +import org.elasticsearch.Version; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.authc.AuthenticationField; + +import java.io.IOException; +import java.util.Base64; + +/** + * A class from reading/writing {@link org.elasticsearch.xpack.core.security.authc.Authentication} objects to/from a + * {@link org.elasticsearch.common.util.concurrent.ThreadContext} under a specified key + */ +public class AuthenticationContextSerializer { + + private final String contextKey; + + public AuthenticationContextSerializer() { + this(AuthenticationField.AUTHENTICATION_KEY); + } + + public AuthenticationContextSerializer(String contextKey) { + this.contextKey = contextKey; + } + + @Nullable + public Authentication readFromContext(ThreadContext ctx) throws IOException { + Authentication authentication = ctx.getTransient(contextKey); + if (authentication != null) { + assert ctx.getHeader(contextKey) != null; + return authentication; + } + + String authenticationHeader = ctx.getHeader(contextKey); + if (authenticationHeader == null) { + return null; + } + return deserializeHeaderAndPutInContext(authenticationHeader, ctx); + } + + Authentication deserializeHeaderAndPutInContext(String headerValue, ThreadContext ctx) + throws IOException, IllegalArgumentException { + assert ctx.getTransient(contextKey) == null; + + Authentication authentication = decode(headerValue); + ctx.putTransient(contextKey, authentication); + return authentication; + } + + public static Authentication decode(String header) throws IOException { + byte[] bytes = Base64.getDecoder().decode(header); + StreamInput input = StreamInput.wrap(bytes); + Version version = Version.readVersion(input); + input.setVersion(version); + return new Authentication(input); + } + + public Authentication getAuthentication(ThreadContext context) { + return context.getTransient(contextKey); + } + + /** + * Writes the authentication to the context. There must not be an existing authentication in the context and if there is an + * {@link IllegalStateException} will be thrown + */ + public void writeToContext(Authentication authentication, ThreadContext ctx) throws IOException { + ensureContextDoesNotContainAuthentication(ctx); + String header = authentication.encode(); + ctx.putTransient(contextKey, authentication); + ctx.putHeader(contextKey, header); + } + + void ensureContextDoesNotContainAuthentication(ThreadContext ctx) { + if (ctx.getTransient(contextKey) != null) { + if (ctx.getHeader(contextKey) == null) { + throw new IllegalStateException("authentication present as a transient ([" + contextKey + "]) but not a header"); + } + throw new IllegalStateException("authentication ([" + contextKey + "]) is already present in the context"); + } + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java index ea8f005be03..57af4eb16c5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java @@ -19,13 +19,14 @@ import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardUtils; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.script.ScriptService; -import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField; import org.elasticsearch.xpack.core.security.authz.permission.DocumentPermissions; import org.elasticsearch.xpack.core.security.support.Exceptions; import org.elasticsearch.xpack.core.security.user.User; import java.io.IOException; +import java.util.Objects; import java.util.function.Function; /** @@ -45,16 +46,16 @@ public class SecurityIndexReaderWrapper implements CheckedFunction queryShardContextProvider; private final DocumentSubsetBitsetCache bitsetCache; private final XPackLicenseState licenseState; - private final ThreadContext threadContext; + private final SecurityContext securityContext; private final ScriptService scriptService; public SecurityIndexReaderWrapper(Function queryShardContextProvider, - DocumentSubsetBitsetCache bitsetCache, ThreadContext threadContext, XPackLicenseState licenseState, - ScriptService scriptService) { + DocumentSubsetBitsetCache bitsetCache, SecurityContext securityContext, + XPackLicenseState licenseState, ScriptService scriptService) { this.scriptService = scriptService; this.queryShardContextProvider = queryShardContextProvider; this.bitsetCache = bitsetCache; - this.threadContext = threadContext; + this.securityContext = securityContext; this.licenseState = licenseState; } @@ -95,6 +96,7 @@ public class SecurityIndexReaderWrapper implements CheckedFunction Collections.singletonList((String) invocationOnMock.getArguments()[0])); - ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext); + final Authentication authentication = mock(Authentication.class); when(authentication.getUser()).thenReturn(mock(User.class)); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + when(authentication.encode()).thenReturn(randomAlphaOfLength(24)); // don't care as long as it's not null + new AuthenticationContextSerializer().writeToContext(authentication, threadContext); + IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(shardId.getIndex(), Settings.EMPTY); Client client = mock(Client.class); when(client.settings()).thenReturn(Settings.EMPTY); @@ -135,7 +140,7 @@ public class SecurityIndexReaderWrapperIntegrationTests extends AbstractBuilderT FieldPermissions(), DocumentPermissions.filteredBy(singleton(new BytesArray(termQuery)))); SecurityIndexReaderWrapper wrapper = new SecurityIndexReaderWrapper(s -> queryShardContext, - bitsetCache, threadContext, licenseState, scriptService) { + bitsetCache, securityContext, licenseState, scriptService) { @Override protected IndicesAccessControl getIndicesAccessControl() { @@ -173,10 +178,13 @@ public class SecurityIndexReaderWrapperIntegrationTests extends AbstractBuilderT when(mapperService.simpleMatchToFullName(anyString())) .then(invocationOnMock -> Collections.singletonList((String) invocationOnMock.getArguments()[0])); - ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext); final Authentication authentication = mock(Authentication.class); when(authentication.getUser()).thenReturn(mock(User.class)); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + when(authentication.encode()).thenReturn(randomAlphaOfLength(24)); // don't care as long as it's not null + new AuthenticationContextSerializer().writeToContext(authentication, threadContext); + final boolean noFilteredIndexPermissions = randomBoolean(); boolean restrictiveLimitedIndexPermissions = false; if (noFilteredIndexPermissions == false) { @@ -208,7 +216,7 @@ public class SecurityIndexReaderWrapperIntegrationTests extends AbstractBuilderT XPackLicenseState licenseState = mock(XPackLicenseState.class); when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(true); SecurityIndexReaderWrapper wrapper = new SecurityIndexReaderWrapper(s -> queryShardContext, - bitsetCache, threadContext, licenseState, scriptService) { + bitsetCache, securityContext, licenseState, scriptService) { @Override protected IndicesAccessControl getIndicesAccessControl() { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperUnitTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperUnitTests.java index 0535c8aa4e2..c91469a62e5 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperUnitTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperUnitTests.java @@ -22,6 +22,7 @@ import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.script.ScriptService; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authz.permission.DocumentPermissions; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition; @@ -50,7 +51,7 @@ public class SecurityIndexReaderWrapperUnitTests extends ESTestCase { META_FIELDS = Collections.unmodifiableSet(metaFields); } - private ThreadContext threadContext; + private SecurityContext securityContext; private ScriptService scriptService; private SecurityIndexReaderWrapper securityIndexReaderWrapper; private ElasticsearchDirectoryReader esIn; @@ -64,7 +65,7 @@ public class SecurityIndexReaderWrapperUnitTests extends ESTestCase { ShardId shardId = new ShardId(index, 0); licenseState = mock(XPackLicenseState.class); when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(true); - threadContext = new ThreadContext(Settings.EMPTY); + securityContext = new SecurityContext(Settings.EMPTY, new ThreadContext(Settings.EMPTY)); IndexShard indexShard = mock(IndexShard.class); when(indexShard.shardId()).thenReturn(shardId); @@ -84,7 +85,7 @@ public class SecurityIndexReaderWrapperUnitTests extends ESTestCase { public void testDefaultMetaFields() throws Exception { securityIndexReaderWrapper = - new SecurityIndexReaderWrapper(null, null, threadContext, licenseState, scriptService) { + new SecurityIndexReaderWrapper(null, null, securityContext, licenseState, scriptService) { @Override protected IndicesAccessControl getIndicesAccessControl() { IndicesAccessControl.IndexAccessControl indexAccessControl = new IndicesAccessControl.IndexAccessControl(true, @@ -114,7 +115,7 @@ public class SecurityIndexReaderWrapperUnitTests extends ESTestCase { public void testWrapReaderWhenFeatureDisabled() throws Exception { when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(false); securityIndexReaderWrapper = - new SecurityIndexReaderWrapper(null, null, threadContext, licenseState, scriptService); + new SecurityIndexReaderWrapper(null, null, securityContext, licenseState, scriptService); DirectoryReader reader = securityIndexReaderWrapper.apply(esIn); assertThat(reader, sameInstance(esIn)); } @@ -148,7 +149,7 @@ public class SecurityIndexReaderWrapperUnitTests extends ESTestCase { public void testFieldPermissionsWithFieldExceptions() throws Exception { securityIndexReaderWrapper = - new SecurityIndexReaderWrapper(null, null, threadContext, licenseState, null); + new SecurityIndexReaderWrapper(null, null, securityContext, licenseState, null); String[] grantedFields = new String[]{}; String[] deniedFields; Set expected = new HashSet<>(META_FIELDS); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 0393ca4931f..8240c94faf5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -427,8 +427,8 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin, securityIndex.set(SecurityIndexManager.buildSecurityMainIndexManager(client, clusterService)); - final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, getLicenseState(), - securityIndex.get(), SecurityIndexManager.buildSecurityTokensIndexManager(client, clusterService), clusterService); + final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, getLicenseState(), securityContext.get(), + securityIndex.get(), SecurityIndexManager.buildSecurityTokensIndexManager(client, clusterService), clusterService); this.tokenService.set(tokenService); components.add(tokenService); @@ -726,7 +726,8 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin, }, null), dlsBitsetCache.get(), - indexService.getThreadPool().getThreadContext(), getLicenseState(), + securityContext.get(), + getLicenseState(), indexService.getScriptService())); /* * We need to forcefully overwrite the query cache implementation to use security's opt-out query cache implementation. This @@ -746,7 +747,7 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin, // attaches information to the scroll context so that we can validate the user that created the scroll against // the user that is executing a scroll operation module.addSearchOperationListener( - new SecuritySearchOperationListener(threadContext.get(), getLicenseState(), auditTrailService.get())); + new SecuritySearchOperationListener(securityContext.get(), getLicenseState(), auditTrailService.get())); } } @@ -854,7 +855,7 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin, @Override public Map getProcessors(Processor.Parameters parameters) { - return Collections.singletonMap(SetSecurityUserProcessor.TYPE, new SetSecurityUserProcessor.Factory(parameters.threadContext)); + return Collections.singletonMap(SetSecurityUserProcessor.TYPE, new SetSecurityUserProcessor.Factory(securityContext::get)); } /** diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportDelegatePkiAuthenticationAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportDelegatePkiAuthenticationAction.java index 0da00b5c013..da73d117df5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportDelegatePkiAuthenticationAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportDelegatePkiAuthenticationAction.java @@ -19,6 +19,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationAction; import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationRequest; import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse; @@ -38,7 +39,7 @@ import java.util.Collections; * {@code PkiRealmSettings#DELEGATION_ENABLED_SETTING} set to {@code true} (default is {@code false}). A successfully trusted target * certificate is also subject to the validation of the subject distinguished name according to that respective's realm * {@code PkiRealmSettings#USERNAME_PATTERN_SETTING}. - * + * * IMPORTANT: The association between the subject public key in the target certificate and the corresponding private key is not * validated. This is part of the TLS authentication process and it is delegated to the proxy calling this API. The proxy is trusted * to have performed the TLS authentication, and this API translates that authentication into an Elasticsearch access token. @@ -51,21 +52,24 @@ public final class TransportDelegatePkiAuthenticationAction private final ThreadPool threadPool; private final AuthenticationService authenticationService; private final TokenService tokenService; + private final SecurityContext securityContext; @Inject public TransportDelegatePkiAuthenticationAction(ThreadPool threadPool, TransportService transportService, ActionFilters actionFilters, - AuthenticationService authenticationService, TokenService tokenService) { + AuthenticationService authenticationService, TokenService tokenService, + SecurityContext securityContext) { super(DelegatePkiAuthenticationAction.NAME, transportService, actionFilters, DelegatePkiAuthenticationRequest::new); this.threadPool = threadPool; this.authenticationService = authenticationService; this.tokenService = tokenService; + this.securityContext = securityContext; } @Override protected void doExecute(Task task, DelegatePkiAuthenticationRequest request, ActionListener listener) { final ThreadContext threadContext = threadPool.getThreadContext(); - Authentication delegateeAuthentication = Authentication.getAuthentication(threadContext); + Authentication delegateeAuthentication = securityContext.getAuthentication(); if (delegateeAuthentication == null) { listener.onFailure(new IllegalStateException("Delegatee authentication cannot be null")); return; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectAuthenticateAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectAuthenticateAction.java index 8700bfb0f10..92ba783e532 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectAuthenticateAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectAuthenticateAction.java @@ -20,6 +20,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectAuthenticateRequest; import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectAuthenticateResponse; import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectAuthenticateAction; @@ -38,17 +39,19 @@ public class TransportOpenIdConnectAuthenticateAction private final ThreadPool threadPool; private final AuthenticationService authenticationService; private final TokenService tokenService; + private final SecurityContext securityContext; private static final Logger logger = LogManager.getLogger(TransportOpenIdConnectAuthenticateAction.class); @Inject public TransportOpenIdConnectAuthenticateAction(ThreadPool threadPool, TransportService transportService, ActionFilters actionFilters, AuthenticationService authenticationService, - TokenService tokenService) { + TokenService tokenService, SecurityContext securityContext) { super(OpenIdConnectAuthenticateAction.NAME, transportService, actionFilters, (Writeable.Reader) OpenIdConnectAuthenticateRequest::new); this.threadPool = threadPool; this.authenticationService = authenticationService; this.tokenService = tokenService; + this.securityContext = securityContext; } @Override @@ -57,7 +60,7 @@ public class TransportOpenIdConnectAuthenticateAction final OpenIdConnectToken token = new OpenIdConnectToken(request.getRedirectUri(), new State(request.getState()), new Nonce(request.getNonce()), request.getRealm()); final ThreadContext threadContext = threadPool.getThreadContext(); - Authentication originatingAuthentication = Authentication.getAuthentication(threadContext); + Authentication originatingAuthentication = securityContext.getAuthentication(); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { authenticationService.authenticate(OpenIdConnectAuthenticateAction.NAME, request, token, ActionListener.wrap( authentication -> { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlAuthenticateAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlAuthenticateAction.java index 528663fbce6..36b78b480c1 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlAuthenticateAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/saml/TransportSamlAuthenticateAction.java @@ -15,6 +15,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.saml.SamlAuthenticateAction; import org.elasticsearch.xpack.core.security.action.saml.SamlAuthenticateRequest; import org.elasticsearch.xpack.core.security.action.saml.SamlAuthenticateResponse; @@ -35,15 +36,17 @@ public final class TransportSamlAuthenticateAction extends HandledTransportActio private final ThreadPool threadPool; private final AuthenticationService authenticationService; private final TokenService tokenService; + private final SecurityContext securityContext; @Inject public TransportSamlAuthenticateAction(ThreadPool threadPool, TransportService transportService, ActionFilters actionFilters, AuthenticationService authenticationService, - TokenService tokenService) { + TokenService tokenService, SecurityContext securityContext) { super(SamlAuthenticateAction.NAME, transportService, actionFilters, SamlAuthenticateRequest::new); this.threadPool = threadPool; this.authenticationService = authenticationService; this.tokenService = tokenService; + this.securityContext = securityContext; } @Override @@ -51,7 +54,7 @@ public final class TransportSamlAuthenticateAction extends HandledTransportActio final SamlToken saml = new SamlToken(request.getSaml(), request.getValidRequestIds(), request.getRealm()); logger.trace("Attempting to authenticate SamlToken [{}]", saml); final ThreadContext threadContext = threadPool.getThreadContext(); - Authentication originatingAuthentication = Authentication.getAuthentication(threadContext); + Authentication originatingAuthentication = securityContext.getAuthentication(); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { authenticationService.authenticate(SamlAuthenticateAction.NAME, request, saml, ActionListener.wrap(authentication -> { AuthenticationResult result = threadContext.getTransient(AuthenticationResult.THREAD_CONTEXT_KEY); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java index 252807779a1..a2172c5b2e6 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction; import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest; import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest.GrantType; @@ -41,14 +42,17 @@ public final class TransportCreateTokenAction extends HandledTransportAction listener) { - Authentication originatingAuthentication = Authentication.getAuthentication(threadPool.getThreadContext()); + Authentication originatingAuthentication = securityContext.getAuthentication(); try (ThreadContext.StoredContext ignore = threadPool.getThreadContext().stashContext()) { final AuthenticationToken authToken = extractAuthenticationToken(grantType, request, listener); if (authToken == null) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportGetUserPrivilegesAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportGetUserPrivilegesAction.java index 00232033b89..30d89115f4e 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportGetUserPrivilegesAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportGetUserPrivilegesAction.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesAction; import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesRequest; import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse; @@ -26,21 +27,28 @@ public class TransportGetUserPrivilegesAction extends HandledTransportAction listener) { final String username = request.username(); - final Authentication authentication = Authentication.getAuthentication(threadPool.getThreadContext()); - final User user = authentication.getUser(); + final Authentication authentication = securityContext.getAuthentication(); + final User user = securityContext.getUser(); + if (authentication == null || user == null) { + listener.onFailure(new IllegalArgumentException("cannot list privileges as there is no active user")); + return; + } if (user.principal().equals(username) == false) { listener.onFailure(new IllegalArgumentException("users may only list the privileges of their own account")); return; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportHasPrivilegesAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportHasPrivilegesAction.java index 7401463bf57..fe9d3c68736 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportHasPrivilegesAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportHasPrivilegesAction.java @@ -58,8 +58,7 @@ public class TransportHasPrivilegesAction extends HandledTransportAction listener) { final String username = request.username(); - - final Authentication authentication = Authentication.getAuthentication(threadPool.getThreadContext()); + final Authentication authentication = securityContext.getAuthentication(); final User user = authentication.getUser(); if (user.principal().equals(username) == false) { listener.onFailure(new IllegalArgumentException("users may only check the privileges of their own account")); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportSetEnabledAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportSetEnabledAction.java index ee30168259b..2640961acf1 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportSetEnabledAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportSetEnabledAction.java @@ -13,10 +13,10 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.user.SetEnabledAction; import org.elasticsearch.xpack.core.security.action.user.SetEnabledRequest; import org.elasticsearch.xpack.core.security.action.user.SetEnabledResponse; -import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.XPackUser; @@ -29,14 +29,16 @@ public class TransportSetEnabledAction extends HandledTransportAction listener) { final String username = request.username(); // make sure the user is not disabling themselves - if (Authentication.getAuthentication(threadPool.getThreadContext()).getUser().principal().equals(request.username())) { + if (securityContext.getUser().principal().equals(request.username())) { listener.onFailure(new IllegalArgumentException("users may not update the enabled status of their own account")); return; } else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java index f94e198c0e3..7e4e8577d01 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java @@ -36,6 +36,7 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; import org.elasticsearch.xpack.core.security.authc.Realm; +import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.EmptyAuthorizationInfo; import org.elasticsearch.xpack.core.security.support.Exceptions; import org.elasticsearch.xpack.core.security.user.AnonymousUser; @@ -87,6 +88,7 @@ public class AuthenticationService { private final ApiKeyService apiKeyService; private final boolean runAsEnabled; private final boolean isAnonymousUserEnabled; + private final AuthenticationContextSerializer authenticationSerializer; public AuthenticationService(Settings settings, Realms realms, AuditTrailService auditTrail, AuthenticationFailureHandler failureHandler, ThreadPool threadPool, @@ -109,6 +111,7 @@ public class AuthenticationService { this.lastSuccessfulAuthCache = null; } this.apiKeyService = apiKeyService; + this.authenticationSerializer = new AuthenticationContextSerializer(); } /** @@ -303,7 +306,7 @@ public class AuthenticationService { private void lookForExistingAuthentication(Consumer authenticationConsumer) { Runnable action; try { - final Authentication authentication = Authentication.readFromContext(threadContext); + final Authentication authentication = authenticationSerializer.readFromContext(threadContext); if (authentication != null && request instanceof AuditableRestRequest) { action = () -> listener.onFailure(request.tamperedRequest()); } else { @@ -611,7 +614,7 @@ public class AuthenticationService { listener.onResponse(authentication); }; try { - authentication.writeToContext(threadContext); + authenticationSerializer.writeToContext(authentication, threadContext); } catch (Exception e) { action = () -> { logger.debug( diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java index e1df1650014..41ece7547a0 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java @@ -82,6 +82,7 @@ import org.elasticsearch.xpack.core.XPackField; import org.elasticsearch.xpack.core.XPackPlugin; import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.ScrollHelper; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType; import org.elasticsearch.xpack.core.security.authc.KeyAndTimestamp; @@ -208,6 +209,7 @@ public final class TokenService { private final ExpiredTokenRemover expiredTokenRemover; private final boolean enabled; private final XPackLicenseState licenseState; + private final SecurityContext securityContext; private volatile TokenKeys keyCache; private volatile long lastExpirationRunMs; private final AtomicLong createdTimeStamps = new AtomicLong(-1); @@ -215,7 +217,7 @@ public final class TokenService { /** * Creates a new token service */ - public TokenService(Settings settings, Clock clock, Client client, XPackLicenseState licenseState, + public TokenService(Settings settings, Clock clock, Client client, XPackLicenseState licenseState, SecurityContext securityContext, SecurityIndexManager securityMainIndex, SecurityIndexManager securityTokensIndex, ClusterService clusterService) throws GeneralSecurityException { byte[] saltArr = new byte[SALT_BYTES]; @@ -226,6 +228,7 @@ public final class TokenService { this.expirationDelay = TOKEN_EXPIRATION.get(settings); this.client = client; this.licenseState = licenseState; + this.securityContext = securityContext; this.securityMainIndex = securityMainIndex; this.securityTokensIndex = securityTokensIndex; this.lastExpirationRunMs = client.threadPool().relativeTimeInMillis(); @@ -801,7 +804,7 @@ public final class TokenService { findTokenFromRefreshToken(refreshToken, backoff, ActionListener.wrap(tokenDocHit -> { - final Authentication clientAuth = Authentication.readFromContext(client.threadPool().getThreadContext()); + final Authentication clientAuth = securityContext.getAuthentication(); innerRefresh(refreshToken, tokenDocHit.getId(), tokenDocHit.getSourceAsMap(), tokenDocHit.getSeqNo(), tokenDocHit.getPrimaryTerm(), clientAuth, backoff, refreshRequested, listener); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/SecuritySearchOperationListener.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/SecuritySearchOperationListener.java index 5e0c2945caa..adfe0d4dd8e 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/SecuritySearchOperationListener.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/SecuritySearchOperationListener.java @@ -12,11 +12,12 @@ import org.elasticsearch.search.SearchContextMissingException; import org.elasticsearch.search.internal.ScrollContext; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.AuthenticationField; -import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.AuthorizationInfo; +import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.security.audit.AuditUtil; import static org.elasticsearch.xpack.security.authz.AuthorizationService.AUTHORIZATION_INFO_KEY; import static org.elasticsearch.xpack.security.authz.AuthorizationService.ORIGINATING_ACTION_KEY; @@ -32,12 +33,12 @@ import static org.elasticsearch.xpack.security.authz.AuthorizationService.ORIGIN */ public final class SecuritySearchOperationListener implements SearchOperationListener { - private final ThreadContext threadContext; + private final SecurityContext securityContext; private final XPackLicenseState licenseState; private final AuditTrailService auditTrailService; - public SecuritySearchOperationListener(ThreadContext threadContext, XPackLicenseState licenseState, AuditTrailService auditTrail) { - this.threadContext = threadContext; + public SecuritySearchOperationListener(SecurityContext securityContext, XPackLicenseState licenseState, AuditTrailService auditTrail) { + this.securityContext = securityContext; this.licenseState = licenseState; this.auditTrailService = auditTrail; } @@ -48,8 +49,7 @@ public final class SecuritySearchOperationListener implements SearchOperationLis @Override public void onNewScrollContext(SearchContext searchContext) { if (licenseState.isAuthAllowed()) { - searchContext.scrollContext().putInContext(AuthenticationField.AUTHENTICATION_KEY, - Authentication.getAuthentication(threadContext)); + searchContext.scrollContext().putInContext(AuthenticationField.AUTHENTICATION_KEY, securityContext.getAuthentication()); } } @@ -62,7 +62,8 @@ public final class SecuritySearchOperationListener implements SearchOperationLis if (licenseState.isAuthAllowed()) { if (searchContext.scrollContext() != null) { final Authentication originalAuth = searchContext.scrollContext().getFromContext(AuthenticationField.AUTHENTICATION_KEY); - final Authentication current = Authentication.getAuthentication(threadContext); + final Authentication current = securityContext.getAuthentication(); + final ThreadContext threadContext = securityContext.getThreadContext(); final String action = threadContext.getTransient(ORIGINATING_ACTION_KEY); ensureAuthenticatedUserIsSame(originalAuth, current, auditTrailService, searchContext.id(), action, request, AuditUtil.extractRequestId(threadContext), threadContext.getTransient(AUTHORIZATION_INFO_KEY)); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessor.java index 791e700ff69..ba15eb3b9d5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessor.java @@ -5,10 +5,10 @@ */ package org.elasticsearch.xpack.security.ingest; -import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.ingest.AbstractProcessor; import org.elasticsearch.ingest.IngestDocument; import org.elasticsearch.ingest.Processor; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.user.User; @@ -18,7 +18,9 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.function.Supplier; import static org.elasticsearch.ingest.ConfigurationUtils.newConfigurationException; import static org.elasticsearch.ingest.ConfigurationUtils.readOptionalList; @@ -31,20 +33,21 @@ public final class SetSecurityUserProcessor extends AbstractProcessor { public static final String TYPE = "set_security_user"; - private final ThreadContext threadContext; + private final SecurityContext securityContext; private final String field; private final Set properties; - public SetSecurityUserProcessor(String tag, ThreadContext threadContext, String field, Set properties) { + public + SetSecurityUserProcessor(String tag, SecurityContext securityContext, String field, Set properties) { super(tag); - this.threadContext = threadContext; + this.securityContext = Objects.requireNonNull(securityContext, "security context must be provided"); this.field = field; this.properties = properties; } @Override public IngestDocument execute(IngestDocument ingestDocument) throws Exception { - Authentication authentication = Authentication.getAuthentication(threadContext); + Authentication authentication = securityContext.getAuthentication(); if (authentication == null) { throw new IllegalStateException("No user authenticated, only use this processor via authenticated user"); } @@ -108,10 +111,10 @@ public final class SetSecurityUserProcessor extends AbstractProcessor { public static final class Factory implements Processor.Factory { - private final ThreadContext threadContext; + private final Supplier securityContext; - public Factory(ThreadContext threadContext) { - this.threadContext = threadContext; + public Factory(Supplier securityContext) { + this.securityContext = securityContext; } @Override @@ -128,7 +131,7 @@ public final class SetSecurityUserProcessor extends AbstractProcessor { } else { properties = EnumSet.allOf(Property.class); } - return new SetSecurityUserProcessor(tag, threadContext, field, properties); + return new SetSecurityUserProcessor(tag, securityContext.get(), field, properties); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java index 2d1f63f5cc1..44b103e1edb 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java @@ -123,7 +123,7 @@ public interface ServerTransportFilter { if (securityAction.equals(TransportService.HANDSHAKE_ACTION_NAME) && SystemUser.is(authentication.getUser()) == false) { securityContext.executeAsUser(SystemUser.INSTANCE, (ctx) -> { - final Authentication replaced = Authentication.getAuthentication(threadContext); + final Authentication replaced = securityContext.getAuthentication(); authzService.authorize(replaced, securityAction, request, listener); }, version); } else { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityContextTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityContextTests.java index fd0c4d8f459..86f1c800ddb 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityContextTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityContextTests.java @@ -74,7 +74,7 @@ public class SecurityContextTests extends ESTestCase { IllegalStateException e = expectThrows(IllegalStateException.class, () -> securityContext.setUser(randomFrom(user, SystemUser.INSTANCE), Version.CURRENT)); - assertEquals("authentication is already present in the context", e.getMessage()); + assertEquals("authentication ([_xpack_security_authentication]) is already present in the context", e.getMessage()); } public void testExecuteAsUser() throws IOException { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java index 91e0c4f7a24..8104ddb98c4 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java @@ -40,6 +40,7 @@ import org.elasticsearch.transport.Transport; import org.elasticsearch.transport.TransportService; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.XPackSettings; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectLogoutRequest; import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectLogoutResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; @@ -177,8 +178,8 @@ public class TransportOpenIdConnectLogoutActionTests extends OpenIdConnectTestCa final XPackLicenseState licenseState = mock(XPackLicenseState.class); when(licenseState.isTokenServiceAllowed()).thenReturn(true); - tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, - securityIndex, securityIndex, clusterService); + tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, new SecurityContext(settings, threadContext), + securityIndex, securityIndex, clusterService); final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java index f0337a7a72b..f569d1a45c8 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java @@ -57,6 +57,7 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.Transport; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.core.XPackSettings; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.saml.SamlInvalidateSessionRequest; import org.elasticsearch.xpack.core.security.action.saml.SamlInvalidateSessionResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; @@ -206,7 +207,9 @@ public class TransportSamlInvalidateSessionActionTests extends SamlTestCase { when(licenseState.isTokenServiceAllowed()).thenReturn(true); final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool); - tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, securityIndex, securityIndex, clusterService); + final SecurityContext securityContext = new SecurityContext(settings, threadContext); + tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, securityContext, securityIndex, securityIndex, + clusterService); final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java index 69498eb9961..a6d13e85617 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java @@ -47,6 +47,7 @@ import org.elasticsearch.transport.Transport; import org.elasticsearch.transport.TransportService; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.XPackSettings; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.saml.SamlLogoutRequest; import org.elasticsearch.xpack.core.security.action.saml.SamlLogoutResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; @@ -207,7 +208,9 @@ public class TransportSamlLogoutActionTests extends SamlTestCase { final XPackLicenseState licenseState = mock(XPackLicenseState.class); when(licenseState.isTokenServiceAllowed()).thenReturn(true); final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool); - tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, securityIndex, securityIndex, clusterService); + final SecurityContext securityContext = new SecurityContext(settings, threadContext); + tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, securityContext, securityIndex, securityIndex, + clusterService); final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java index b32ceee1a87..358f33d1b8b 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java @@ -39,6 +39,7 @@ import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.core.XPackSettings; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction; import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest; import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse; @@ -81,6 +82,7 @@ public class TransportCreateTokenActionTests extends ESTestCase { private AtomicReference idxReqReference; private AuthenticationService authenticationService; private XPackLicenseState license; + private SecurityContext securityContext; @Before public void setupClient() { @@ -126,6 +128,8 @@ public class TransportCreateTokenActionTests extends ESTestCase { return null; }).when(client).execute(eq(IndexAction.INSTANCE), any(IndexRequest.class), any(ActionListener.class)); + securityContext = new SecurityContext(Settings.EMPTY, threadPool.getThreadContext()); + // setup lifecycle service securityIndex = mock(SecurityIndexManager.class); doAnswer(invocationOnMock -> { @@ -175,14 +179,14 @@ public class TransportCreateTokenActionTests extends ESTestCase { } public void testClientCredentialsCreatesWithoutRefreshToken() throws Exception { - final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license, + final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license, securityContext, securityIndex, securityIndex, clusterService); Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null); authentication.writeToContext(threadPool.getThreadContext()); final TransportCreateTokenAction action = new TransportCreateTokenAction(threadPool, mock(TransportService.class), new ActionFilters(Collections.emptySet()), tokenService, - authenticationService); + authenticationService, securityContext); final CreateTokenRequest createTokenRequest = new CreateTokenRequest(); createTokenRequest.setGrantType("client_credentials"); @@ -200,14 +204,14 @@ public class TransportCreateTokenActionTests extends ESTestCase { } public void testPasswordGrantTypeCreatesWithRefreshToken() throws Exception { - final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license, + final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license, securityContext, securityIndex, securityIndex, clusterService); Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null); authentication.writeToContext(threadPool.getThreadContext()); final TransportCreateTokenAction action = new TransportCreateTokenAction(threadPool, mock(TransportService.class), new ActionFilters(Collections.emptySet()), tokenService, - authenticationService); + authenticationService, securityContext); final CreateTokenRequest createTokenRequest = new CreateTokenRequest(); createTokenRequest.setGrantType("password"); createTokenRequest.setUsername("user"); @@ -227,14 +231,14 @@ public class TransportCreateTokenActionTests extends ESTestCase { } public void testKerberosGrantTypeCreatesWithRefreshToken() throws Exception { - final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license, + final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license, securityContext, securityIndex, securityIndex, clusterService); Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null); authentication.writeToContext(threadPool.getThreadContext()); final TransportCreateTokenAction action = new TransportCreateTokenAction(threadPool, mock(TransportService.class), new ActionFilters(Collections.emptySet()), tokenService, - authenticationService); + authenticationService, securityContext); final CreateTokenRequest createTokenRequest = new CreateTokenRequest(); createTokenRequest.setGrantType("_kerberos"); String failOrSuccess = randomBoolean() ? "fail" : "success"; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportSetEnabledActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportSetEnabledActionTests.java index d811b6359b1..3fc6d40c999 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportSetEnabledActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportSetEnabledActionTests.java @@ -16,10 +16,11 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.Transport; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.user.SetEnabledRequest; import org.elasticsearch.xpack.core.security.action.user.SetEnabledResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; -import org.elasticsearch.xpack.core.security.authc.AuthenticationField; +import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.ElasticUser; import org.elasticsearch.xpack.core.security.user.KibanaUser; @@ -53,20 +54,23 @@ import static org.mockito.Mockito.when; */ public class TransportSetEnabledActionTests extends ESTestCase { - public void testAnonymousUser() { + public void testAnonymousUser() throws Exception { Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build(); final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe")); ThreadPool threadPool = mock(ThreadPool.class); ThreadContext threadContext = new ThreadContext(Settings.EMPTY); - Authentication authentication = mock(Authentication.class); when(threadPool.getThreadContext()).thenReturn(threadContext); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + Authentication authentication = mock(Authentication.class); when(authentication.getUser()).thenReturn(user); + when(authentication.encode()).thenReturn(randomAlphaOfLength(24)); // just can't be null + new AuthenticationContextSerializer().writeToContext(authentication, threadContext); + NativeUsersStore usersStore = mock(NativeUsersStore.class); TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); + final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext); TransportSetEnabledAction action = new TransportSetEnabledAction(settings, threadPool, transportService, mock(ActionFilters.class), - usersStore); + securityContext, usersStore); SetEnabledRequest request = new SetEnabledRequest(); request.username(new AnonymousUser(settings).principal()); @@ -92,19 +96,23 @@ public class TransportSetEnabledActionTests extends ESTestCase { verifyZeroInteractions(usersStore); } - public void testInternalUser() { + public void testInternalUser() throws Exception { final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe")); ThreadPool threadPool = mock(ThreadPool.class); ThreadContext threadContext = new ThreadContext(Settings.EMPTY); - Authentication authentication = mock(Authentication.class); when(threadPool.getThreadContext()).thenReturn(threadContext); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + + Authentication authentication = mock(Authentication.class); when(authentication.getUser()).thenReturn(user); + when(authentication.encode()).thenReturn(randomAlphaOfLength(24)); // just can't be null + new AuthenticationContextSerializer().writeToContext(authentication, threadContext); + NativeUsersStore usersStore = mock(NativeUsersStore.class); TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); + final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext); TransportSetEnabledAction action = new TransportSetEnabledAction(Settings.EMPTY, threadPool, transportService, - mock(ActionFilters.class), usersStore); + mock(ActionFilters.class), securityContext, usersStore); SetEnabledRequest request = new SetEnabledRequest(); request.username(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal())); @@ -130,13 +138,15 @@ public class TransportSetEnabledActionTests extends ESTestCase { verifyZeroInteractions(usersStore); } - public void testValidUser() { + public void testValidUser() throws Exception { ThreadPool threadPool = mock(ThreadPool.class); ThreadContext threadContext = new ThreadContext(Settings.EMPTY); - Authentication authentication = mock(Authentication.class); when(threadPool.getThreadContext()).thenReturn(threadContext); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + + Authentication authentication = mock(Authentication.class); when(authentication.getUser()).thenReturn(new User("the runner")); + when(authentication.encode()).thenReturn(randomAlphaOfLength(24)); // just can't be null + new AuthenticationContextSerializer().writeToContext(authentication, threadContext); final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe")); NativeUsersStore usersStore = mock(NativeUsersStore.class); @@ -157,8 +167,9 @@ public class TransportSetEnabledActionTests extends ESTestCase { .setEnabled(eq(user.principal()), eq(request.enabled()), eq(request.getRefreshPolicy()), any(ActionListener.class)); TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); + final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext); TransportSetEnabledAction action = new TransportSetEnabledAction(Settings.EMPTY, threadPool, transportService, - mock(ActionFilters.class), usersStore); + mock(ActionFilters.class), securityContext, usersStore); final AtomicReference throwableRef = new AtomicReference<>(); final AtomicReference responseRef = new AtomicReference<>(); @@ -181,13 +192,15 @@ public class TransportSetEnabledActionTests extends ESTestCase { .setEnabled(eq(user.principal()), eq(request.enabled()), eq(request.getRefreshPolicy()), any(ActionListener.class)); } - public void testException() { + public void testException() throws Exception { ThreadPool threadPool = mock(ThreadPool.class); ThreadContext threadContext = new ThreadContext(Settings.EMPTY); - Authentication authentication = mock(Authentication.class); when(threadPool.getThreadContext()).thenReturn(threadContext); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + + Authentication authentication = mock(Authentication.class); when(authentication.getUser()).thenReturn(new User("the runner")); + when(authentication.encode()).thenReturn(randomAlphaOfLength(24)); // just can't be null + new AuthenticationContextSerializer().writeToContext(authentication, threadContext); final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe")); NativeUsersStore usersStore = mock(NativeUsersStore.class); @@ -209,8 +222,9 @@ public class TransportSetEnabledActionTests extends ESTestCase { .setEnabled(eq(user.principal()), eq(request.enabled()), eq(request.getRefreshPolicy()), any(ActionListener.class)); TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); + final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext); TransportSetEnabledAction action = new TransportSetEnabledAction(Settings.EMPTY, threadPool, transportService, - mock(ActionFilters.class), usersStore); + mock(ActionFilters.class), securityContext, usersStore); final AtomicReference throwableRef = new AtomicReference<>(); final AtomicReference responseRef = new AtomicReference<>(); @@ -233,14 +247,16 @@ public class TransportSetEnabledActionTests extends ESTestCase { .setEnabled(eq(user.principal()), eq(request.enabled()), eq(request.getRefreshPolicy()), any(ActionListener.class)); } - public void testUserModifyingThemselves() { + public void testUserModifyingThemselves() throws Exception { final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe")); ThreadPool threadPool = mock(ThreadPool.class); ThreadContext threadContext = new ThreadContext(Settings.EMPTY); - Authentication authentication = mock(Authentication.class); when(threadPool.getThreadContext()).thenReturn(threadContext); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + + Authentication authentication = mock(Authentication.class); when(authentication.getUser()).thenReturn(user); + when(authentication.encode()).thenReturn(randomAlphaOfLength(24)); // just can't be null + new AuthenticationContextSerializer().writeToContext(authentication, threadContext); NativeUsersStore usersStore = mock(NativeUsersStore.class); SetEnabledRequest request = new SetEnabledRequest(); @@ -249,8 +265,9 @@ public class TransportSetEnabledActionTests extends ESTestCase { request.setRefreshPolicy(randomFrom(RefreshPolicy.values())); TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); + final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext); TransportSetEnabledAction action = new TransportSetEnabledAction(Settings.EMPTY, threadPool, transportService, - mock(ActionFilters.class), usersStore); + mock(ActionFilters.class), securityContext, usersStore); final AtomicReference throwableRef = new AtomicReference<>(); final AtomicReference responseRef = new AtomicReference<>(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index 0737087429d..af80fce0523 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -57,6 +57,7 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportMessage; import org.elasticsearch.xpack.core.XPackField; import org.elasticsearch.xpack.core.XPackSettings; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType; import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef; @@ -231,8 +232,10 @@ public class AuthenticationServiceTests extends ESTestCase { return null; }).when(securityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class)); ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool); + final SecurityContext securityContext = new SecurityContext(settings, threadContext); apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, licenseState, securityIndex, clusterService, threadPool); - tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, securityIndex, securityIndex, clusterService); + tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, securityContext, securityIndex, securityIndex, + clusterService); service = new AuthenticationService(settings, realms, auditTrail, new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, new AnonymousUser(settings), tokenService, apiKeyService); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java index 9721ffb04fb..df88a44df84 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java @@ -46,6 +46,7 @@ import org.elasticsearch.test.EqualsHashCodeTestUtils; import org.elasticsearch.threadpool.FixedExecutorBuilder; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.core.XPackSettings; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType; import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef; @@ -103,6 +104,7 @@ public class TokenServiceTests extends ESTestCase { private Settings tokenServiceEnabledSettings = Settings.builder() .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true).build(); private XPackLicenseState licenseState; + private SecurityContext securityContext; @Before public void setupClient() { @@ -127,6 +129,7 @@ public class TokenServiceTests extends ESTestCase { return null; }).when(client).execute(eq(IndexAction.INSTANCE), any(IndexRequest.class), any(ActionListener.class)); + this.securityContext = new SecurityContext(settings, threadPool.getThreadContext()); // setup lifecycle service this.securityMainIndex = SecurityMocks.mockSecurityIndexManager(); this.securityTokensIndex = SecurityMocks.mockSecurityIndexManager(); @@ -557,7 +560,7 @@ public class TokenServiceTests extends ESTestCase { TokenService tokenService = new TokenService(Settings.builder() .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false) .build(), - Clock.systemUTC(), client, licenseState, securityMainIndex, securityTokensIndex, clusterService); + Clock.systemUTC(), client, licenseState, securityContext, securityMainIndex, securityTokensIndex, clusterService); IllegalStateException e = expectThrows(IllegalStateException.class, () -> tokenService.createOAuth2Tokens(null, null, null, true, null)); assertEquals("security tokens are not enabled", e.getMessage()); @@ -763,7 +766,8 @@ public class TokenServiceTests extends ESTestCase { } private TokenService createTokenService(Settings settings, Clock clock) throws GeneralSecurityException { - return new TokenService(settings, clock, client, licenseState, securityMainIndex, securityTokensIndex, clusterService); + return new TokenService(settings, clock, client, licenseState, securityContext, securityMainIndex, securityTokensIndex, + clusterService); } private void mockGetTokenFromId(TokenService tokenService, String accessToken, Authentication authentication, boolean isExpired) { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SecuritySearchOperationListenerTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SecuritySearchOperationListenerTests.java index 5b73d6d212f..aa3b612e513 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SecuritySearchOperationListenerTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SecuritySearchOperationListenerTests.java @@ -19,6 +19,7 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.TestSearchContext; import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportRequest.Empty; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef; import org.elasticsearch.xpack.core.security.authc.AuthenticationField; @@ -47,11 +48,12 @@ public class SecuritySearchOperationListenerTests extends ESTestCase { XPackLicenseState licenseState = mock(XPackLicenseState.class); when(licenseState.isAuthAllowed()).thenReturn(false); ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext); AuditTrailService auditTrailService = mock(AuditTrailService.class); SearchContext searchContext = mock(SearchContext.class); when(searchContext.scrollContext()).thenReturn(new ScrollContext()); - SecuritySearchOperationListener listener = new SecuritySearchOperationListener(threadContext, licenseState, auditTrailService); + SecuritySearchOperationListener listener = new SecuritySearchOperationListener(securityContext, licenseState, auditTrailService); listener.onNewScrollContext(searchContext); listener.validateSearchContext(searchContext, Empty.INSTANCE); verify(licenseState, times(2)).isAuthAllowed(); @@ -66,11 +68,12 @@ public class SecuritySearchOperationListenerTests extends ESTestCase { XPackLicenseState licenseState = mock(XPackLicenseState.class); when(licenseState.isAuthAllowed()).thenReturn(true); ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext); AuditTrailService auditTrailService = mock(AuditTrailService.class); Authentication authentication = new Authentication(new User("test", "role"), new RealmRef("realm", "file", "node"), null); authentication.writeToContext(threadContext); - SecuritySearchOperationListener listener = new SecuritySearchOperationListener(threadContext, licenseState, auditTrailService); + SecuritySearchOperationListener listener = new SecuritySearchOperationListener(securityContext, licenseState, auditTrailService); listener.onNewScrollContext(testSearchContext); Authentication contextAuth = testSearchContext.scrollContext().getFromContext(AuthenticationField.AUTHENTICATION_KEY); @@ -90,9 +93,10 @@ public class SecuritySearchOperationListenerTests extends ESTestCase { XPackLicenseState licenseState = mock(XPackLicenseState.class); when(licenseState.isAuthAllowed()).thenReturn(true); ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext); AuditTrailService auditTrailService = mock(AuditTrailService.class); - SecuritySearchOperationListener listener = new SecuritySearchOperationListener(threadContext, licenseState, auditTrailService); + SecuritySearchOperationListener listener = new SecuritySearchOperationListener(securityContext, licenseState, auditTrailService); try (StoredContext ignore = threadContext.newStoredContext(false)) { Authentication authentication = new Authentication(new User("test", "role"), new RealmRef("realm", "file", "node"), null); authentication.writeToContext(threadContext); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessorFactoryTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessorFactoryTests.java index 01acbc19c2a..d1efb8917a9 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessorFactoryTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessorFactoryTests.java @@ -6,20 +6,33 @@ package org.elasticsearch.xpack.security.ingest; import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor.Property; +import org.junit.Before; import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; +import static org.elasticsearch.test.TestMatchers.throwableWithMessage; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; public class SetSecurityUserProcessorFactoryTests extends ESTestCase { + private SecurityContext securityContext; + + @Before + public void setupContext() { + securityContext = new SecurityContext(Settings.EMPTY, new ThreadContext(Settings.EMPTY)); + } + public void testProcessor() throws Exception { - SetSecurityUserProcessor.Factory factory = new SetSecurityUserProcessor.Factory(null); + SetSecurityUserProcessor.Factory factory = new SetSecurityUserProcessor.Factory(() -> securityContext); Map config = new HashMap<>(); config.put("field", "_field"); SetSecurityUserProcessor processor = factory.create(null, "_tag", config); @@ -28,7 +41,7 @@ public class SetSecurityUserProcessorFactoryTests extends ESTestCase { } public void testProcessor_noField() throws Exception { - SetSecurityUserProcessor.Factory factory = new SetSecurityUserProcessor.Factory(null); + SetSecurityUserProcessor.Factory factory = new SetSecurityUserProcessor.Factory(() -> securityContext); Map config = new HashMap<>(); ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, () -> factory.create(null, "_tag", config)); assertThat(e.getMetadata("es.property_name").get(0), equalTo("field")); @@ -37,7 +50,7 @@ public class SetSecurityUserProcessorFactoryTests extends ESTestCase { } public void testProcessor_validProperties() throws Exception { - SetSecurityUserProcessor.Factory factory = new SetSecurityUserProcessor.Factory(null); + SetSecurityUserProcessor.Factory factory = new SetSecurityUserProcessor.Factory(() -> securityContext); Map config = new HashMap<>(); config.put("field", "_field"); config.put("properties", Arrays.asList(Property.USERNAME.name(), Property.ROLES.name())); @@ -47,7 +60,7 @@ public class SetSecurityUserProcessorFactoryTests extends ESTestCase { } public void testProcessor_invalidProperties() throws Exception { - SetSecurityUserProcessor.Factory factory = new SetSecurityUserProcessor.Factory(null); + SetSecurityUserProcessor.Factory factory = new SetSecurityUserProcessor.Factory(() -> securityContext); Map config = new HashMap<>(); config.put("field", "_field"); config.put("properties", Arrays.asList("invalid")); @@ -57,4 +70,12 @@ public class SetSecurityUserProcessorFactoryTests extends ESTestCase { assertThat(e.getMetadata("es.processor_tag").get(0), equalTo("_tag")); } + public void testNullSecurityContextThrowsException() throws Exception { + SetSecurityUserProcessor.Factory factory = new SetSecurityUserProcessor.Factory(() -> null); + Map config = new HashMap<>(); + config.put("field", "_field"); + NullPointerException e = expectThrows(NullPointerException.class, () -> factory.create(null, "_tag", config)); + assertThat(e, throwableWithMessage(containsString("security context"))); + } + } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessorTests.java index 428445043f0..d2246520b6b 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessorTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/ingest/SetSecurityUserProcessorTests.java @@ -9,10 +9,13 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.ingest.IngestDocument; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authc.Authentication; -import org.elasticsearch.xpack.core.security.authc.AuthenticationField; +import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType; +import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor.Property; +import org.junit.Before; import org.mockito.Mockito; import java.util.Collections; @@ -25,15 +28,23 @@ import static org.hamcrest.Matchers.equalTo; public class SetSecurityUserProcessorTests extends ESTestCase { - public void testProcessor() throws Exception { - User user = new User("_username", new String[]{"role1", "role2"}, "firstname lastname", "_email", - Collections.singletonMap("key", "value"), true); + private ThreadContext threadContext; + private SecurityContext securityContext; + + @Before + public void setupObjects() { + threadContext = new ThreadContext(Settings.EMPTY); + securityContext = new SecurityContext(Settings.EMPTY, threadContext); + } + + public void testProcessorWithData() throws Exception { + User user = new User("_username", new String[] { "role1", "role2" }, "firstname lastname", "_email", + Collections.singletonMap("key", "value"), true); Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name"); - ThreadContext threadContext = new ThreadContext(Settings.EMPTY); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null)); + new Authentication(user, realmRef, null).writeToContext(threadContext); IngestDocument ingestDocument = new IngestDocument(new HashMap<>(), new HashMap<>()); - SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", threadContext, "_field", EnumSet.allOf(Property.class)); + SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", securityContext, "_field", EnumSet.allOf(Property.class)); processor.execute(ingestDocument); Map result = ingestDocument.getFieldValue("_field", Map.class); @@ -51,33 +62,32 @@ public class SetSecurityUserProcessorTests extends ESTestCase { User user = Mockito.mock(User.class); Authentication authentication = Mockito.mock(Authentication.class); Mockito.when(authentication.getUser()).thenReturn(user); - - ThreadContext threadContext = new ThreadContext(Settings.EMPTY); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + Mockito.when(authentication.getAuthenticatedBy()).thenReturn(new Authentication.RealmRef("_name", "_type", "_node_name")); + Mockito.when(authentication.getAuthenticationType()).thenReturn(AuthenticationType.REALM); + Mockito.when(authentication.encode()).thenReturn(randomAlphaOfLength(24)); // don't care as long as it's not null + new AuthenticationContextSerializer().writeToContext(authentication, threadContext); IngestDocument ingestDocument = new IngestDocument(new HashMap<>(), new HashMap<>()); - SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", threadContext, "_field", EnumSet.allOf(Property.class)); + SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", securityContext, "_field", EnumSet.allOf(Property.class)); processor.execute(ingestDocument); Map result = ingestDocument.getFieldValue("_field", Map.class); assertThat(result.size(), equalTo(0)); } public void testNoCurrentUser() throws Exception { - ThreadContext threadContext = new ThreadContext(Settings.EMPTY); IngestDocument ingestDocument = new IngestDocument(new HashMap<>(), new HashMap<>()); - SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", threadContext, "_field", EnumSet.allOf(Property.class)); + SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", securityContext, "_field", EnumSet.allOf(Property.class)); IllegalStateException e = expectThrows(IllegalStateException.class, () -> processor.execute(ingestDocument)); assertThat(e.getMessage(), equalTo("No user authenticated, only use this processor via authenticated user")); } public void testUsernameProperties() throws Exception { - ThreadContext threadContext = new ThreadContext(Settings.EMPTY); User user = new User("_username", null, null); Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name"); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null)); + new Authentication(user, realmRef, null).writeToContext(threadContext); IngestDocument ingestDocument = new IngestDocument(new HashMap<>(), new HashMap<>()); - SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", threadContext, "_field", EnumSet.of(Property.USERNAME)); + SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", securityContext, "_field", EnumSet.of(Property.USERNAME)); processor.execute(ingestDocument); @SuppressWarnings("unchecked") @@ -87,13 +97,12 @@ public class SetSecurityUserProcessorTests extends ESTestCase { } public void testRolesProperties() throws Exception { - ThreadContext threadContext = new ThreadContext(Settings.EMPTY); User user = new User(randomAlphaOfLengthBetween(4, 12), "role1", "role2"); Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name"); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null)); + new Authentication(user, realmRef, null).writeToContext(threadContext); IngestDocument ingestDocument = new IngestDocument(new HashMap<>(), new HashMap<>()); - SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", threadContext, "_field", EnumSet.of(Property.ROLES)); + SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", securityContext, "_field", EnumSet.of(Property.ROLES)); processor.execute(ingestDocument); @SuppressWarnings("unchecked") @@ -103,13 +112,13 @@ public class SetSecurityUserProcessorTests extends ESTestCase { } public void testFullNameProperties() throws Exception { - ThreadContext threadContext = new ThreadContext(Settings.EMPTY); User user = new User(randomAlphaOfLengthBetween(4, 12), null, "_full_name", null, null, true); Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name"); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null)); + new Authentication(user, realmRef, null).writeToContext(threadContext); IngestDocument ingestDocument = new IngestDocument(new HashMap<>(), new HashMap<>()); - SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", threadContext, "_field", EnumSet.of(Property.FULL_NAME)); + SetSecurityUserProcessor processor + = new SetSecurityUserProcessor("_tag", securityContext, "_field", EnumSet.of(Property.FULL_NAME)); processor.execute(ingestDocument); @SuppressWarnings("unchecked") @@ -119,13 +128,12 @@ public class SetSecurityUserProcessorTests extends ESTestCase { } public void testEmailProperties() throws Exception { - ThreadContext threadContext = new ThreadContext(Settings.EMPTY); User user = new User(randomAlphaOfLengthBetween(4, 12), null, null, "_email", null, true); Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name"); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null)); + new Authentication(user, realmRef, null).writeToContext(threadContext); IngestDocument ingestDocument = new IngestDocument(new HashMap<>(), new HashMap<>()); - SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", threadContext, "_field", EnumSet.of(Property.EMAIL)); + SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", securityContext, "_field", EnumSet.of(Property.EMAIL)); processor.execute(ingestDocument); @SuppressWarnings("unchecked") @@ -135,13 +143,12 @@ public class SetSecurityUserProcessorTests extends ESTestCase { } public void testMetadataProperties() throws Exception { - ThreadContext threadContext = new ThreadContext(Settings.EMPTY); User user = new User(randomAlphaOfLengthBetween(4, 12), null, null, null, Collections.singletonMap("key", "value"), true); Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name"); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null)); + new Authentication(user, realmRef, null).writeToContext(threadContext); IngestDocument ingestDocument = new IngestDocument(new HashMap<>(), new HashMap<>()); - SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", threadContext, "_field", EnumSet.of(Property.METADATA)); + SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", securityContext, "_field", EnumSet.of(Property.METADATA)); processor.execute(ingestDocument); @SuppressWarnings("unchecked") @@ -152,12 +159,11 @@ public class SetSecurityUserProcessorTests extends ESTestCase { } public void testOverwriteExistingField() throws Exception { - ThreadContext threadContext = new ThreadContext(Settings.EMPTY); User user = new User("_username", null, null); Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name"); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null)); + new Authentication(user, realmRef, null).writeToContext(threadContext); - SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", threadContext, "_field", EnumSet.of(Property.USERNAME)); + SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", securityContext, "_field", EnumSet.of(Property.USERNAME)); IngestDocument ingestDocument = new IngestDocument(new HashMap<>(), new HashMap<>()); ingestDocument.setFieldValue("_field", "test");