Skip to content

Commit

Permalink
Naming refactor to use cross cluster access consistently (#94424)
Browse files Browse the repository at this point in the history
Rename refactor PR that uses `cross_cluster_access` in place of
`remote_access` wherever appropriate, since `cross_cluster_access` is a
more precise, clearer term. No functional changes, however I did make a
few tweaks around version handling.
  • Loading branch information
n1v0lg committed Mar 14, 2023
1 parent c40fc1f commit 90f6af3
Show file tree
Hide file tree
Showing 51 changed files with 873 additions and 797 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ tasks.register("run-ccs", RunTask) {
println "** Fulfilling cluster transport endpoints are: ${-> fulfillingCluster.get().getAllTransportPortURI().join(",")}"
if (false == basicSecurityMode) {
println "** Fulfilling cluster remote access endpoints are: ${-> fulfillingCluster.get().getAllRemoteAccessPortURI().join(",")}"
println "** NOTE: you must manually configure a remote access API key on the two clusters to run cross cluster operations"
println "** NOTE: you must manually configure a cross cluster access API key on the two clusters to run cross cluster operations"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ private static void configureRemoteCluster() throws IOException {
final var createApiKeyRequest = new Request("POST", "/_security/api_key");
createApiKeyRequest.setJsonEntity("""
{
"name": "remote_access_key",
"name": "cross_cluster_access_key",
"role_descriptors": {
"role": {
"cluster": ["cross_cluster_access"],
Expand All @@ -225,10 +225,10 @@ private static void configureRemoteCluster() throws IOException {
final Response createApiKeyResponse = adminClient().performRequest(createApiKeyRequest);
assertOK(createApiKeyResponse);
final Map<String, Object> apiKeyMap = responseAsMap(createApiKeyResponse);
final String encodedRemoteAccessApiKey = (String) apiKeyMap.get("encoded");
final String encodedCrossClusterAccessApiKey = (String) apiKeyMap.get("encoded");

final Settings.Builder builder = Settings.builder()
.put("cluster.remote." + REMOTE_CLUSTER_NAME + ".authorization", encodedRemoteAccessApiKey);
.put("cluster.remote." + REMOTE_CLUSTER_NAME + ".authorization", encodedCrossClusterAccessApiKey);
if (randomBoolean()) {
builder.put("cluster.remote." + REMOTE_CLUSTER_NAME + ".mode", "proxy")
.put("cluster.remote." + REMOTE_CLUSTER_NAME + ".proxy_address", fulfillingCluster.getRemoteClusterServerEndpoint(0));
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ public final class AuthenticationField {
public static final String ATTACH_REALM_NAME = "__attach";
public static final String ATTACH_REALM_TYPE = "__attach";

public static final String REMOTE_ACCESS_REALM_NAME = "_es_remote_access";
public static final String REMOTE_ACCESS_REALM_TYPE = "_es_remote_access";
public static final String REMOTE_ACCESS_AUTHENTICATION_KEY = "_security_remote_access_authentication";
public static final String REMOTE_ACCESS_ROLE_DESCRIPTORS_KEY = "_security_remote_access_role_descriptors";
public static final String CROSS_CLUSTER_ACCESS_REALM_NAME = "_es_cross_cluster_access";
public static final String CROSS_CLUSTER_ACCESS_REALM_TYPE = "_es_cross_cluster_access";
public static final String CROSS_CLUSTER_ACCESS_AUTHENTICATION_KEY = "_security_cross_cluster_access_authentication";
public static final String CROSS_CLUSTER_ACCESS_ROLE_DESCRIPTORS_KEY = "_security_cross_cluster_access_role_descriptors";

private AuthenticationField() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,31 @@
import java.util.Objects;
import java.util.Set;

public final class RemoteAccessAuthentication {
public static final String REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY = "_remote_access_authentication";
public final class CrossClusterAccessSubjectInfo {
public static final String CROSS_CLUSTER_ACCESS_SUBJECT_INFO_HEADER_KEY = "_cross_cluster_access_subject_info";
private final Authentication authentication;
private final List<RoleDescriptorsBytes> roleDescriptorsBytesList;

public RemoteAccessAuthentication(Authentication authentication, RoleDescriptorsIntersection roleDescriptorsIntersection)
public CrossClusterAccessSubjectInfo(Authentication authentication, RoleDescriptorsIntersection roleDescriptorsIntersection)
throws IOException {
this(authentication, toRoleDescriptorsBytesList(roleDescriptorsIntersection));
}

private RemoteAccessAuthentication(Authentication authentication, List<RoleDescriptorsBytes> roleDescriptorsBytesList) {
private CrossClusterAccessSubjectInfo(Authentication authentication, List<RoleDescriptorsBytes> roleDescriptorsBytesList) {
this.authentication = authentication;
this.roleDescriptorsBytesList = roleDescriptorsBytesList;
}

public void writeToContext(final ThreadContext ctx) throws IOException {
ctx.putHeader(REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY, encode());
ctx.putHeader(CROSS_CLUSTER_ACCESS_SUBJECT_INFO_HEADER_KEY, encode());
}

public static RemoteAccessAuthentication readFromContext(final ThreadContext ctx) throws IOException {
final String header = ctx.getHeader(REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY);
public static CrossClusterAccessSubjectInfo readFromContext(final ThreadContext ctx) throws IOException {
final String header = ctx.getHeader(CROSS_CLUSTER_ACCESS_SUBJECT_INFO_HEADER_KEY);
if (header == null) {
throw new IllegalArgumentException("remote access header [" + REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY + "] is required");
throw new IllegalArgumentException(
"cross cluster access header [" + CROSS_CLUSTER_ACCESS_SUBJECT_INFO_HEADER_KEY + "] is required"
);
}
return decode(header);
}
Expand All @@ -76,7 +78,7 @@ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

RemoteAccessAuthentication that = (RemoteAccessAuthentication) o;
CrossClusterAccessSubjectInfo that = (CrossClusterAccessSubjectInfo) o;

if (false == authentication.equals(that.authentication)) return false;
return roleDescriptorsBytesList.equals(that.roleDescriptorsBytesList);
Expand All @@ -91,7 +93,7 @@ public int hashCode() {

@Override
public String toString() {
return "RemoteAccessAuthentication{"
return "CrossClusterAccessSubjectInfo{"
+ "authentication="
+ authentication
+ ", roleDescriptorsBytesList="
Expand All @@ -103,8 +105,9 @@ private static List<RoleDescriptorsBytes> toRoleDescriptorsBytesList(final RoleD
throws IOException {
// If we ever lift this restriction, we need to ensure that the serialization of each set of role descriptors to raw bytes is
// deterministic. We can do so by sorting the role descriptors before serializing.

assert roleDescriptorsIntersection.roleDescriptorsList().stream().noneMatch(rds -> rds.size() > 1)
: "sets with more than one role descriptor are not supported for remote access authentication";
: "sets with more than one role descriptor are not supported for cross cluster access authentication";
final List<RoleDescriptorsBytes> roleDescriptorsBytesList = new ArrayList<>();
for (Set<RoleDescriptor> roleDescriptors : roleDescriptorsIntersection.roleDescriptorsList()) {
roleDescriptorsBytesList.add(RoleDescriptorsBytes.fromRoleDescriptors(roleDescriptors));
Expand All @@ -121,30 +124,31 @@ public String encode() throws IOException {
return Base64.getEncoder().encodeToString(BytesReference.toBytes(out.bytes()));
}

public static RemoteAccessAuthentication decode(final String header) throws IOException {
public static CrossClusterAccessSubjectInfo decode(final String header) throws IOException {
Objects.requireNonNull(header);
final byte[] bytes = Base64.getDecoder().decode(header);
final StreamInput in = StreamInput.wrap(bytes);
final TransportVersion version = TransportVersion.readVersion(in);
in.setTransportVersion(version);
final Authentication authentication = new Authentication(in);
final List<RoleDescriptorsBytes> roleDescriptorsBytesList = in.readImmutableList(RoleDescriptorsBytes::new);
return new RemoteAccessAuthentication(authentication, roleDescriptorsBytesList);
return new CrossClusterAccessSubjectInfo(authentication, roleDescriptorsBytesList);
}

/**
* Returns a copy of the passed-in metadata map, with the relevant remote access fields included. Does not modify the original map.
* Returns a copy of the passed-in metadata map, with the relevant cross cluster access fields included.
* Does not modify the original map.
*/
public Map<String, Object> copyWithRemoteAccessEntries(final Map<String, Object> authenticationMetadata) {
assert false == authenticationMetadata.containsKey(AuthenticationField.REMOTE_ACCESS_AUTHENTICATION_KEY)
: "metadata already contains [" + AuthenticationField.REMOTE_ACCESS_AUTHENTICATION_KEY + "] entry";
assert false == authenticationMetadata.containsKey(AuthenticationField.REMOTE_ACCESS_ROLE_DESCRIPTORS_KEY)
: "metadata already contains [" + AuthenticationField.REMOTE_ACCESS_ROLE_DESCRIPTORS_KEY + "] entry";
assert false == getAuthentication().isRemoteAccess()
: "authentication included in remote access header cannot itself be remote access";
public Map<String, Object> copyWithCrossClusterAccessEntries(final Map<String, Object> authenticationMetadata) {
assert false == authenticationMetadata.containsKey(AuthenticationField.CROSS_CLUSTER_ACCESS_AUTHENTICATION_KEY)
: "metadata already contains [" + AuthenticationField.CROSS_CLUSTER_ACCESS_AUTHENTICATION_KEY + "] entry";
assert false == authenticationMetadata.containsKey(AuthenticationField.CROSS_CLUSTER_ACCESS_ROLE_DESCRIPTORS_KEY)
: "metadata already contains [" + AuthenticationField.CROSS_CLUSTER_ACCESS_ROLE_DESCRIPTORS_KEY + "] entry";
assert false == getAuthentication().isCrossClusterAccess()
: "authentication included in cross cluster access header cannot itself be cross cluster access";
final Map<String, Object> copy = new HashMap<>(authenticationMetadata);
copy.put(AuthenticationField.REMOTE_ACCESS_AUTHENTICATION_KEY, getAuthentication());
copy.put(AuthenticationField.REMOTE_ACCESS_ROLE_DESCRIPTORS_KEY, getRoleDescriptorsBytesList());
copy.put(AuthenticationField.CROSS_CLUSTER_ACCESS_AUTHENTICATION_KEY, getAuthentication());
copy.put(AuthenticationField.CROSS_CLUSTER_ACCESS_ROLE_DESCRIPTORS_KEY, getRoleDescriptorsBytesList());
return Collections.unmodifiableMap(copy);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.util.ArrayUtils;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.xpack.core.security.authc.RemoteAccessAuthentication.RoleDescriptorsBytes;
import org.elasticsearch.xpack.core.security.authc.CrossClusterAccessSubjectInfo.RoleDescriptorsBytes;
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings;
import org.elasticsearch.xpack.core.security.authz.store.RoleReference;
import org.elasticsearch.xpack.core.security.authz.store.RoleReferenceIntersection;
Expand All @@ -28,13 +28,13 @@
import static org.elasticsearch.xpack.core.security.authc.Authentication.VERSION_API_KEY_ROLES_AS_BYTES;
import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY;
import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY;
import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.REMOTE_ACCESS_AUTHENTICATION_KEY;
import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.CROSS_CLUSTER_ACCESS_AUTHENTICATION_KEY;
import static org.elasticsearch.xpack.core.security.authc.Subject.Type.API_KEY;
import static org.elasticsearch.xpack.core.security.authc.Subject.Type.REMOTE_ACCESS;
import static org.elasticsearch.xpack.core.security.authc.Subject.Type.CROSS_CLUSTER_ACCESS;

/**
* A subject is a more generic concept similar to user and associated to the current authentication.
* It is more generic than user because it can also represent API keys, service accounts, or remote access users.
* It is more generic than user because it can also represent API keys, service accounts, or cross cluster access users.
* It also contains authentication level information, e.g. realm and metadata so that it can answer
* queries in a better encapsulated way.
*/
Expand All @@ -44,7 +44,7 @@ public enum Type {
USER,
API_KEY,
SERVICE_ACCOUNT,
REMOTE_ACCESS,
CROSS_CLUSTER_ACCESS,
}

private final TransportVersion version;
Expand All @@ -71,9 +71,9 @@ public Subject(User user, Authentication.RealmRef realm, TransportVersion versio
} else if (ServiceAccountSettings.REALM_TYPE.equals(realm.getType())) {
assert ServiceAccountSettings.REALM_NAME.equals(realm.getName()) : "service account realm name mismatch";
this.type = Type.SERVICE_ACCOUNT;
} else if (AuthenticationField.REMOTE_ACCESS_REALM_TYPE.equals(realm.getType())) {
assert AuthenticationField.REMOTE_ACCESS_REALM_NAME.equals(realm.getName()) : "remote access realm name mismatch";
this.type = Type.REMOTE_ACCESS;
} else if (AuthenticationField.CROSS_CLUSTER_ACCESS_REALM_TYPE.equals(realm.getType())) {
assert AuthenticationField.CROSS_CLUSTER_ACCESS_REALM_NAME.equals(realm.getName()) : "cross cluster access realm name mismatch";
this.type = Type.CROSS_CLUSTER_ACCESS;
} else {
this.type = Type.USER;
}
Expand Down Expand Up @@ -108,8 +108,8 @@ public RoleReferenceIntersection getRoleReferenceIntersection(@Nullable Anonymou
return buildRoleReferencesForApiKey();
case SERVICE_ACCOUNT:
return new RoleReferenceIntersection(new RoleReference.ServiceAccountRoleReference(user.principal()));
case REMOTE_ACCESS:
return buildRoleReferencesForRemoteAccess();
case CROSS_CLUSTER_ACCESS:
return buildRoleReferencesForCrossClusterAccess();
default:
assert false : "unknown subject type: [" + type + "]";
throw new IllegalStateException("unknown subject type: [" + type + "]");
Expand All @@ -124,16 +124,16 @@ public boolean canAccessResourcesOf(Subject resourceCreatorSubject) {
// an API Key cannot access resources created by non-API Keys or vice-versa
return false;
}
} else if (eitherIsRemoteAccess(resourceCreatorSubject)) {
if (bothAreRemoteAccess(resourceCreatorSubject)) {
} else if (eitherIsCrossClusterAccess(resourceCreatorSubject)) {
if (bothAreCrossClusterAccess(resourceCreatorSubject)) {
if (false == isTheSameApiKey(resourceCreatorSubject)) {
return false;
}
return ((Authentication) getMetadata().get(REMOTE_ACCESS_AUTHENTICATION_KEY)).canAccessResourcesOf(
(Authentication) resourceCreatorSubject.getMetadata().get(REMOTE_ACCESS_AUTHENTICATION_KEY)
return ((Authentication) getMetadata().get(CROSS_CLUSTER_ACCESS_AUTHENTICATION_KEY)).canAccessResourcesOf(
(Authentication) resourceCreatorSubject.getMetadata().get(CROSS_CLUSTER_ACCESS_AUTHENTICATION_KEY)
);
} else {
// A remote access subject can never share resources with non-remote access
// A cross cluster access subject can never share resources with non-cross cluster access
return false;
}
} else {
Expand Down Expand Up @@ -182,12 +182,12 @@ private boolean bothAreApiKeys(Subject resourceCreatorSubject) {
return API_KEY.equals(getType()) && API_KEY.equals(resourceCreatorSubject.getType());
}

private boolean eitherIsRemoteAccess(Subject resourceCreatorSubject) {
return REMOTE_ACCESS.equals(getType()) || REMOTE_ACCESS.equals(resourceCreatorSubject.getType());
private boolean eitherIsCrossClusterAccess(Subject resourceCreatorSubject) {
return CROSS_CLUSTER_ACCESS.equals(getType()) || CROSS_CLUSTER_ACCESS.equals(resourceCreatorSubject.getType());
}

private boolean bothAreRemoteAccess(Subject resourceCreatorSubject) {
return REMOTE_ACCESS.equals(getType()) && REMOTE_ACCESS.equals(resourceCreatorSubject.getType());
private boolean bothAreCrossClusterAccess(Subject resourceCreatorSubject) {
return CROSS_CLUSTER_ACCESS.equals(getType()) && CROSS_CLUSTER_ACCESS.equals(resourceCreatorSubject.getType());
}

@Override
Expand Down Expand Up @@ -264,22 +264,22 @@ private RoleReferenceIntersection buildRoleReferencesForApiKey() {
);
}

private RoleReferenceIntersection buildRoleReferencesForRemoteAccess() {
private RoleReferenceIntersection buildRoleReferencesForCrossClusterAccess() {
final List<RoleReference> roleReferences = new ArrayList<>(4);
@SuppressWarnings("unchecked")
final var remoteAccessRoleDescriptorsBytes = (List<RoleDescriptorsBytes>) metadata.get(
AuthenticationField.REMOTE_ACCESS_ROLE_DESCRIPTORS_KEY
final var crossClusterAccessRoleDescriptorsBytes = (List<RoleDescriptorsBytes>) metadata.get(
AuthenticationField.CROSS_CLUSTER_ACCESS_ROLE_DESCRIPTORS_KEY
);
if (remoteAccessRoleDescriptorsBytes.isEmpty()) {
// If the remote access role descriptors are empty, the remote user has no privileges. We need to add an empty role to restrict
// access of the overall intersection accordingly
roleReferences.add(new RoleReference.RemoteAccessRoleReference(RoleDescriptorsBytes.EMPTY));
if (crossClusterAccessRoleDescriptorsBytes.isEmpty()) {
// If the cross cluster access role descriptors are empty, the remote user has no privileges. We need to add an empty role to
// restrict access of the overall intersection accordingly
roleReferences.add(new RoleReference.CrossClusterAccessRoleReference(RoleDescriptorsBytes.EMPTY));
} else {
// TODO handle this once we support API keys as querying subjects
assert remoteAccessRoleDescriptorsBytes.size() == 1
: "only a singleton list of remote access role descriptors bytes is supported";
for (RoleDescriptorsBytes roleDescriptorsBytes : remoteAccessRoleDescriptorsBytes) {
roleReferences.add(new RoleReference.RemoteAccessRoleReference(roleDescriptorsBytes));
assert crossClusterAccessRoleDescriptorsBytes.size() == 1
: "only a singleton list of cross cluster access role descriptors bytes is supported";
for (RoleDescriptorsBytes roleDescriptorsBytes : crossClusterAccessRoleDescriptorsBytes) {
roleReferences.add(new RoleReference.CrossClusterAccessRoleReference(roleDescriptorsBytes));
}
}
roleReferences.addAll(buildRoleReferencesForApiKey().getRoleReferences());
Expand Down

0 comments on commit 90f6af3

Please sign in to comment.