Skip to content

Commit

Permalink
User Profile - Add new action origin and internal user (elastic#86026)
Browse files Browse the repository at this point in the history
Profile documents are stored in a separate system index from the main
security index. Hence a more scoped origin and internal user is better
than the all powerful _xpack_security user. This PR adds
_security_profile user that has privileges only over the profile index
and updates all profile related actions to use it.
  • Loading branch information
ywangd authored Apr 21, 2022
1 parent f06dafa commit a97bd33
Show file tree
Hide file tree
Showing 14 changed files with 240 additions and 24 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/86026.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 86026
summary: User Profile - Add new action origin and internal user
area: Security
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ private static String maybeRewriteSingleAuthenticationHeaderForVersion(
@Deprecated
public static final String ACTION_ORIGIN_TRANSIENT_NAME = ThreadContext.ACTION_ORIGIN_TRANSIENT_NAME;
public static final String SECURITY_ORIGIN = "security";
public static final String SECURITY_PROFILE_ORIGIN = "security_profile";
public static final String WATCHER_ORIGIN = "watcher";
public static final String ML_ORIGIN = "ml";
public static final String INDEX_LIFECYCLE_ORIGIN = "index_lifecycle";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public static User readFrom(StreamInput input) throws IOException {
return XPackUser.INSTANCE;
} else if (XPackSecurityUser.is(username)) {
return XPackSecurityUser.INSTANCE;
} else if (SecurityProfileUser.is(username)) {
return SecurityProfileUser.INSTANCE;
} else if (AsyncSearchUser.is(username)) {
return AsyncSearchUser.INSTANCE;
}
Expand All @@ -40,6 +42,9 @@ public static void writeTo(User user, StreamOutput output) throws IOException {
} else if (XPackSecurityUser.is(user)) {
output.writeBoolean(true);
output.writeString(XPackSecurityUser.NAME);
} else if (SecurityProfileUser.is(user)) {
output.writeBoolean(true);
output.writeString(SecurityProfileUser.NAME);
} else if (AsyncSearchUser.is(user)) {
output.writeBoolean(true);
output.writeString(AsyncSearchUser.NAME);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.core.security.user;

import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.support.MetadataUtils;

import java.util.Map;

/**
* internal user that manages the security profile index. Has no cluster permission.
*/
public class SecurityProfileUser extends User {

public static final String NAME = UsernamesField.SECURITY_PROFILE_NAME;
public static final SecurityProfileUser INSTANCE = new SecurityProfileUser();
private static final String ROLE_NAME = UsernamesField.SECURITY_PROFILE_ROLE;
public static final RoleDescriptor ROLE_DESCRIPTOR = new RoleDescriptor(
ROLE_NAME,
null,
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder()
.indices(".security-profile", "/\\.security-profile-[0-9].*/")
.privileges("all")
.allowRestrictedIndices(true)
.build() },
null,
null,
null,
MetadataUtils.DEFAULT_RESERVED_METADATA,
Map.of()
);

private SecurityProfileUser() {
super(NAME, ROLE_NAME);
// the following traits, and especially the run-as one, go with all the internal users
// TODO abstract in a base `InternalUser` class
assert false == isRunAs() : "cannot run-as the system user";
assert enabled();
assert roles() != null && roles().length == 1;
}

@Override
public boolean equals(Object o) {
return INSTANCE == o;
}

@Override
public int hashCode() {
return System.identityHashCode(this);
}

public static boolean is(User user) {
return INSTANCE.equals(user);
}

public static boolean is(String principal) {
return NAME.equals(principal);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,18 @@ public static void writeTo(User user, StreamOutput output) throws IOException {
}

public static boolean isInternal(User user) {
return SystemUser.is(user) || XPackUser.is(user) || XPackSecurityUser.is(user) || AsyncSearchUser.is(user);
return SystemUser.is(user)
|| XPackUser.is(user)
|| XPackSecurityUser.is(user)
|| SecurityProfileUser.is(user)
|| AsyncSearchUser.is(user);
}

public static boolean isInternalUsername(String username) {
return SystemUser.NAME.equals(username)
|| XPackUser.NAME.equals(username)
|| XPackSecurityUser.NAME.equals(username)
|| SecurityProfileUser.NAME.equals(username)
|| AsyncSearchUser.NAME.equals(username);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public final class UsernamesField {
public static final String SYSTEM_ROLE = "_system";
public static final String XPACK_SECURITY_NAME = "_xpack_security";
public static final String XPACK_SECURITY_ROLE = "_xpack_security";
public static final String SECURITY_PROFILE_NAME = "_security_profile";
public static final String SECURITY_PROFILE_ROLE = "_security_profile";
public static final String XPACK_NAME = "_xpack";
public static final String XPACK_ROLE = "_xpack";
public static final String LOGSTASH_NAME = "logstash_system";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
import org.elasticsearch.xpack.core.security.user.SecurityProfileUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
Expand Down Expand Up @@ -227,7 +228,13 @@ public AuthenticationTestBuilder anonymous(User user) {

public AuthenticationTestBuilder internal() {
return internal(
ESTestCase.randomFrom(SystemUser.INSTANCE, XPackUser.INSTANCE, XPackSecurityUser.INSTANCE, AsyncSearchUser.INSTANCE)
ESTestCase.randomFrom(
SystemUser.INSTANCE,
XPackUser.INSTANCE,
XPackSecurityUser.INSTANCE,
SecurityProfileUser.INSTANCE,
AsyncSearchUser.INSTANCE
)
);
}

Expand Down Expand Up @@ -404,6 +411,7 @@ public Authentication build(boolean runAsIfNotAlready) {
SystemUser.INSTANCE,
XPackUser.INSTANCE,
XPackSecurityUser.INSTANCE,
SecurityProfileUser.INSTANCE,
AsyncSearchUser.INSTANCE
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField;
import org.elasticsearch.xpack.core.security.support.Automatons;
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
import org.elasticsearch.xpack.core.security.user.SecurityProfileUser;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
import org.elasticsearch.xpack.core.security.user.XPackUser;

Expand All @@ -36,6 +37,7 @@
import static org.elasticsearch.xpack.core.ClientHelper.ROLLUP_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.SEARCHABLE_SNAPSHOTS_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_PROFILE_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.STACK_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.TRANSFORM_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.WATCHER_ORIGIN;
Expand Down Expand Up @@ -117,6 +119,9 @@ public static void switchUserBasedOnActionOriginAndExecute(
case SECURITY_ORIGIN:
securityContext.executeAsInternalUser(XPackSecurityUser.INSTANCE, Version.CURRENT, consumer);
break;
case SECURITY_PROFILE_ORIGIN:
securityContext.executeAsInternalUser(SecurityProfileUser.INSTANCE, Version.CURRENT, consumer);
break;
case WATCHER_ORIGIN:
case ML_ORIGIN:
case MONITORING_ORIGIN:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.elasticsearch.xpack.core.security.support.CacheIteratorHelper;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
import org.elasticsearch.xpack.core.security.user.SecurityProfileUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
Expand Down Expand Up @@ -104,6 +105,7 @@ public class CompositeRolesStore {
private final RoleDescriptorStore roleReferenceResolver;
private final Role superuserRole;
private final Role xpackSecurityRole;
private final Role securityProfileRole;
private final Role xpackUserRole;
private final Role asyncSearchUserRole;
private final RestrictedIndices restrictedIndices;
Expand Down Expand Up @@ -154,6 +156,7 @@ public void providersChanged() {
this.superuserRole = Role.builder(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR, fieldPermissionsCache, this.restrictedIndices)
.build();
xpackSecurityRole = Role.builder(XPackSecurityUser.ROLE_DESCRIPTOR, fieldPermissionsCache, this.restrictedIndices).build();
securityProfileRole = Role.builder(SecurityProfileUser.ROLE_DESCRIPTOR, fieldPermissionsCache, this.restrictedIndices).build();
xpackUserRole = Role.builder(XPackUser.ROLE_DESCRIPTOR, fieldPermissionsCache, this.restrictedIndices).build();
asyncSearchUserRole = Role.builder(AsyncSearchUser.ROLE_DESCRIPTOR, fieldPermissionsCache, this.restrictedIndices).build();

Expand Down Expand Up @@ -220,6 +223,9 @@ Role tryGetRoleForInternalUser(Subject subject) {
if (XPackSecurityUser.is(user)) {
return xpackSecurityRole;
}
if (SecurityProfileUser.is(user)) {
return securityProfileRole;
}
if (AsyncSearchUser.is(user)) {
return asyncSearchUserRole;
}
Expand Down Expand Up @@ -364,7 +370,8 @@ public void getRoleDescriptorsList(Subject subject, ActionListener<Collection<Se
);
}

private static Optional<RoleDescriptor> tryGetRoleDescriptorForInternalUser(Subject subject) {
// Package private for testing
static Optional<RoleDescriptor> tryGetRoleDescriptorForInternalUser(Subject subject) {
final User user = subject.getUser();
if (SystemUser.is(user)) {
throw new IllegalArgumentException(
Expand All @@ -377,6 +384,9 @@ private static Optional<RoleDescriptor> tryGetRoleDescriptorForInternalUser(Subj
if (XPackSecurityUser.is(user)) {
return Optional.of(XPackSecurityUser.ROLE_DESCRIPTOR);
}
if (SecurityProfileUser.is(user)) {
return Optional.of(SecurityProfileUser.ROLE_DESCRIPTOR);
}
if (AsyncSearchUser.is(user)) {
return Optional.of(AsyncSearchUser.ROLE_DESCRIPTOR);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
import java.util.stream.Collectors;

import static org.elasticsearch.action.bulk.TransportSingleItemBulkWriteAction.toSingleItemBulkRequest;
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_PROFILE_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
import static org.elasticsearch.xpack.core.security.authc.Authentication.isFileOrNativeRealm;
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_PROFILE_ALIAS;
Expand Down Expand Up @@ -192,7 +192,7 @@ public void suggestProfile(SuggestProfilesRequest request, ActionListener<Sugges
listener::onFailure,
() -> executeAsyncWithOrigin(
client,
SECURITY_ORIGIN,
SECURITY_PROFILE_ORIGIN,
SearchAction.INSTANCE,
searchRequest,
ActionListener.wrap(searchResponse -> {
Expand Down Expand Up @@ -282,20 +282,26 @@ private void getVersionedDocument(String uid, ActionListener<VersionedDocument>
final GetRequest getRequest = new GetRequest(SECURITY_PROFILE_ALIAS, uidToDocId(uid));
frozenProfileIndex.checkIndexVersionThenExecute(
listener::onFailure,
() -> executeAsyncWithOrigin(client, SECURITY_ORIGIN, GetAction.INSTANCE, getRequest, ActionListener.wrap(response -> {
if (false == response.isExists()) {
logger.debug("profile with uid [{}] does not exist", uid);
listener.onResponse(null);
return;
}
listener.onResponse(
new VersionedDocument(
buildProfileDocument(response.getSourceAsBytesRef()),
response.getPrimaryTerm(),
response.getSeqNo()
)
);
}, listener::onFailure))
() -> executeAsyncWithOrigin(
client,
SECURITY_PROFILE_ORIGIN,
GetAction.INSTANCE,
getRequest,
ActionListener.wrap(response -> {
if (false == response.isExists()) {
logger.debug("profile with uid [{}] does not exist", uid);
listener.onResponse(null);
return;
}
listener.onResponse(
new VersionedDocument(
buildProfileDocument(response.getSourceAsBytesRef()),
response.getPrimaryTerm(),
response.getSeqNo()
)
);
}, listener::onFailure)
)
);
});
}
Expand Down Expand Up @@ -335,7 +341,7 @@ void searchVersionedDocumentForSubject(Subject subject, ActionListener<Versioned
listener::onFailure,
() -> executeAsyncWithOrigin(
client,
SECURITY_ORIGIN,
SECURITY_PROFILE_ORIGIN,
SearchAction.INSTANCE,
searchRequest,
ActionListener.wrap(searchResponse -> {
Expand Down Expand Up @@ -407,7 +413,7 @@ private void createNewProfile(Subject subject, String uid, ActionListener<Profil
listener::onFailure,
() -> executeAsyncWithOrigin(
client,
SECURITY_ORIGIN,
SECURITY_PROFILE_ORIGIN,
BulkAction.INSTANCE,
bulkRequest,
TransportSingleItemBulkWriteAction.<IndexResponse>wrapBulkResponse(ActionListener.wrap(indexResponse -> {
Expand Down Expand Up @@ -553,12 +559,13 @@ private UpdateRequest buildUpdateRequest(
return updateRequestBuilder.request();
}

private void doUpdate(UpdateRequest updateRequest, ActionListener<UpdateResponse> listener) {
// Package private for testing
void doUpdate(UpdateRequest updateRequest, ActionListener<UpdateResponse> listener) {
profileIndex.prepareIndexIfNeededThenExecute(
listener::onFailure,
() -> executeAsyncWithOrigin(
client,
SECURITY_ORIGIN,
SECURITY_PROFILE_ORIGIN,
UpdateAction.INSTANCE,
updateRequest,
ActionListener.wrap(updateResponse -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_PROFILE_ORIGIN;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_VERSION_STRING;

/**
Expand Down Expand Up @@ -736,7 +737,7 @@ private SystemIndexDescriptor getSecurityProfileIndexDescriptor() {
.setAliasName(SECURITY_PROFILE_ALIAS)
.setIndexFormat(INTERNAL_PROFILE_INDEX_FORMAT)
.setVersionMetaKey(SECURITY_VERSION_STRING)
.setOrigin(SECURITY_ORIGIN)
.setOrigin(SECURITY_PROFILE_ORIGIN)
.setThreadPools(ExecutorNames.CRITICAL_SYSTEM_INDEX_THREAD_POOLS)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField;
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
import org.elasticsearch.xpack.core.security.user.SecurityProfileUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
Expand Down Expand Up @@ -111,6 +112,10 @@ public void testSwitchAndExecuteXpackSecurityUser() throws Exception {
assertSwitchBasedOnOriginAndExecute(ClientHelper.SECURITY_ORIGIN, XPackSecurityUser.INSTANCE);
}

public void testSwitchAndExecuteSecurityProfileUser() throws Exception {
assertSwitchBasedOnOriginAndExecute(ClientHelper.SECURITY_PROFILE_ORIGIN, SecurityProfileUser.INSTANCE);
}

public void testSwitchAndExecuteXpackUser() throws Exception {
for (String origin : Arrays.asList(
ClientHelper.ML_ORIGIN,
Expand Down
Loading

0 comments on commit a97bd33

Please sign in to comment.