Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,33 @@ Pair<String, String> predefinedHiddenAndReservedConfigEntities(LocalCluster loca

public void availableForTLSAdminUser(LocalCluster localCluster) throws Exception {
try (TestRestClient client = localCluster.getAdminCertRestClient()) {
availableForSuperAdminUser(client);
verifySuperAdminCanManageRestAdminPermissions(client);
}
}

public void availableForRESTAdminUser(LocalCluster localCluster) throws Exception {
try (TestRestClient client = localCluster.getRestClient(REST_ADMIN_USER)) {
availableForSuperAdminUser(client);
verifyRestAdminCannotManageRestAdminPermissions(client);
}
}

void availableForSuperAdminUser(final TestRestClient client) throws Exception {
// mTLS super-admin bypasses the restapi:admin/* permission guard — can create/modify/delete freely
private void verifySuperAdminCanManageRestAdminPermissions(final TestRestClient client) throws Exception {
creationOfReadOnlyEntityForbidden(
randomAlphanumericString(),
client,
(builder, params) -> testDescriptor.staticEntityPayload().toXContent(builder, params)
);
verifyCrudOperations(true, null, client);
verifyCrudOperations(null, true, client);
verifyCrudOperations(null, null, client);
verifyBadRequestOperations(client);
verifySuperAdminCanCreateEntityWithRestAdminPermissions(client);
verifySuperAdminCanUpdateAndDeleteEntityWithRestAdminPermissions(client);
}

// REST admin user does NOT bypass the guard — still blocked from touching restapi:admin/* entities
private void verifyRestAdminCannotManageRestAdminPermissions(final TestRestClient client) throws Exception {
creationOfReadOnlyEntityForbidden(
randomAlphanumericString(),
client,
Expand Down Expand Up @@ -276,6 +292,10 @@ void forbiddenToCreateEntityWithRestAdminPermissions(final TestRestClient client

void forbiddenToUpdateAndDeleteExistingEntityWithRestAdminPermissions(final TestRestClient client) throws Exception {}

void verifySuperAdminCanCreateEntityWithRestAdminPermissions(final TestRestClient client) throws Exception {}

void verifySuperAdminCanUpdateAndDeleteEntityWithRestAdminPermissions(final TestRestClient client) throws Exception {}

abstract void verifyBadRequestOperations(final TestRestClient client) throws Exception;

abstract void verifyCrudOperations(final Boolean hidden, final Boolean reserved, final TestRestClient client) throws Exception;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,25 @@ void forbiddenToUpdateAndDeleteExistingEntityWithRestAdminPermissions(final Test
assertThat(client.delete(apiPath(REST_ADMIN_PERMISSION_ACTION_GROUP)), isForbidden());
}

@Override
void verifySuperAdminCanCreateEntityWithRestAdminPermissions(final TestRestClient client) throws Exception {
assertThat(client.putJson(apiPath("new_rest_admin_action_group"), actionGroup(randomRestAdminPermission())), isCreated());
assertThat(
client.patch(apiPath(), patch(addOp("new_rest_admin_action_group_2", actionGroup(randomRestAdminPermission())))),
isOk()
);
}

@Override
void verifySuperAdminCanUpdateAndDeleteEntityWithRestAdminPermissions(final TestRestClient client) throws Exception {
final var tempGroup = "temp_rest_admin_action_group";
assertThat(client.putJson(apiPath(tempGroup), actionGroup(randomRestAdminPermission())), isCreated());
assertThat(client.putJson(apiPath(tempGroup), actionGroup("a", "b")), isOk());
assertThat(client.patch(apiPath(), patch(replaceOp(tempGroup, actionGroup("a", "b")))), isOk());
assertThat(client.patch(apiPath(tempGroup), patch(replaceOp("allowed_actions", configJsonArray("c", "d")))), isOk());
assertThat(client.delete(apiPath(tempGroup)), isOk());
}

@Override
void verifyCrudOperations(final Boolean hidden, final Boolean reserved, final TestRestClient client) throws Exception {
// create
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.opensearch.security.DefaultObjectMapper;
import org.opensearch.security.dlic.rest.api.Endpoint;
import org.opensearch.test.framework.TestSecurityConfig;
import org.opensearch.test.framework.TestSecurityConfig.Role;
import org.opensearch.test.framework.cluster.LocalCluster;
import org.opensearch.test.framework.cluster.TestRestClient;
import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse;
Expand All @@ -38,7 +39,6 @@
import static org.opensearch.security.api.PatchPayloadHelper.patch;
import static org.opensearch.security.api.PatchPayloadHelper.removeOp;
import static org.opensearch.security.api.PatchPayloadHelper.replaceOp;
import static org.opensearch.test.framework.TestSecurityConfig.Role;
import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest;
import static org.opensearch.test.framework.matcher.RestMatchers.isCreated;
import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden;
Expand Down Expand Up @@ -498,6 +498,31 @@ void forbiddenToUpdateAndDeleteExistingEntityWithRestAdminPermissions(TestRestCl
assertThat(client.delete(apiPath(REST_ADMIN_ROLE_WITH_MAPPING)), isForbidden());
}

@Override
void verifySuperAdminCanCreateEntityWithRestAdminPermissions(TestRestClient client) throws Exception {
final var users = arrayOptions(false).get(0);
assertThat(client.putJson(apiPath(REST_ADMIN_ROLE), roleMappingWithUsers(users)), isCreated());
assertThat(client.patch(apiPath(), patch(replaceOp(REST_ADMIN_ROLE, roleMappingWithUsers(users)))), isOk());
}

@Override
void verifySuperAdminCanUpdateAndDeleteEntityWithRestAdminPermissions(TestRestClient client) throws Exception {
final var users = arrayOptions(false).get(0);
assertThat(client.putJson(apiPath(REST_ADMIN_ROLE_WITH_MAPPING), roleMapping(users, users, users, users)), isOk());
assertThat(
client.patch(apiPath(), patch(replaceOp(REST_ADMIN_ROLE_WITH_MAPPING, roleMapping(users, users, users, users)))),
isOk()
);
assertThat(client.patch(apiPath(REST_ADMIN_ROLE_WITH_MAPPING), patch(replaceOp("users", users))), isOk());
assertThat(
client.putJson(
apiPath(REST_ADMIN_ROLE_WITH_MAPPING),
roleMapping(configJsonArray(), configJsonArray(), configJsonArray(), configJsonArray())
),
isOk()
);
}

List<String> jsonProperties() {
return List.of("backend_roles", "hosts", "users", "and_backend_roles");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ void verifyBadRequestOperations(TestRestClient client) throws Exception {
void forbiddenToCreateEntityWithRestAdminPermissions(final TestRestClient client) throws Exception {
assertThat(client.putJson(apiPath("new_rest_admin_role"), roleWithClusterPermissions(randomRestAdminPermission())), isForbidden());
assertThat(
client.patch(apiPath(), patch(addOp("new_rest_admin_action_group", roleWithClusterPermissions(randomRestAdminPermission())))),
client.patch(apiPath(), patch(addOp("new_rest_admin_role", roleWithClusterPermissions(randomRestAdminPermission())))),
isForbidden()
);
}
Expand Down Expand Up @@ -312,6 +312,49 @@ void forbiddenToUpdateAndDeleteExistingEntityWithRestAdminPermissions(final Test
assertThat(client.delete(apiPath(REST_ADMIN_PERMISSION_ROLE)), isForbidden());
}

@Override
void verifySuperAdminCanCreateEntityWithRestAdminPermissions(final TestRestClient client) throws Exception {
assertThat(client.putJson(apiPath("new_rest_admin_role"), roleWithClusterPermissions(randomRestAdminPermission())), isCreated());
assertThat(
client.patch(apiPath(), patch(addOp("new_rest_admin_role_2", roleWithClusterPermissions(randomRestAdminPermission())))),
isOk()
);
}

@Override
void verifySuperAdminCanUpdateAndDeleteEntityWithRestAdminPermissions(final TestRestClient client) throws Exception {
final var tempRole = "temp_rest_admin_role";
assertThat(client.putJson(apiPath(tempRole), roleWithClusterPermissions(randomRestAdminPermission())), isCreated());
assertThat(
client.putJson(
apiPath(tempRole),
role(clusterPermissionsOptions(false).get(0), indexPermissionsOptions(false).get(0), tenantPermissionsOptions(false).get(0))
),
isOk()
);
assertThat(
client.patch(
apiPath(),
patch(
replaceOp(
tempRole,
role(
clusterPermissionsOptions(false).get(0),
indexPermissionsOptions(false).get(0),
tenantPermissionsOptions(false).get(0)
)
)
)
),
isOk()
);
assertThat(
client.patch(apiPath(tempRole), patch(replaceOp("cluster_permissions", clusterPermissionsOptions(false).get(0)))),
isOk()
);
assertThat(client.delete(apiPath(tempRole)), isOk());
}

void assertRole(
final TestRestClient.HttpResponse response,
final String roleName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ public boolean isCurrentUserAdminFor(final Endpoint endpoint, final String actio
return hasAccess && restapiAdminEnabled;
}

public boolean isCurrentUserSuperAdmin() {
final Pair<User, TransportAddress> userAndRemoteAddress = Utils.userAndRemoteAddressFrom(threadContext);
return userAndRemoteAddress.getLeft() != null && adminDNs.isAdmin(userAndRemoteAddress.getLeft());
}

public boolean isCurrentUserAdminFor(final Endpoint endpoint) {
return isCurrentUserAdminFor(endpoint, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ public interface EndpointValidator {

RestApiAuthorizationEvaluator restApiAuthorizationEvaluator();

default boolean isCurrentUserSuperAdmin() {
return restApiAuthorizationEvaluator().isCurrentUserSuperAdmin();
}

private String resourceName() {
if (Objects.isNull(endpoint())) {
return "";
Expand Down Expand Up @@ -162,6 +166,9 @@ default ValidationResult<SecurityDynamicConfiguration<?>> validateRoles(
default ValidationResult<SecurityConfiguration> isAllowedToChangeEntityWithRestAdminPermissions(
final SecurityConfiguration securityConfiguration
) throws IOException {
if (isCurrentUserSuperAdmin()) {
return ValidationResult.success(securityConfiguration);
}
final var configuration = securityConfiguration.configuration();
if (securityConfiguration.entityExists()) {
final var existingEntity = configuration.getCEntry(securityConfiguration.entityName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.junit.Test;

import org.opensearch.core.rest.RestStatus;
import org.opensearch.security.securityconf.impl.CType;
import org.opensearch.security.securityconf.impl.v7.RoleV7;

import org.mockito.Mockito;
Expand All @@ -39,6 +40,52 @@ public void isAllowedToChangeImmutableEntity() throws Exception {
assertTrue(result.isValid());
}

@Test
public void superAdminIsAllowedToCreateRoleWithRestAdminPermissions() throws Exception {
when(restApiAuthorizationEvaluator.isCurrentUserSuperAdmin()).thenReturn(true);

final var role = objectMapper.createObjectNode();
final var clusterPermissions = objectMapper.createArrayNode();
clusterPermissions.add("restapi:admin/actiongroups");
clusterPermissions.add("restapi:admin/roles");
clusterPermissions.add("restapi:admin/rolesmapping");
role.set("cluster_permissions", clusterPermissions);
final var rolesApiActionEndpointValidator = new RolesApiAction(clusterService, threadPool, securityApiDependencies)
.createEndpointValidator();
assertTrue(rolesApiActionEndpointValidator.isCurrentUserSuperAdmin());

final var result = rolesApiActionEndpointValidator.isAllowedToChangeImmutableEntity(
SecurityConfiguration.of(role, "test_role", configuration)
);

assertTrue(result.isValid());
}

@Test
public void nonSuperAdminIsNotAllowedToCreateRoleWithRestAdminPermissions() throws Exception {
when(restApiAuthorizationEvaluator.isCurrentUserSuperAdmin()).thenReturn(false);
Mockito.doReturn(CType.ROLES).when(configuration).getCType();
when(configuration.getImplementingClass()).thenCallRealMethod();
when(restApiAuthorizationEvaluator.containsRestApiAdminPermissions(any(Object.class))).thenCallRealMethod();

final var role = objectMapper.createObjectNode();
final var clusterPermissions = objectMapper.createArrayNode();
clusterPermissions.add("restapi:admin/actiongroups");
clusterPermissions.add("restapi:admin/roles");
clusterPermissions.add("restapi:admin/rolesmapping");
role.set("cluster_permissions", clusterPermissions);
final var rolesApiActionEndpointValidator = new RolesApiAction(clusterService, threadPool, securityApiDependencies)
.createEndpointValidator();
assertFalse(rolesApiActionEndpointValidator.isCurrentUserSuperAdmin());

final var result = rolesApiActionEndpointValidator.isAllowedToChangeImmutableEntity(
SecurityConfiguration.of(role, "test_role", configuration)
);

assertFalse(result.isValid());
assertThat(result.status(), is(RestStatus.FORBIDDEN));
}

@Test
public void isNotAllowedRightsToChangeImmutableEntity() throws Exception {
final var role = new RoleV7();
Expand Down
Loading