Skip to content
Draft
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 @@ -41,6 +41,8 @@
import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden;
import static org.opensearch.test.framework.matcher.RestMatchers.isOk;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
* An integration test matrix for Dashboards multi-tenancy. Verifies both read and write operations
Expand Down Expand Up @@ -959,6 +961,85 @@ public DashboardMultiTenancyIntTests(
this.clusterConfig = clusterConfig;
}

/**
* Verifies that a user with kibana_user role cannot directly read another user's concrete
* tenant index via _mget without the securitytenant header. The kibana_user role should not
* grant index-level access to .kibana_* indices.
*/
@Test
public void mget_concreteIndexWithoutTenantHeader_shouldBeDenied() {
if (user.reference(READ).covers(dashboards_index_human_resources)) {
return;
}
try (TestRestClient restClient = cluster.getRestClient(user)) {
String docId = dashboards_index_human_resources.anyDocument().id();

TestRestClient.HttpResponse response = restClient.postJson("_mget/?pretty", """
{
"docs": [
{
"_index": "%s",
"_id": "%s"
}
]
}
""".formatted(dashboards_index_human_resources.name(), docId));

// _mget returns 200 at top level but each doc has an error
if (response.getStatusCode() == 200) {
String body = response.getBody();
assertTrue("Expected security_exception in mget doc response but got: " + body, body.contains("security_exception"));
assertFalse("Expected no found documents but got: " + body, body.contains("\"found\" : true"));
} else {
assertThat(response, isForbidden());
}
}
}

/**
* Verifies that a user with kibana_user role cannot directly search another user's concrete
* tenant index without the securitytenant header.
*/
@Test
public void search_concreteIndexWithoutTenantHeader_shouldBeDenied() {
if (user.reference(READ).covers(dashboards_index_human_resources)) {
return;
}
try (TestRestClient restClient = cluster.getRestClient(user)) {
TestRestClient.HttpResponse response = restClient.get(dashboards_index_human_resources.name() + "/_search?size=10");

assertThat(response, isForbidden());
}
}

/**
* Verifies that a user with kibana_user role cannot directly write to another user's concrete
* tenant index via _bulk without the securitytenant header.
*/
@Test
public void bulk_concreteIndexWithoutTenantHeader_shouldBeDenied() {
if (user.reference(WRITE).covers(dashboards_index_human_resources)) {
return;
}
try (TestRestClient restClient = cluster.getRestClient(user)) {
TestRestClient.HttpResponse response = restClient.postJson("_bulk", """
{ "index" : { "_index" : "%s", "_id" : "test_no_header_1" } }
{ "type": "dashboard", "title": "test" }
""".formatted(dashboards_index_human_resources.name()));

// _bulk returns 200 at top level but each item has a 403 status
if (response.getStatusCode() == 200) {
String body = response.getBody();
assertTrue(
"Expected security_exception in bulk item response but got: " + body,
body.contains("security_exception") || body.contains("\"status\":403")
);
} else {
assertThat(response, isForbidden());
}
}
}

private void delete(String... paths) {
try (TestRestClient adminRestClient = cluster.getAdminCertRestClient()) {
for (String path : paths) {
Expand Down
11 changes: 0 additions & 11 deletions src/main/resources/static_config/static_roles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,12 @@ kibana_user:
index_permissions:
- index_patterns:
- ".kibana"
- ".kibana-6"
- ".kibana_*"
- ".opensearch_dashboards"
- ".opensearch_dashboards-6"
- ".opensearch_dashboards_*"
allowed_actions:
- "delete"
- "index"
- "manage"
- "read"
- index_patterns:
- ".tasks"
- ".management-beats"
- "*:.tasks"
- "*:.management-beats"
allowed_actions:
- "indices_all"

own_index:
reserved: true
Expand Down
Loading