Skip to content

Commit

Permalink
[Backport 2.x] Implement new extension points in IdentityPlugin and a…
Browse files Browse the repository at this point in the history
…dd ContextProvidingPluginSubject (opensearch-project#5028)

Signed-off-by: Craig Perkins <[email protected]>
  • Loading branch information
cwperks authored Jan 16, 2025
1 parent 4af1d07 commit a7d2c57
Show file tree
Hide file tree
Showing 32 changed files with 1,321 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .github/actions/run-bwc-suite/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ runs:
-Dbwc.version.previous=${{ steps.build-previous.outputs.built-version }}
-Dbwc.version.next=${{ steps.build-next.outputs.built-version }} -i
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: always()
with:
name: ${{ inputs.report-artifact-name }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ jobs:
arguments: |
integrationTest -Dbuild.snapshot=false
- uses: alehechka/upload-tartifact@v2
- uses: actions/upload-artifact@v4
if: always()
with:
name: integration-${{ matrix.platform }}-JDK${{ matrix.jdk }}-reports
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:

- run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test

- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: always()
with:
name: ${{ matrix.jdk }}-${{ matrix.test-run }}-reports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,28 @@ public void wildcard() throws Exception {
);
}

@Test
public void wildcardByUsername() throws Exception {
SecurityDynamicConfiguration<RoleV7> roles = SecurityDynamicConfiguration.empty(CType.ROLES);

ActionPrivileges subject = new ActionPrivileges(
roles,
FlattenedActionGroups.EMPTY,
null,
Settings.EMPTY,
Map.of("plugin:org.opensearch.sample.SamplePlugin", Set.of("*"))
);

assertThat(
subject.hasClusterPrivilege(ctxByUsername("plugin:org.opensearch.sample.SamplePlugin"), "cluster:whatever"),
isAllowed()
);
assertThat(
subject.hasClusterPrivilege(ctx("plugin:org.opensearch.other.OtherPlugin"), "cluster:whatever"),
isForbidden(missingPrivileges("cluster:whatever"))
);
}

@Test
public void explicit_wellKnown() throws Exception {
SecurityDynamicConfiguration<RoleV7> roles = SecurityDynamicConfiguration.fromYaml("non_explicit_role:\n" + //
Expand Down Expand Up @@ -455,7 +477,8 @@ public IndicesAndAliases(IndexSpec indexSpec, ActionSpec actionSpec, Statefulnes
settings,
WellKnownActions.CLUSTER_ACTIONS,
WellKnownActions.INDEX_ACTIONS,
WellKnownActions.INDEX_ACTIONS
WellKnownActions.INDEX_ACTIONS,
Map.of()
);

if (statefulness == Statefulness.STATEFUL || statefulness == Statefulness.STATEFUL_LIMITED) {
Expand Down Expand Up @@ -1030,4 +1053,19 @@ static PrivilegesEvaluationContext ctx(String... roles) {
null
);
}

static PrivilegesEvaluationContext ctxByUsername(String username) {
User user = new User(username);
user.addAttributes(ImmutableMap.of("attrs.dept_no", "a11"));
return new PrivilegesEvaluationContext(
user,
ImmutableSet.of(),
null,
null,
null,
null,
new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)),
null
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* compatible open source license.
*
*/
package org.opensearch.security;
package org.opensearch.security.systemindex;

import java.util.List;
import java.util.Map;
Expand All @@ -19,17 +19,22 @@
import org.junit.runner.RunWith;

import org.opensearch.core.rest.RestStatus;
import org.opensearch.security.http.ExampleSystemIndexPlugin;
import org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1;
import org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin2;
import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain;
import org.opensearch.test.framework.cluster.ClusterManager;
import org.opensearch.test.framework.cluster.LocalCluster;
import org.opensearch.test.framework.cluster.TestRestClient;
import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse;
import org.opensearch.test.framework.matcher.RestMatchers;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED;
import static org.opensearch.security.support.ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY;
import static org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1.SYSTEM_INDEX_1;
import static org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin2.SYSTEM_INDEX_2;
import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS;
import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN;

Expand All @@ -44,7 +49,7 @@ public class SystemIndexTests {
.anonymousAuth(false)
.authc(AUTHC_DOMAIN)
.users(USER_ADMIN)
.plugin(ExampleSystemIndexPlugin.class)
.plugin(SystemIndexPlugin1.class, SystemIndexPlugin2.class)
.nodeSettings(
Map.of(
SECURITY_RESTAPI_ROLES_ENABLED,
Expand Down Expand Up @@ -89,6 +94,100 @@ public void adminShouldNotBeAbleToDeleteSecurityIndex() {
}
}

@Test
public void testPluginShouldBeAbleToIndexDocumentIntoItsSystemIndex() {
try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
HttpResponse response = client.put("try-create-and-index/" + SYSTEM_INDEX_1);

assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
assertThat(response.getBody(), containsString("{\"acknowledged\":true}"));
}
}

@Test
public void testPluginShouldNotBeAbleToIndexDocumentIntoSystemIndexRegisteredByOtherPlugin() {
try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
HttpResponse response = client.put("try-create-and-index/" + SYSTEM_INDEX_2);

assertThat(
response,
RestMatchers.isForbidden(
"/error/root_cause/0/reason",
"no permissions for [] and User [name=plugin:org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1"
)
);
}
}

@Test
public void testPluginShouldBeAbleToCreateSystemIndexButUserShouldNotBeAbleToIndex() {
try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
HttpResponse response = client.put("try-create-and-index/" + SYSTEM_INDEX_1 + "?runAs=user");

assertThat(response, RestMatchers.isForbidden("/error/root_cause/0/reason", "no permissions for [] and User [name=admin"));
}
}

@Test
public void testPluginShouldNotBeAbleToRunClusterActions() {
try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
HttpResponse response = client.get("try-cluster-health/plugin");

assertThat(
response,
RestMatchers.isForbidden(
"/error/root_cause/0/reason",
"no permissions for [cluster:monitor/health] and User [name=plugin:org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1"
)
);
}
}

@Test
public void testAdminUserShouldBeAbleToRunClusterActions() {
try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
HttpResponse response = client.get("try-cluster-health/user");

assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
}
}

