Skip to content
Closed
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
@@ -0,0 +1,91 @@
/*
* 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.privileges;

import java.io.IOException;

import org.junit.ClassRule;
import org.junit.Test;

import org.opensearch.client.RestHighLevelClient;
import org.opensearch.client.indices.GetIndexRequest;
import org.opensearch.test.framework.TestSecurityConfig;
import org.opensearch.test.framework.cluster.ClusterManager;
import org.opensearch.test.framework.cluster.LocalCluster;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.opensearch.client.RequestOptions.DEFAULT;
import static org.opensearch.core.rest.RestStatus.FORBIDDEN;
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL;
import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy;
import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.statusException;

/**
* End-to-end regression test for https://github.com/opensearch-project/security/issues/6103
*/
public class KibanaServerRoleIssue6103IntegTest {

private static final TestSecurityConfig.User DASHBOARDS_SERVER_USER = new TestSecurityConfig.User("kibanaserver").password(
"kibanaserver-secret"
);

/**
* Mirrors the pre-fix catch-all permissions: alias management only, no {@code indices:admin/get}.
*/
private static final TestSecurityConfig.Role LEGACY_DASHBOARDS_SERVER_ROLE = new TestSecurityConfig.Role("legacy_dashboards_server")
.clusterPermissions(
"cluster_composite_ops",
"cluster_monitor",
"indices:admin/index_template*",
"indices:admin/template*",
"indices:data/read/scroll*",
"manage_point_in_time"
)
.indexPermissions("indices_all")
.on(
".kibana",
".opensearch_dashboards",
".kibana-6",
".opensearch_dashboards-6",
".kibana_*",
".opensearch_dashboards_*",
".tasks",
".management-beats*"
)
.indexPermissions("indices:admin/aliases*")
.on("*");

private static final TestSecurityConfig.User LEGACY_DASHBOARDS_SERVER_USER = new TestSecurityConfig.User("legacy-kibanaserver")
.password("legacy-kibanaserver-secret")
.roles(LEGACY_DASHBOARDS_SERVER_ROLE);

@ClassRule
public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE)
.authc(AUTHC_HTTPBASIC_INTERNAL)
.users(DASHBOARDS_SERVER_USER, LEGACY_DASHBOARDS_SERVER_USER)
.rolesMapping(new TestSecurityConfig.RoleMapping("kibana_server").reserved(true).users(DASHBOARDS_SERVER_USER.getName()))
.build();

@Test
public void kibanaServerUser_canCheckIfKibanaIndexExistsBeforeBootstrap() throws IOException {
try (RestHighLevelClient client = cluster.getRestHighLevelClient(DASHBOARDS_SERVER_USER)) {
boolean exists = client.indices().exists(new GetIndexRequest(".kibana"), DEFAULT);

assertThat(exists, is(false));
}
}

