Skip to content

Commit

Permalink
Authentication under domains (#82639)
Browse files Browse the repository at this point in the history
This makes the ubiquitous Authentication object contain the
domain information that will later be used for access control
decisions related to ownership.
The domain information is a Set of RealmIdentifiers of the
authentication realms configured under the same domain name.
  • Loading branch information
albertzaharovits committed Feb 4, 2022
1 parent fe29908 commit 53adb09
Show file tree
Hide file tree
Showing 41 changed files with 1,167 additions and 602 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/82639.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 82639
summary: Authentication under domains
area: Authentication
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,29 @@
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.Version;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.concurrent.ThreadContext.StoredContext;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.node.Node;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentType;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType;
import org.elasticsearch.xpack.core.security.authc.AuthenticationField;
import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer;
import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

import static org.elasticsearch.xpack.core.security.authc.Authentication.VERSION_API_KEY_ROLES_AS_BYTES;
import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.ATTACH_REALM_NAME;
import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.ATTACH_REALM_TYPE;

/**
* A lightweight utility that can find the current user and authentication information for the local thread.
*/
public class SecurityContext {

private final Logger logger = LogManager.getLogger(SecurityContext.class);
private static final Logger logger = LogManager.getLogger(SecurityContext.class);

private final ThreadContext threadContext;
private final AuthenticationContextSerializer authenticationSerializer;
Expand Down Expand Up @@ -107,41 +95,32 @@ public ThreadContext getThreadContext() {
* 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.
*/
public void setUser(User user, Version version) {
Objects.requireNonNull(user);
final Authentication.RealmRef authenticatedBy = new Authentication.RealmRef(ATTACH_REALM_NAME, ATTACH_REALM_TYPE, nodeName);
final Authentication.RealmRef lookedUpBy;
if (user.isRunAs()) {
lookedUpBy = authenticatedBy;
} else {
lookedUpBy = null;
}
setAuthentication(
new Authentication(user, authenticatedBy, lookedUpBy, version, AuthenticationType.INTERNAL, Collections.emptyMap())
);
}

/** Writes the authentication to the thread context */
private void setAuthentication(Authentication authentication) {
try {
authentication.writeToContext(threadContext);
} catch (IOException e) {
throw new AssertionError("how can we have a IOException with a user we set", e);
}
public void setInternalUser(User internalUser, Version version) {
assert User.isInternal(internalUser);
setAuthentication(Authentication.newInternalAuthentication(internalUser, version, nodeName));
}

/**
* Runs the consumer in a new context as the provided user. The original context is provided to the consumer. When this method
* returns, the original context is restored.
*/
public void executeAsUser(User user, Consumer<StoredContext> consumer, Version version) {
public void executeAsInternalUser(User internalUser, Version version, Consumer<StoredContext> consumer) {
assert User.isInternal(internalUser);
final StoredContext original = threadContext.newStoredContext(true);
try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
setUser(user, version);
setInternalUser(internalUser, version);
consumer.accept(original);
}
}

public void executeAsSystemUser(Consumer<StoredContext> consumer) {
executeAsSystemUser(Version.CURRENT, consumer);
}

public void executeAsSystemUser(Version version, Consumer<StoredContext> consumer) {
executeAsInternalUser(SystemUser.INSTANCE, version, consumer);
}

/**
* Runs the consumer in a new context as the provided user. The original context is provided to the consumer. When this method
* returns, the original context is restored.
Expand All @@ -164,16 +143,7 @@ public void executeAfterRewritingAuthentication(Consumer<StoredContext> consumer
final StoredContext original = threadContext.newStoredContext(true);
final Authentication authentication = getAuthentication();
try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
setAuthentication(
new Authentication(
authentication.getUser(),
authentication.getAuthenticatedBy(),
authentication.getLookedUpBy(),
version,
authentication.getAuthenticationType(),
rewriteMetadataForApiKeyRoleDescriptors(version, authentication)
)
);
setAuthentication(authentication.maybeRewriteForOlderVersion(version));
existingRequestHeaders.forEach((k, v) -> {
if (threadContext.getHeader(k) == null) {
threadContext.putHeader(k, v);
Expand All @@ -183,54 +153,12 @@ public void executeAfterRewritingAuthentication(Consumer<StoredContext> consumer
}
}

@SuppressWarnings("unchecked")
private Map<String, Object> rewriteMetadataForApiKeyRoleDescriptors(Version streamVersion, Authentication authentication) {
Map<String, Object> metadata = authentication.getMetadata();
// If authentication type is API key, regardless whether it has run-as, the metadata must contain API key role descriptors
if (authentication.isAuthenticatedWithApiKey()) {
if (authentication.getVersion().onOrAfter(VERSION_API_KEY_ROLES_AS_BYTES)
&& streamVersion.before(VERSION_API_KEY_ROLES_AS_BYTES)) {
metadata = new HashMap<>(metadata);
metadata.put(
AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY,
convertRoleDescriptorsBytesToMap((BytesReference) metadata.get(AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY))
);
metadata.put(
AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY,
convertRoleDescriptorsBytesToMap(
(BytesReference) metadata.get(AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY)
)
);
} else if (authentication.getVersion().before(VERSION_API_KEY_ROLES_AS_BYTES)
&& streamVersion.onOrAfter(VERSION_API_KEY_ROLES_AS_BYTES)) {
metadata = new HashMap<>(metadata);
metadata.put(
AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY,
convertRoleDescriptorsMapToBytes(
(Map<String, Object>) metadata.get(AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY)
)
);
metadata.put(
AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY,
convertRoleDescriptorsMapToBytes(
(Map<String, Object>) metadata.get(AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY)
)
);
}
}
return metadata;
}

private Map<String, Object> convertRoleDescriptorsBytesToMap(BytesReference roleDescriptorsBytes) {
return XContentHelper.convertToMap(roleDescriptorsBytes, false, XContentType.JSON).v2();
}

private BytesReference convertRoleDescriptorsMapToBytes(Map<String, Object> roleDescriptorsMap) {
try (XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent())) {
builder.map(roleDescriptorsMap);
return BytesReference.bytes(builder);
/** Writes the authentication to the thread context */
private void setAuthentication(Authentication authentication) {
try {
authentication.writeToContext(threadContext);
} catch (IOException e) {
throw new UncheckedIOException(e);
throw new AssertionError("how can we have a IOException with a user we set", e);
}
}
}

0 comments on commit 53adb09

Please sign in to comment.