@Test
public void testAuthenticatedUserShouldBeAbleToRunClusterActions() {
try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
HttpResponse response = client.get("try-cluster-health/default");

assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
}
}

@Test
public void testPluginShouldBeAbleToBulkIndexDocumentIntoItsSystemIndex() {
try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
HttpResponse response = client.put("try-create-and-bulk-index/" + SYSTEM_INDEX_1);

assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
}
}

@Test
public void testPluginShouldNotBeAbleToBulkIndexDocumentIntoMixOfSystemIndexWhereAtLeastOneDoesNotBelongToPlugin() {
try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) {
client.put(".system-index1");
client.put(".system-index2");
}
try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
HttpResponse response = client.put("try-create-and-bulk-mixed-index");

assertThat(
response.getBody(),
containsString(
"no permissions for [] and User [name=plugin:org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1"
)
);
}
}

@Test
public void regularUserShouldGetNoResultsWhenSearchingSystemIndex() {
// Create system index and index a dummy document as the super admin user, data returned to super admin
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/

package org.opensearch.security.systemindex.sampleplugin;

// CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here
import org.opensearch.action.ActionType;
import org.opensearch.action.support.master.AcknowledgedResponse;
// CS-ENFORCE-SINGLE

public class IndexDocumentIntoSystemIndexAction extends ActionType<AcknowledgedResponse> {
public static final IndexDocumentIntoSystemIndexAction INSTANCE = new IndexDocumentIntoSystemIndexAction();
public static final String NAME = "cluster:mock/systemindex/index";

private IndexDocumentIntoSystemIndexAction() {
super(NAME, AcknowledgedResponse::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/

package org.opensearch.security.systemindex.sampleplugin;

import java.io.IOException;

import org.opensearch.action.ActionRequest;
import org.opensearch.action.ActionRequestValidationException;
import org.opensearch.core.common.io.stream.StreamInput;

public class IndexDocumentIntoSystemIndexRequest extends ActionRequest {

private final String indexName;

private final String runAs;

public IndexDocumentIntoSystemIndexRequest(String indexName, String runAs) {
this.indexName = indexName;
this.runAs = runAs;
}

public IndexDocumentIntoSystemIndexRequest(StreamInput in) throws IOException {
super(in);
this.indexName = in.readString();
this.runAs = in.readOptionalString();
}

@Override
public ActionRequestValidationException validate() {
return null;
}

public String getIndexName() {
return this.indexName;
}

public String getRunAs() {
return this.runAs;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/

package org.opensearch.security.systemindex.sampleplugin;

import java.util.List;

import org.opensearch.action.bulk.BulkRequest;
import org.opensearch.action.bulk.BulkRequestBuilder;
import org.opensearch.action.index.IndexRequest;
import org.opensearch.action.support.WriteRequest;
import org.opensearch.client.Client;
import org.opensearch.client.node.NodeClient;
import org.opensearch.core.action.ActionListener;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.core.xcontent.ToXContent;
import org.opensearch.rest.BaseRestHandler;
import org.opensearch.rest.BytesRestResponse;
import org.opensearch.rest.RestChannel;
import org.opensearch.rest.RestRequest;

import static java.util.Collections.singletonList;
import static org.opensearch.rest.RestRequest.Method.PUT;
import static org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin1.SYSTEM_INDEX_1;
import static org.opensearch.security.systemindex.sampleplugin.SystemIndexPlugin2.SYSTEM_INDEX_2;

public class RestBulkIndexDocumentIntoMixOfSystemIndexAction extends BaseRestHandler {

private final Client client;
private final RunAsSubjectClient pluginClient;

public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, RunAsSubjectClient pluginClient) {
this.client = client;
this.pluginClient = pluginClient;
}

@Override
public List<Route> routes() {
return singletonList(new Route(PUT, "/try-create-and-bulk-mixed-index"));
}

@Override
public String getName() {
return "test_bulk_index_document_into_mix_of_system_index_action";
}

@Override
public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) {
return new RestChannelConsumer() {

@Override
public void accept(RestChannel channel) throws Exception {
BulkRequestBuilder builder = client.prepareBulk();
builder.add(new IndexRequest(SYSTEM_INDEX_1).source("content", 1));
builder.add(new IndexRequest(SYSTEM_INDEX_2).source("content", 1));
builder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
BulkRequest bulkRequest = builder.request();
pluginClient.bulk(bulkRequest, ActionListener.wrap(r -> {
channel.sendResponse(new BytesRestResponse(RestStatus.OK, r.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS)));
}, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); }));
}
};
}
}
Loading

0 comments on commit a7d2c57

Please sign in to comment.