@Test
public void legacyDashboardsServerRole_cannotCheckIfKibanaIndexExistsBeforeBootstrap() throws IOException {
try (RestHighLevelClient client = cluster.getRestHighLevelClient(LEGACY_DASHBOARDS_SERVER_USER)) {
assertThatThrownBy(() -> client.indices().exists(new GetIndexRequest(".kibana"), DEFAULT), statusException(FORBIDDEN));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* 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.privileges;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Set;

import org.apache.commons.io.IOUtils;
import org.junit.Test;

import org.opensearch.cluster.ClusterState;
import org.opensearch.cluster.metadata.ResolvedIndices;
import org.opensearch.common.settings.Settings;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.security.privileges.actionlevel.RoleBasedActionPrivileges;
import org.opensearch.security.privileges.dlsfls.FieldMasking;
import org.opensearch.security.securityconf.DynamicConfigFactory;
import org.opensearch.security.securityconf.FlattenedActionGroups;
import org.opensearch.security.securityconf.impl.CType;
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;
import org.opensearch.security.securityconf.impl.v7.RoleV7;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.opensearch.security.privileges.PrivilegeEvaluatorResponseMatcher.isAllowed;
import static org.opensearch.security.privileges.PrivilegeEvaluatorResponseMatcher.isForbidden;
import static org.opensearch.security.privileges.PrivilegeEvaluatorResponseMatcher.missingPrivileges;
import static org.opensearch.security.util.MockIndexMetadataBuilder.indices;
import static org.opensearch.security.util.MockPrivilegeEvaluationContextBuilder.ctx;

/**
* Regression test for https://github.com/opensearch-project/security/issues/6103
*/
public class KibanaServerRoleIssue6103Test {

@Test
public void kibanaServerRole_hasIndicesAdminGet_onCatchAllIndexPattern() throws Exception {
RoleBasedActionPrivileges actionPrivileges = loadStaticActionPrivileges();

PrivilegesEvaluatorResponse response = actionPrivileges.hasIndexPrivilege(
ctx().roles("kibana_server").actionPrivileges(actionPrivileges).get(),
Set.of("indices:admin/get"),
ResolvedIndices.of("some-unrelated-index")
);

assertThat(response, isAllowed());
}

@Test
public void kibanaServerRole_hasIndicesAdminGet_onKibanaIndex() throws Exception {
RoleBasedActionPrivileges actionPrivileges = loadStaticActionPrivileges();

PrivilegesEvaluatorResponse response = actionPrivileges.hasIndexPrivilege(
ctx().roles("kibana_server").actionPrivileges(actionPrivileges).get(),
Set.of("indices:admin/get"),
ResolvedIndices.of(".kibana_1")
);

assertThat(response, isAllowed());
}

@Test
public void kibanaServerRole_hasIndicesAdminGet_forAnyIndex_whenKibanaNotYetCreated() throws Exception {
RoleBasedActionPrivileges actionPrivileges = loadStaticActionPrivileges();
ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).metadata(indices().build()).build();

PrivilegesEvaluatorResponse response = actionPrivileges.hasIndexPrivilegeForAnyIndex(
ctx().roles("kibana_server").actionPrivileges(actionPrivileges).clusterState(clusterState).get(),
Set.of("indices:admin/get")
);

assertThat(response, isAllowed());
}

@Test
public void kibanaServerRole_hasIndicesAdminGet_onDotKibanaAliasWhenExists() throws Exception {
RoleBasedActionPrivileges actionPrivileges = loadStaticActionPrivileges();
ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE)
.metadata(indices().index(".kibana_1").alias(".kibana").of(".kibana_1").build())
.build();

PrivilegesEvaluatorResponse response = actionPrivileges.hasIndexPrivilege(
ctx().roles("kibana_server").actionPrivileges(actionPrivileges).clusterState(clusterState).get(),
Set.of("indices:admin/get"),
ResolvedIndices.of(".kibana")
);

assertThat(response, isAllowed());
}

@Test
public void kibanaServerRole_cannotWriteUnrelatedIndex() throws Exception {
RoleBasedActionPrivileges actionPrivileges = loadStaticActionPrivileges();

PrivilegesEvaluatorResponse response = actionPrivileges.hasIndexPrivilege(
ctx().roles("kibana_server").actionPrivileges(actionPrivileges).get(),
Set.of("indices:data/write/index"),
ResolvedIndices.of("some-unrelated-index")
);

assertThat(response, isForbidden());
assertThat(response, missingPrivileges("indices:data/write/index"));
}

private static RoleBasedActionPrivileges loadStaticActionPrivileges() throws Exception {
SecurityDynamicConfiguration<RoleV7> roles = SecurityDynamicConfiguration.fromYaml(
testResource("/static_config/static_roles.yml"),
CType.ROLES
);
FlattenedActionGroups actionGroups = new FlattenedActionGroups(
SecurityDynamicConfiguration.fromYaml(testResource("/static_config/static_action_groups.yml"), CType.ACTIONGROUPS)
);

return new RoleBasedActionPrivileges(
new CompiledRoles(roles, actionGroups, NamedXContentRegistry.EMPTY, FieldMasking.Config.DEFAULT, false),
org.opensearch.security.privileges.actionlevel.RuntimeOptimizedActionPrivileges.SpecialIndexProtection.NONE,
Settings.EMPTY,
false
);
}

private static String testResource(String fileName) throws IOException {
InputStream in = DynamicConfigFactory.class.getResourceAsStream(fileName);

if (in == null) {
throw new FileNotFoundException("could not find " + fileName);
}

return IOUtils.toString(in, StandardCharsets.UTF_8);
}
}
1 change: 1 addition & 0 deletions src/main/resources/static_config/static_roles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ kibana_server:
- "*"
allowed_actions:
- "indices:admin/aliases*"
- "indices:admin/get"

logstash:
reserved: true
Expand Down