diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index cc95833a86..256a2f64fc 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -268,7 +268,7 @@ Checkstyle enforces several rules within this codebase. Sometimes it will be nec *Suppression All Checkstyle Rules* ``` - // CS-SUPRESS-ALL: Legacy code to be deleted in Z.Y.X see http://github/issues/1234 + // CS-SUPPRESS-ALL: Legacy code to be deleted in Z.Y.X see http://github/issues/1234 ... // CS-ENFORCE-ALL ``` diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index e69efb26d5..1803a58fc9 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -1803,6 +1803,14 @@ public List> getSettings() { Property.Filtered ) ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY, + ConfigConstants.SECURITY_SYSTEM_INDICES_PERMISSIONS_DEFAULT, + Property.NodeScope, + Property.Filtered + ) + ); } return settings; diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 83dba986ee..48f4db38e9 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -314,7 +314,17 @@ public PrivilegesEvaluatorResponse evaluate( } // Security index access - if (securityIndexAccessEvaluator.evaluate(request, task, action0, requestedResolved, presponse).isComplete()) { + if (securityIndexAccessEvaluator.evaluate( + request, + task, + action0, + requestedResolved, + presponse, + securityRoles, + user, + resolver, + clusterService + ).isComplete()) { return presponse; } diff --git a/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java index 94b0478759..3743b27383 100644 --- a/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java @@ -26,39 +26,50 @@ package org.opensearch.security.privileges; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; - import org.opensearch.action.ActionRequest; import org.opensearch.action.RealtimeRequest; import org.opensearch.action.search.SearchRequest; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.Settings; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.resolver.IndexResolverReplacer; import org.opensearch.security.resolver.IndexResolverReplacer.Resolved; +import org.opensearch.security.securityconf.SecurityRoles; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.WildcardMatcher; +import org.opensearch.security.user.User; import org.opensearch.tasks.Task; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * This class performs authorization on requests targeting system indices + * NOTE: + * - The term `protected system indices` used here translates to system indices + * which have an added layer of security and cannot be accessed by anyone except Super Admin + */ public class SecurityIndexAccessEvaluator { Logger log = LogManager.getLogger(this.getClass()); private final String securityIndex; private final AuditLog auditLog; - private final WildcardMatcher securityDeniedActionMatcher; private final IndexResolverReplacer irr; private final boolean filterSecurityIndex; - // for system-indices configuration private final WildcardMatcher systemIndexMatcher; - private final boolean systemIndexEnabled; + private final WildcardMatcher superAdminAccessOnlyIndexMatcher; + private final WildcardMatcher deniedActionsMatcher; + + private final boolean isSystemIndexEnabled; + private final boolean isSystemIndexPermissionEnabled; public SecurityIndexAccessEvaluator(final Settings settings, AuditLog auditLog, IndexResolverReplacer irr) { this.securityIndex = settings.get( @@ -71,17 +82,33 @@ public SecurityIndexAccessEvaluator(final Settings settings, AuditLog auditLog, this.systemIndexMatcher = WildcardMatcher.from( settings.getAsList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_DEFAULT) ); - this.systemIndexEnabled = settings.getAsBoolean( + this.superAdminAccessOnlyIndexMatcher = WildcardMatcher.from(this.securityIndex); + this.isSystemIndexEnabled = settings.getAsBoolean( ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT ); - final boolean restoreSecurityIndexEnabled = settings.getAsBoolean( ConfigConstants.SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED, false ); - final List securityIndexDeniedActionPatternsList = new ArrayList(); + final List deniedActionPatternsList = deniedActionPatterns(); + + final List deniedActionPatternsListNoSnapshot = new ArrayList<>(deniedActionPatternsList); + deniedActionPatternsListNoSnapshot.add("indices:admin/close*"); + deniedActionPatternsListNoSnapshot.add("cluster:admin/snapshot/restore*"); + + deniedActionsMatcher = WildcardMatcher.from( + restoreSecurityIndexEnabled ? deniedActionPatternsList : deniedActionPatternsListNoSnapshot + ); + isSystemIndexPermissionEnabled = settings.getAsBoolean( + ConfigConstants.SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY, + ConfigConstants.SECURITY_SYSTEM_INDICES_PERMISSIONS_DEFAULT + ); + } + + private static List deniedActionPatterns() { + final List securityIndexDeniedActionPatternsList = new ArrayList<>(); securityIndexDeniedActionPatternsList.add("indices:data/write*"); securityIndexDeniedActionPatternsList.add("indices:admin/delete*"); securityIndexDeniedActionPatternsList.add("indices:admin/mapping/delete*"); @@ -89,15 +116,7 @@ public SecurityIndexAccessEvaluator(final Settings settings, AuditLog auditLog, securityIndexDeniedActionPatternsList.add("indices:admin/freeze*"); securityIndexDeniedActionPatternsList.add("indices:admin/settings/update*"); securityIndexDeniedActionPatternsList.add("indices:admin/aliases"); - - final List securityIndexDeniedActionPatternsListNoSnapshot = new ArrayList(); - securityIndexDeniedActionPatternsListNoSnapshot.addAll(securityIndexDeniedActionPatternsList); - securityIndexDeniedActionPatternsListNoSnapshot.add("indices:admin/close*"); - securityIndexDeniedActionPatternsListNoSnapshot.add("cluster:admin/snapshot/restore*"); - - securityDeniedActionMatcher = WildcardMatcher.from( - restoreSecurityIndexEnabled ? securityIndexDeniedActionPatternsList : securityIndexDeniedActionPatternsListNoSnapshot - ); + return securityIndexDeniedActionPatternsList; } public PrivilegesEvaluatorResponse evaluate( @@ -105,88 +124,200 @@ public PrivilegesEvaluatorResponse evaluate( final Task task, final String action, final Resolved requestedResolved, - final PrivilegesEvaluatorResponse presponse + final PrivilegesEvaluatorResponse presponse, + final SecurityRoles securityRoles, + final User user, + final IndexNameExpressionResolver resolver, + final ClusterService clusterService ) { - final boolean isDebugEnabled = log.isDebugEnabled(); - if (securityDeniedActionMatcher.test(action)) { + evaluateSystemIndicesAccess(action, requestedResolved, request, task, presponse, securityRoles, user, resolver, clusterService); + + if (requestedResolved.isLocalAll() + || requestedResolved.getAllIndices().contains(securityIndex) + || requestContainsAnySystemIndices(requestedResolved)) { + + if (request instanceof SearchRequest) { + ((SearchRequest) request).requestCache(Boolean.FALSE); + if (log.isDebugEnabled()) { + log.debug("Disable search request cache for this request"); + } + } + + if (request instanceof RealtimeRequest) { + ((RealtimeRequest) request).realtime(Boolean.FALSE); + if (log.isDebugEnabled()) { + log.debug("Disable realtime for this request"); + } + } + } + return presponse; + } + + /** + * Checks if request is for any system index + * @param requestedResolved request which contains indices to be matched against system indices + * @return true if a match is found, false otherwise + */ + private boolean requestContainsAnySystemIndices(final Resolved requestedResolved) { + return !getAllSystemIndices(requestedResolved).isEmpty(); + } + + /** + * Gets all indices requested in the original request. + * It will always return security index if it is present in the request, as security index is protected regardless + * of feature being enabled or disabled + * @param requestedResolved request which contains indices to be matched against system indices + * @return the list of protected system indices present in the request + */ + private List getAllSystemIndices(final Resolved requestedResolved) { + final List systemIndices = requestedResolved.getAllIndices() + .stream() + .filter(securityIndex::equals) + .collect(Collectors.toList()); + if (isSystemIndexEnabled) { + systemIndices.addAll(systemIndexMatcher.getMatchAny(requestedResolved.getAllIndices(), Collectors.toList())); + } + return systemIndices; + } + + /** + * Checks if request contains any system index that is non-permission-able + * NOTE: Security index is currently non-permission-able + * @param requestedResolved request which contains indices to be matched against non-permission-able system indices + * @return true if the request contains any non-permission-able index,false otherwise + */ + private boolean requestContainsAnyProtectedSystemIndices(final Resolved requestedResolved) { + return !getAllProtectedSystemIndices(requestedResolved).isEmpty(); + } + + /** + * Filters the request to get all system indices that are protected and are non-permission-able + * @param requestedResolved request which contains indices to be matched against non-permission-able system indices + * @return the list of protected system indices present in the request + */ + private List getAllProtectedSystemIndices(final Resolved requestedResolved) { + return new ArrayList<>(superAdminAccessOnlyIndexMatcher.getMatchAny(requestedResolved.getAllIndices(), Collectors.toList())); + } + + /** + * Is the current action allowed to be performed on security index + * @param action request action on security index + * @return true if action is allowed, false otherwise + */ + private boolean isActionAllowed(String action) { + return deniedActionsMatcher.test(action); + } + + /** + * Perform access check on requested indices and actions for those indices + * @param action action to be performed on request indices + * @param requestedResolved this object contains all indices this request is resolved to + * @param request the action request to be used for audit logging + * @param task task in which this access check will be performed + * @param presponse the pre-response object that will eventually become a response and returned to the requester + * @param securityRoles user's roles which will be used for access evaluation + * @param user this user's permissions will be looked up + * @param resolver the index expression resolver + * @param clusterService required to fetch cluster state metadata + */ + private void evaluateSystemIndicesAccess( + final String action, + final Resolved requestedResolved, + final ActionRequest request, + final Task task, + final PrivilegesEvaluatorResponse presponse, + SecurityRoles securityRoles, + final User user, + final IndexNameExpressionResolver resolver, + final ClusterService clusterService + ) { + // Perform access check is system index permissions are enabled + boolean containsSystemIndex = requestContainsAnySystemIndices(requestedResolved); + + if (isSystemIndexPermissionEnabled) { + boolean containsProtectedIndex = requestContainsAnyProtectedSystemIndices(requestedResolved); + if (containsProtectedIndex) { + auditLog.logSecurityIndexAttempt(request, action, task); + if (log.isInfoEnabled()) { + log.info( + "{} not permitted for a regular user {} on protected system indices {}", + action, + securityRoles, + String.join(", ", getAllProtectedSystemIndices(requestedResolved)) + ); + } + presponse.allowed = false; + presponse.markComplete(); + return; + } else if (containsSystemIndex + && !securityRoles.hasExplicitIndexPermission( + requestedResolved, + user, + new String[] { ConfigConstants.SYSTEM_INDEX_PERMISSION }, + resolver, + clusterService + )) { + auditLog.logSecurityIndexAttempt(request, action, task); + if (log.isInfoEnabled()) { + log.info( + "No {} permission for user roles {} to System Indices {}", + action, + securityRoles, + String.join(", ", getAllSystemIndices(requestedResolved)) + ); + } + presponse.allowed = false; + presponse.markComplete(); + return; + } + } + + if (isActionAllowed(action)) { if (requestedResolved.isLocalAll()) { if (filterSecurityIndex) { irr.replace(request, false, "*", "-" + securityIndex); - if (isDebugEnabled) { + if (log.isDebugEnabled()) { log.debug( - "Filtered '{}'from {}, resulting list with *,-{} is {}", + "Filtered '{}' from {}, resulting list with *,-{} is {}", securityIndex, requestedResolved, securityIndex, irr.resolveRequest(request) ); } - return presponse; } else { auditLog.logSecurityIndexAttempt(request, action, task); log.warn("{} for '_all' indices is not allowed for a regular user", action); presponse.allowed = false; - return presponse.markComplete(); + presponse.markComplete(); } - } else if (matchAnySystemIndices(requestedResolved)) { + } + // if system index is enabled and system index permissions are enabled we don't need to perform any further + // checks as it has already been performed via hasExplicitIndexPermission + else if (containsSystemIndex && !isSystemIndexPermissionEnabled) { if (filterSecurityIndex) { Set allWithoutSecurity = new HashSet<>(requestedResolved.getAllIndices()); allWithoutSecurity.remove(securityIndex); if (allWithoutSecurity.isEmpty()) { - if (isDebugEnabled) { + if (log.isDebugEnabled()) { log.debug("Filtered '{}' but resulting list is empty", securityIndex); } presponse.allowed = false; - return presponse.markComplete(); + presponse.markComplete(); + return; } irr.replace(request, false, allWithoutSecurity.toArray(new String[0])); - if (isDebugEnabled) { + if (log.isDebugEnabled()) { log.debug("Filtered '{}', resulting list is {}", securityIndex, allWithoutSecurity); } - return presponse; } else { auditLog.logSecurityIndexAttempt(request, action, task); - final String foundSystemIndexes = getProtectedIndexes(requestedResolved).stream().collect(Collectors.joining(", ")); + final String foundSystemIndexes = String.join(", ", getAllSystemIndices(requestedResolved)); log.warn("{} for '{}' index is not allowed for a regular user", action, foundSystemIndexes); presponse.allowed = false; - return presponse.markComplete(); + presponse.markComplete(); } } } - - if (requestedResolved.isLocalAll() - || requestedResolved.getAllIndices().contains(securityIndex) - || matchAnySystemIndices(requestedResolved)) { - - if (request instanceof SearchRequest) { - ((SearchRequest) request).requestCache(Boolean.FALSE); - if (isDebugEnabled) { - log.debug("Disable search request cache for this request"); - } - } - - if (request instanceof RealtimeRequest) { - ((RealtimeRequest) request).realtime(Boolean.FALSE); - if (isDebugEnabled) { - log.debug("Disable realtime for this request"); - } - } - } - return presponse; - } - - private boolean matchAnySystemIndices(final Resolved requestedResolved) { - return !getProtectedIndexes(requestedResolved).isEmpty(); - } - - private List getProtectedIndexes(final Resolved requestedResolved) { - final List protectedIndexes = requestedResolved.getAllIndices() - .stream() - .filter(securityIndex::equals) - .collect(Collectors.toList()); - if (systemIndexEnabled) { - protectedIndexes.addAll(systemIndexMatcher.getMatchAny(requestedResolved.getAllIndices(), Collectors.toList())); - } - return protectedIndexes; } } diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java index d488c9b7d1..84109c845e 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java @@ -448,6 +448,36 @@ public EvaluatedDlsFlsConfig getDlsFls( return new EvaluatedDlsFlsConfig(dlsQueries, flsFields, maskedFieldsMap); } + public boolean hasExplicitIndexPermission( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { + final Set indicesForRequest = new HashSet<>(resolved.getAllIndicesResolved(cs, resolver)); + if (indicesForRequest.isEmpty()) { + // If no indices could be found on the request there is no way to check for the explicit permissions + return false; + } + + final Set explicitlyAllowedIndices = roles.stream() + .map(role -> role.getAllResolvedPermittedIndices(resolved, user, actions, resolver, cs, true)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + + if (log.isDebugEnabled()) { + log.debug( + "ExplicitIndexPermission check indices for request {}, explicitly allowed indices {}", + indicesForRequest.toString(), + explicitlyAllowedIndices.toString() + ); + } + + indicesForRequest.removeAll(explicitlyAllowedIndices); + return indicesForRequest.isEmpty(); + } + // opensearchDashboards special only, terms eval public Set getAllPermittedIndicesForDashboards( Resolved resolved, @@ -458,7 +488,7 @@ public Set getAllPermittedIndicesForDashboards( ) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { - retVal.addAll(sr.getAllResolvedPermittedIndices(Resolved._LOCAL_ALL, user, actions, resolver, cs)); + retVal.addAll(sr.getAllResolvedPermittedIndices(Resolved._LOCAL_ALL, user, actions, resolver, cs, false)); retVal.addAll(resolved.getRemoteIndices()); } return Collections.unmodifiableSet(retVal); @@ -468,7 +498,7 @@ public Set getAllPermittedIndicesForDashboards( public Set reduce(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { - retVal.addAll(sr.getAllResolvedPermittedIndices(resolved, user, actions, resolver, cs)); + retVal.addAll(sr.getAllResolvedPermittedIndices(resolved, user, actions, resolver, cs, false)); } if (log.isDebugEnabled()) { log.debug("Reduced requested resolved indices {} to permitted indices {}.", resolved, retVal.toString()); @@ -536,7 +566,8 @@ private Set getAllResolvedPermittedIndices( User user, String[] actions, IndexNameExpressionResolver resolver, - ClusterService cs + ClusterService cs, + boolean matchExplicitly ) { final Set retVal = new HashSet<>(); @@ -545,7 +576,11 @@ private Set getAllResolvedPermittedIndices( boolean patternMatch = false; final Set tperms = p.getTypePerms(); for (TypePerm tp : tperms) { - if (tp.typeMatcher.matchAny(resolved.getTypes())) { + // if matchExplicitly is true we don't want to match against `*` pattern + WildcardMatcher matcher = matchExplicitly && (tp.getPerms() == WildcardMatcher.ANY) + ? WildcardMatcher.NONE + : tp.getTypeMatcher(); + if (matcher.matchAny(resolved.getTypes())) { patternMatch = tp.getPerms().matchAll(actions); } } diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index 6f43c43d03..928981efd6 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -34,6 +34,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -463,7 +464,7 @@ public Set getAllPermittedIndicesForDashboards( ) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { - retVal.addAll(sr.getAllResolvedPermittedIndices(Resolved._LOCAL_ALL, user, actions, resolver, cs)); + retVal.addAll(sr.getAllResolvedPermittedIndices(Resolved._LOCAL_ALL, user, actions, resolver, cs, Function.identity())); retVal.addAll(resolved.getRemoteIndices()); } return Collections.unmodifiableSet(retVal); @@ -473,7 +474,7 @@ public Set getAllPermittedIndicesForDashboards( public Set reduce(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { - retVal.addAll(sr.getAllResolvedPermittedIndices(resolved, user, actions, resolver, cs)); + retVal.addAll(sr.getAllResolvedPermittedIndices(resolved, user, actions, resolver, cs, Function.identity())); } if (log.isDebugEnabled()) { log.debug("Reduced requested resolved indices {} to permitted indices {}.", resolved, retVal.toString()); @@ -498,10 +499,43 @@ public boolean impliesClusterPermissionPermission(String action) { @Override public boolean hasExplicitClusterPermissionPermission(String action) { - return roles.stream() - .map(r -> r.clusterPerms == WildcardMatcher.ANY ? WildcardMatcher.NONE : r.clusterPerms) - .filter(m -> m.test(action)) - .count() > 0; + return roles.stream().map(r -> matchExplicitly(r.clusterPerms)).filter(m -> m.test(action)).count() > 0; + } + + private static WildcardMatcher matchExplicitly(final WildcardMatcher matcher) { + return matcher == WildcardMatcher.ANY ? WildcardMatcher.NONE : matcher; + } + + @Override + public boolean hasExplicitIndexPermission( + final Resolved resolved, + final User user, + final String[] actions, + final IndexNameExpressionResolver resolver, + final ClusterService cs + ) { + + final Set indicesForRequest = new HashSet<>(resolved.getAllIndicesResolved(cs, resolver)); + if (indicesForRequest.isEmpty()) { + // If no indices could be found on the request there is no way to check for the explicit permissions + return false; + } + + final Set explicitlyAllowedIndices = roles.stream() + .map(role -> role.getAllResolvedPermittedIndices(resolved, user, actions, resolver, cs, SecurityRoles::matchExplicitly)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + + if (log.isDebugEnabled()) { + log.debug( + "ExplicitIndexPermission check indices for request {}, explicitly allowed indices {}", + indicesForRequest.toString(), + explicitlyAllowedIndices.toString() + ); + } + + indicesForRequest.removeAll(explicitlyAllowedIndices); + return indicesForRequest.isEmpty(); } // rolespan @@ -578,13 +612,14 @@ private Set getAllResolvedPermittedIndices( User user, String[] actions, IndexNameExpressionResolver resolver, - ClusterService cs + ClusterService cs, + Function matcherModification ) { final Set retVal = new HashSet<>(); for (IndexPattern p : ipatterns) { // what if we cannot resolve one (for create purposes) - final boolean patternMatch = p.getPerms().matchAll(actions); + final boolean patternMatch = matcherModification.apply(p.getPerms()).matchAll(actions); // final Set tperms = p.getTypePerms(); // for (TypePerm tp : tperms) { diff --git a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java index c52a3c1bad..079853d581 100644 --- a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java +++ b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java @@ -41,6 +41,18 @@ public interface SecurityRoles { boolean hasExplicitClusterPermissionPermission(String action); + /** + * Determines if the actions are explicitly granted for indices + * @return if all indices in the request have an explicit grant for all actions + */ + boolean hasExplicitIndexPermission( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ); + Set getRoleNames(); Set reduce( @@ -84,5 +96,4 @@ Set getAllPermittedIndicesForDashboards( ); SecurityRoles filter(Set roles); - } diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index 61962a61f7..8317d65335 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -311,8 +311,11 @@ public enum RolesMappingResolution { "opendistro_security_injected_roles_validation_header"; // System indices settings + public static final String SYSTEM_INDEX_PERMISSION = "system:admin/system_index"; public static final String SECURITY_SYSTEM_INDICES_ENABLED_KEY = "plugins.security.system_indices.enabled"; public static final Boolean SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT = false; + public static final String SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY = "plugins.security.system_indices.permission.enabled"; + public static final Boolean SECURITY_SYSTEM_INDICES_PERMISSIONS_DEFAULT = false; public static final String SECURITY_SYSTEM_INDICES_KEY = "plugins.security.system_indices.indices"; public static final List SECURITY_SYSTEM_INDICES_DEFAULT = Collections.emptyList(); diff --git a/src/test/java/org/opensearch/security/IntegrationTests.java b/src/test/java/org/opensearch/security/IntegrationTests.java index 4c1086ef4f..63ae25316b 100644 --- a/src/test/java/org/opensearch/security/IntegrationTests.java +++ b/src/test/java/org/opensearch/security/IntegrationTests.java @@ -1010,6 +1010,7 @@ public void testSecurityIndexSecurity() throws Exception { encodeBasicHeader("nagilum", "nagilum") ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); + res = rh.executePutRequest( "*dis*rit*/_mapping?pretty", "{\"properties\": {\"name\":{\"type\":\"text\"}}}", @@ -1045,9 +1046,8 @@ public void testSecurityIndexSecurity() throws Exception { encodeBasicHeader("nagilum", "nagilum") ); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, res.getStatusCode()); - // res = rh.executePostRequest(".opendistro_security/_freeze", "", - // encodeBasicHeader("nagilum", "nagilum")); - // Assert.assertTrue(res.getStatusCode() >= 400); + res = rh.executePostRequest(".opendistro_security/_freeze", "", encodeBasicHeader("nagilum", "nagilum")); + Assert.assertEquals(400, res.getStatusCode()); String bulkBody = "{ \"index\" : { \"_index\" : \".opendistro_security\", \"_id\" : \"1\" } }\n" + "{ \"field1\" : \"value1\" }\n" diff --git a/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java b/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java index b953ac8ddb..4f25c71d66 100644 --- a/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java +++ b/src/test/java/org/opensearch/security/privileges/PrivilegesEvaluatorTest.java @@ -14,6 +14,7 @@ import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.opensearch.common.settings.Settings; @@ -25,6 +26,7 @@ public class PrivilegesEvaluatorTest extends SingleClusterTest { private static final Header NegativeLookaheadUserHeader = encodeBasicHeader("negative_lookahead_user", "negative_lookahead_user"); private static final Header NegatedRegexUserHeader = encodeBasicHeader("negated_regex_user", "negated_regex_user"); + @Before public void setupSettingsIndexPattern() throws Exception { Settings settings = Settings.builder().build(); setup( @@ -39,7 +41,6 @@ public void setupSettingsIndexPattern() throws Exception { @Test public void testNegativeLookaheadPattern() throws Exception { - setupSettingsIndexPattern(); RestHelper rh = nonSslRestHelper(); RestHelper.HttpResponse response = rh.executeGetRequest("*/_search", NegativeLookaheadUserHeader); @@ -50,8 +51,6 @@ public void testNegativeLookaheadPattern() throws Exception { @Test public void testRegexPattern() throws Exception { - setupSettingsIndexPattern(); - RestHelper rh = nonSslRestHelper(); RestHelper.HttpResponse response = rh.executeGetRequest("*/_search", NegatedRegexUserHeader); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); diff --git a/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java b/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java index 14c5eabb73..dc95a0dbe0 100644 --- a/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java +++ b/src/test/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluatorTest.java @@ -14,7 +14,6 @@ import com.google.common.collect.ImmutableSet; import org.apache.logging.log4j.Logger; import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -24,20 +23,36 @@ import org.opensearch.action.get.MultiGetRequest; import org.opensearch.action.search.SearchRequest; import org.opensearch.action.support.IndicesOptions; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.resolver.IndexResolverReplacer; import org.opensearch.security.resolver.IndexResolverReplacer.Resolved; +import org.opensearch.security.securityconf.ConfigModelV7; +import org.opensearch.security.securityconf.SecurityRoles; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; import org.opensearch.tasks.Task; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import static org.opensearch.security.support.ConfigConstants.SYSTEM_INDEX_PERMISSION; @RunWith(MockitoJUnitRunner.class) public class SecurityIndexAccessEvaluatorTest { @@ -54,18 +69,78 @@ public class SecurityIndexAccessEvaluatorTest { private PrivilegesEvaluatorResponse presponse; @Mock private Logger log; + @Mock + ClusterService cs; private SecurityIndexAccessEvaluator evaluator; - private static final String UNPROTECTED_ACTION = "indices:data/read"; private static final String PROTECTED_ACTION = "indices:data/write"; - @Before - public void before() { + private static final String TEST_SYSTEM_INDEX = ".test_system_index"; + private static final String TEST_INDEX = ".test"; + private static final String SECURITY_INDEX = ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX; + + @Mock + SecurityRoles securityRoles; + + User user; + + IndexNameExpressionResolver indexNameExpressionResolver; + + private ThreadContext createThreadContext() { + return new ThreadContext(Settings.EMPTY); + } + + protected IndexNameExpressionResolver createIndexNameExpressionResolver(ThreadContext threadContext) { + return new IndexNameExpressionResolver(threadContext); + } + + public void setup( + boolean isSystemIndexEnabled, + boolean isSystemIndexPermissionsEnabled, + String index, + boolean createIndexPatternWithSystemIndexPermission + ) { + ThreadContext threadContext = createThreadContext(); + indexNameExpressionResolver = createIndexNameExpressionResolver(threadContext); + + // create a security role + ConfigModelV7.IndexPattern ip = spy(new ConfigModelV7.IndexPattern(index)); + ConfigModelV7.SecurityRole.Builder _securityRole = new ConfigModelV7.SecurityRole.Builder("role_a"); + ip.addPerm(createIndexPatternWithSystemIndexPermission ? Set.of("*", SYSTEM_INDEX_PERMISSION) : Set.of("*")); + _securityRole.addIndexPattern(ip); + _securityRole.addClusterPerms(List.of("*")); + ConfigModelV7.SecurityRole secRole = _securityRole.build(); + + try { + // create an instance of Security Role + Constructor constructor = ConfigModelV7.SecurityRoles.class.getDeclaredConstructor(int.class); + constructor.setAccessible(true); + securityRoles = constructor.newInstance(1); + + // add security role to Security Roles + Method addSecurityRoleMethod = ConfigModelV7.SecurityRoles.class.getDeclaredMethod( + "addSecurityRole", + ConfigModelV7.SecurityRole.class + ); + addSecurityRoleMethod.setAccessible(true); + addSecurityRoleMethod.invoke(securityRoles, secRole); + + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + + // create a user and associate them with the role + user = new User("user_a"); + user.addSecurityRoles(List.of("role_a")); + + // when trying to resolve Index Names + evaluator = new SecurityIndexAccessEvaluator( - Settings.EMPTY.builder() - .put("plugins.security.system_indices.indices", ".test") - .put("plugins.security.system_indices.enabled", true) + Settings.builder() + .put(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, TEST_SYSTEM_INDEX) + .put(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, isSystemIndexEnabled) + .put(ConfigConstants.SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY, isSystemIndexPermissionsEnabled) .build(), auditLog, irr @@ -73,6 +148,9 @@ public void before() { evaluator.log = log; when(log.isDebugEnabled()).thenReturn(true); + when(log.isInfoEnabled()).thenReturn(true); + + doReturn(ImmutableSet.of(index)).when(ip).getResolvedIndexPattern(user, indexNameExpressionResolver, cs, true); } @After @@ -81,66 +159,522 @@ public void after() { } @Test - public void actionIsNotProtected_noSystemIndexInvolved() { - final Resolved resolved = createResolved(".test"); + public void testUnprotectedActionOnRegularIndex_systemIndexDisabled() { + setup(false, false, TEST_INDEX, false); + final Resolved resolved = createResolved(TEST_INDEX); + + // Action + final PrivilegesEvaluatorResponse response = evaluator.evaluate( + request, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + verifyNoInteractions(presponse); + assertThat(response, is(presponse)); + + } + + @Test + public void testUnprotectedActionOnRegularIndex_systemIndexPermissionDisabled() { + setup(true, false, TEST_INDEX, false); + final Resolved resolved = createResolved(TEST_INDEX); + + // Action + final PrivilegesEvaluatorResponse response = evaluator.evaluate( + request, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + verifyNoInteractions(presponse); + assertThat(response, is(presponse)); + } + + @Test + public void testUnprotectedActionOnRegularIndex_systemIndexPermissionEnabled() { + setup(true, true, TEST_INDEX, false); + final Resolved resolved = createResolved(TEST_INDEX); + + // Action + final PrivilegesEvaluatorResponse response = evaluator.evaluate( + request, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + verifyNoInteractions(presponse); + assertThat(response, is(presponse)); + } + + @Test + public void testUnprotectedActionOnSystemIndex_systemIndexDisabled() { + setup(false, false, TEST_SYSTEM_INDEX, false); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); // Action - final PrivilegesEvaluatorResponse response = evaluator.evaluate(request, null, UNPROTECTED_ACTION, resolved, presponse); + final PrivilegesEvaluatorResponse response = evaluator.evaluate( + request, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + verifyNoInteractions(presponse); + assertThat(response, is(presponse)); + } + @Test + public void testUnprotectedActionOnSystemIndex_systemIndexPermissionDisabled() { + setup(true, false, TEST_SYSTEM_INDEX, false); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); + + // Action + final PrivilegesEvaluatorResponse response = evaluator.evaluate( + request, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); verifyNoInteractions(presponse); assertThat(response, is(presponse)); + } + + @Test + public void testUnprotectedActionOnSystemIndex_systemIndexPermissionEnabled_WithoutSystemIndexPermission() { + setup(true, true, TEST_SYSTEM_INDEX, false); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); + + // Action + final PrivilegesEvaluatorResponse response = evaluator.evaluate( + request, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + verify(presponse).markComplete(); + assertThat(response, is(presponse)); + + verify(auditLog).logSecurityIndexAttempt(request, UNPROTECTED_ACTION, null); + verify(log).isInfoEnabled(); + verify(log).info("No {} permission for user roles {} to System Indices {}", UNPROTECTED_ACTION, securityRoles, TEST_SYSTEM_INDEX); + } - verify(log).isDebugEnabled(); + @Test + public void testUnprotectedActionOnSystemIndex_systemIndexPermissionEnabled_WithSystemIndexPermission() { + setup(true, true, TEST_SYSTEM_INDEX, true); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); + + // Action + final PrivilegesEvaluatorResponse response = evaluator.evaluate( + request, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + assertThat(response, is(presponse)); + // unprotected action is not allowed on a system index + assertThat(presponse.allowed, is(false)); } @Test - public void disableCacheOrRealtimeOnSystemIndex() { + public void testDisableCacheOrRealtimeOnSystemIndex_systemIndexDisabled() { + setup(false, false, TEST_SYSTEM_INDEX, false); + final SearchRequest searchRequest = mock(SearchRequest.class); final MultiGetRequest realtimeRequest = mock(MultiGetRequest.class); - final Resolved resolved = createResolved(".test"); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); // Action - evaluator.evaluate(request, null, UNPROTECTED_ACTION, resolved, presponse); - evaluator.evaluate(searchRequest, null, UNPROTECTED_ACTION, resolved, presponse); - evaluator.evaluate(realtimeRequest, null, UNPROTECTED_ACTION, resolved, presponse); + evaluator.evaluate(request, null, UNPROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + evaluator.evaluate( + searchRequest, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + evaluator.evaluate( + realtimeRequest, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); verifyNoInteractions(presponse); + } + + @Test + public void testDisableCacheOrRealtimeOnSystemIndex_systemIndexPermissionDisabled() { + setup(true, false, TEST_SYSTEM_INDEX, false); + + final SearchRequest searchRequest = mock(SearchRequest.class); + final MultiGetRequest realtimeRequest = mock(MultiGetRequest.class); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); + + // Action + evaluator.evaluate(request, null, UNPROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + evaluator.evaluate( + searchRequest, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + evaluator.evaluate( + realtimeRequest, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + + verify(searchRequest).requestCache(Boolean.FALSE); + verify(realtimeRequest).realtime(Boolean.FALSE); + + verify(log, times(2)).isDebugEnabled(); + verify(log).debug("Disable search request cache for this request"); + verify(log).debug("Disable realtime for this request"); + } + + @Test + public void testDisableCacheOrRealtimeOnSystemIndex_systemIndexPermissionEnabled_withoutSystemIndexPermission() { + setup(true, true, TEST_SYSTEM_INDEX, false); + + final SearchRequest searchRequest = mock(SearchRequest.class); + final MultiGetRequest realtimeRequest = mock(MultiGetRequest.class); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); + + // Action + evaluator.evaluate(request, null, UNPROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + evaluator.evaluate( + searchRequest, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + evaluator.evaluate( + realtimeRequest, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + + verify(searchRequest).requestCache(Boolean.FALSE); + verify(realtimeRequest).realtime(Boolean.FALSE); + + verify(log, times(2)).isDebugEnabled(); + verify(log).debug("Disable search request cache for this request"); + verify(log).debug("Disable realtime for this request"); + verify(auditLog).logSecurityIndexAttempt(request, UNPROTECTED_ACTION, null); + verify(auditLog).logSecurityIndexAttempt(searchRequest, UNPROTECTED_ACTION, null); + verify(auditLog).logSecurityIndexAttempt(realtimeRequest, UNPROTECTED_ACTION, null); + verify(presponse, times(3)).markComplete(); + verify(log, times(2)).isDebugEnabled(); + verify(log, times(3)).isInfoEnabled(); + verify(log, times(3)).info( + "No {} permission for user roles {} to System Indices {}", + UNPROTECTED_ACTION, + securityRoles, + TEST_SYSTEM_INDEX + ); + verify(log).debug("Disable search request cache for this request"); + verify(log).debug("Disable realtime for this request"); + } + + @Test + public void testDisableCacheOrRealtimeOnSystemIndex_systemIndexPermissionEnabled_withSystemIndexPermission() { + setup(true, true, TEST_SYSTEM_INDEX, true); + + final SearchRequest searchRequest = mock(SearchRequest.class); + final MultiGetRequest realtimeRequest = mock(MultiGetRequest.class); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); + + // Action + evaluator.evaluate(request, null, UNPROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + evaluator.evaluate( + searchRequest, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + evaluator.evaluate( + realtimeRequest, + null, + UNPROTECTED_ACTION, + resolved, + presponse, + securityRoles, + user, + indexNameExpressionResolver, + cs + ); + verify(searchRequest).requestCache(Boolean.FALSE); verify(realtimeRequest).realtime(Boolean.FALSE); - verify(log, times(3)).isDebugEnabled(); + verify(log, times(2)).isDebugEnabled(); verify(log).debug("Disable search request cache for this request"); verify(log).debug("Disable realtime for this request"); } @Test - public void protectedActionLocalAll() { + public void testProtectedActionLocalAll_systemIndexDisabled() { + setup(false, false, TEST_SYSTEM_INDEX, false); final Resolved resolved = Resolved._LOCAL_ALL; // Action - evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse); + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); assertThat(presponse.allowed, is(false)); verify(presponse).markComplete(); - - verify(log).isDebugEnabled(); verify(log).warn("{} for '_all' indices is not allowed for a regular user", "indices:data/write"); } @Test - public void protectedActionSystemIndex() { - final Resolved resolved = createResolved(".test", ".opendistro_security"); + public void testProtectedActionLocalAll_systemIndexPermissionDisabled() { + setup(true, false, TEST_SYSTEM_INDEX, false); + final Resolved resolved = Resolved._LOCAL_ALL; + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + + verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); + assertThat(presponse.allowed, is(false)); + verify(presponse).markComplete(); + verify(log).warn("{} for '_all' indices is not allowed for a regular user", PROTECTED_ACTION); + } + + @Test + public void testProtectedActionLocalAll_systemIndexPermissionEnabled() { + setup(true, true, TEST_SYSTEM_INDEX, false); + final Resolved resolved = Resolved._LOCAL_ALL; + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + + verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); + assertThat(presponse.allowed, is(false)); + verify(presponse).markComplete(); + verify(log).warn("{} for '_all' indices is not allowed for a regular user", PROTECTED_ACTION); + } + + @Test + public void testProtectedActionOnRegularIndex_systemIndexDisabled() { + setup(false, false, TEST_INDEX, false); + final Resolved resolved = createResolved(TEST_INDEX); + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + + assertThat(presponse.allowed, is(false)); + } + + @Test + public void testProtectedActionOnRegularIndex_systemIndexPermissionDisabled() { + setup(true, false, TEST_INDEX, false); + final Resolved resolved = createResolved(TEST_INDEX); + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + + assertThat(presponse.allowed, is(false)); + } + + @Test + public void testProtectedActionOnRegularIndex_systemIndexPermissionEnabled() { + setup(true, true, TEST_INDEX, false); + final Resolved resolved = createResolved(TEST_INDEX); + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + + assertThat(presponse.allowed, is(false)); + } + + @Test + public void testProtectedActionOnSystemIndex_systemIndexDisabled() { + setup(false, false, TEST_SYSTEM_INDEX, false); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + + assertThat(presponse.allowed, is(false)); + } + + @Test + public void testProtectedActionOnSystemIndex_systemIndexPermissionDisabled() { + setup(true, false, TEST_SYSTEM_INDEX, false); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + + verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); + assertThat(presponse.allowed, is(false)); + verify(presponse).markComplete(); + verify(log).warn("{} for '{}' index is not allowed for a regular user", PROTECTED_ACTION, TEST_SYSTEM_INDEX); + } + + @Test + public void testProtectedActionOnSystemIndex_systemIndexPermissionEnabled_withoutSystemIndexPermission() { + setup(true, true, TEST_SYSTEM_INDEX, false); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + + verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); + assertThat(presponse.allowed, is(false)); + verify(presponse).markComplete(); + verify(log).isInfoEnabled(); + verify(log).info("No {} permission for user roles {} to System Indices {}", PROTECTED_ACTION, securityRoles, TEST_SYSTEM_INDEX); + } + + @Test + public void testProtectedActionOnSystemIndex_systemIndexPermissionEnabled_withSystemIndexPermission() { + + setup(true, true, TEST_SYSTEM_INDEX, true); + final Resolved resolved = createResolved(TEST_SYSTEM_INDEX); + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + + assertThat(presponse.allowed, is(false)); + } + + @Test + public void testProtectedActionOnProtectedSystemIndex_systemIndexDisabled() { + setup(false, false, SECURITY_INDEX, false); + final Resolved resolved = createResolved(SECURITY_INDEX); + + // Action + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + + verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); + assertThat(presponse.allowed, is(false)); + verify(presponse).markComplete(); + + verify(log).warn("{} for '{}' index is not allowed for a regular user", PROTECTED_ACTION, SECURITY_INDEX); + } + + @Test + public void testProtectedActionOnProtectedSystemIndex_systemIndexPermissionDisabled() { + setup(true, false, SECURITY_INDEX, false); + final Resolved resolved = createResolved(SECURITY_INDEX); // Action - evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse); + evaluator.evaluate(request, task, PROTECTED_ACTION, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); assertThat(presponse.allowed, is(false)); verify(presponse).markComplete(); - verify(log).isDebugEnabled(); - verify(log).warn("{} for '{}' index is not allowed for a regular user", "indices:data/write", ".opendistro_security, .test"); + verify(log).warn("{} for '{}' index is not allowed for a regular user", PROTECTED_ACTION, SECURITY_INDEX); + } + + @Test + public void testUnprotectedActionOnProtectedSystemIndex_systemIndexPermissionEnabled_withoutSystemIndexPermission() { + testSecurityIndexAccess(UNPROTECTED_ACTION); + } + + @Test + public void testUnprotectedActionOnProtectedSystemIndex_systemIndexPermissionEnabled_withSystemIndexPermission() { + testSecurityIndexAccess(UNPROTECTED_ACTION); + } + + @Test + public void testProtectedActionOnProtectedSystemIndex_systemIndexPermissionEnabled_withoutSystemIndexPermission() { + testSecurityIndexAccess(PROTECTED_ACTION); + } + + @Test + public void testProtectedActionOnProtectedSystemIndex_systemIndexPermissionEnabled_withSystemIndexPermission() { + testSecurityIndexAccess(PROTECTED_ACTION); + } + + private void testSecurityIndexAccess(String action) { + setup(true, true, SECURITY_INDEX, true); + + final Resolved resolved = createResolved(SECURITY_INDEX); + + // Action + evaluator.evaluate(request, task, action, resolved, presponse, securityRoles, user, indexNameExpressionResolver, cs); + + verify(auditLog).logSecurityIndexAttempt(request, action, task); + assertThat(presponse.allowed, is(false)); + verify(presponse).markComplete(); + + verify(log).isInfoEnabled(); + verify(log).info("{} not permitted for a regular user {} on protected system indices {}", action, securityRoles, SECURITY_INDEX); } private Resolved createResolved(final String... indexes) { diff --git a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsV6Test.java b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsV6Test.java new file mode 100644 index 0000000000..edf5a7533b --- /dev/null +++ b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsV6Test.java @@ -0,0 +1,180 @@ +/* + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.securityconf; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.quality.Strictness; +import org.opensearch.action.support.IndicesOptions; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.metadata.IndexAbstraction; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.resolver.IndexResolverReplacer; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +public class SecurityRolesPermissionsV6Test { + static final String TEST_INDEX = ".test"; + + // a role with * permission but no system:admin/system_index permission + static final Map NO_EXPLICIT_SYSTEM_INDEX_PERMISSION = ImmutableMap.builder() + .put("all_access_without_system_index_permission", role(new String[] { "*" }, new String[] { TEST_INDEX }, new String[] { "*" })) + .build(); + + static final Map HAS_SYSTEM_INDEX_PERMISSION = ImmutableMap.builder() + .put( + "has_system_index_permission", + role(new String[] { "*" }, new String[] { TEST_INDEX }, new String[] { ConfigConstants.SYSTEM_INDEX_PERMISSION }) + ) + .build(); + + static ObjectNode role(final String[] clusterPermissions, final String[] indexPatterns, final String[] allowedActions) { + ObjectMapper objectMapper = DefaultObjectMapper.objectMapper; + // form cluster permissions + final ArrayNode clusterPermissionsArrayNode = objectMapper.createArrayNode(); + Arrays.stream(clusterPermissions).forEach(clusterPermissionsArrayNode::add); + + // form index_permissions + ArrayNode permissions = objectMapper.createArrayNode(); + Arrays.stream(allowedActions).forEach(permissions::add); // permission in v6 format + + ObjectNode permissionNode = objectMapper.createObjectNode(); + permissionNode.set("*", permissions); // type : "*" + + ObjectNode indexPermission = objectMapper.createObjectNode(); + indexPermission.set("*", permissionNode); // '*' -> all indices + + // add both to the role + ObjectNode role = objectMapper.createObjectNode(); + role.put("readonly", true); + role.set("cluster", clusterPermissionsArrayNode); + role.set("indices", indexPermission); + + return role; + } + + final ConfigModel configModel; + + public SecurityRolesPermissionsV6Test() throws IOException { + this.configModel = new ConfigModelV6( + createRolesConfig(), + createRoleMappingsConfig(), + createActionGroupsConfig(), + mock(DynamicConfigModel.class), + Settings.EMPTY + ); + } + + @Test + public void hasExplicitIndexPermission() { + IndexNameExpressionResolver resolver = mock(IndexNameExpressionResolver.class); + User user = new User("test"); + ClusterService cs = mock(ClusterService.class); + doReturn(createClusterState(new IndexShorthand(TEST_INDEX, IndexAbstraction.Type.ALIAS))).when(cs).state(); + IndexResolverReplacer.Resolved resolved = createResolved(TEST_INDEX); + + // test hasExplicitIndexPermission + final SecurityRoles securityRoleWithStarAccess = configModel.getSecurityRoles() + .filter(ImmutableSet.of("all_access_without_system_index_permission")); + user.addSecurityRoles(List.of("all_access_without_system_index_permission")); + + Assert.assertFalse( + "Should not allow system index access with * only", + securityRoleWithStarAccess.hasExplicitIndexPermission(resolved, user, new String[] {}, resolver, cs) + ); + + final SecurityRoles securityRoleWithExplicitAccess = configModel.getSecurityRoles() + .filter(ImmutableSet.of("has_system_index_permission")); + user.addSecurityRoles(List.of("has_system_index_permission")); + + Assert.assertTrue( + "Should allow system index access with explicit only", + securityRoleWithExplicitAccess.hasExplicitIndexPermission(resolved, user, new String[] {}, resolver, cs) + ); + } + + static SecurityDynamicConfiguration createRolesConfig() throws IOException { + final ObjectNode rolesNode = DefaultObjectMapper.objectMapper.createObjectNode(); + NO_EXPLICIT_SYSTEM_INDEX_PERMISSION.forEach(rolesNode::set); + HAS_SYSTEM_INDEX_PERMISSION.forEach(rolesNode::set); + return SecurityDynamicConfiguration.fromNode(rolesNode, CType.ROLES, 1, 0, 0); + } + + static SecurityDynamicConfiguration createRoleMappingsConfig() throws IOException { + final ObjectNode metaNode = DefaultObjectMapper.objectMapper.createObjectNode(); + return SecurityDynamicConfiguration.fromNode(metaNode, CType.ROLESMAPPING, 1, 0, 0); + } + + static SecurityDynamicConfiguration createActionGroupsConfig() throws IOException { + final ObjectNode metaNode = DefaultObjectMapper.objectMapper.createObjectNode(); + return SecurityDynamicConfiguration.fromNode(metaNode, CType.ACTIONGROUPS, 1, 0, 0); + } + + private IndexResolverReplacer.Resolved createResolved(final String... indexes) { + return new IndexResolverReplacer.Resolved( + ImmutableSet.of(), + ImmutableSet.copyOf(indexes), + ImmutableSet.copyOf(indexes), + ImmutableSet.of(), + IndicesOptions.STRICT_EXPAND_OPEN + ); + } + + private ClusterState createClusterState(final IndexShorthand... indices) { + final TreeMap indexMap = new TreeMap(); + Arrays.stream(indices).forEach(indexShorthand -> { + final IndexAbstraction indexAbstraction = mock(IndexAbstraction.class); + when(indexAbstraction.getType()).thenReturn(indexShorthand.type); + indexMap.put(indexShorthand.name, indexAbstraction); + }); + + final Metadata mockMetadata = mock(Metadata.class, withSettings().strictness(Strictness.LENIENT)); + when(mockMetadata.getIndicesLookup()).thenReturn(indexMap); + + final ClusterState mockClusterState = mock(ClusterState.class, withSettings().strictness(Strictness.LENIENT)); + when(mockClusterState.getMetadata()).thenReturn(mockMetadata); + + return mockClusterState; + } + + private class IndexShorthand { + public final String name; + public final IndexAbstraction.Type type; + + public IndexShorthand(final String name, final IndexAbstraction.Type type) { + this.name = name; + this.type = type; + } + } +} diff --git a/src/test/java/org/opensearch/security/system_indices/AbstractSystemIndicesTests.java b/src/test/java/org/opensearch/security/system_indices/AbstractSystemIndicesTests.java new file mode 100644 index 0000000000..5dcc050a37 --- /dev/null +++ b/src/test/java/org/opensearch/security/system_indices/AbstractSystemIndicesTests.java @@ -0,0 +1,193 @@ +/* + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.system_indices; + +import java.io.IOException; +import java.util.List; + +import org.apache.hc.core5.http.Header; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.opensearch.action.admin.cluster.repositories.put.PutRepositoryRequest; +import org.opensearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.LoggingDeprecationHandler; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.DynamicSecurityConfig; +import org.opensearch.security.test.SingleClusterTest; +import org.opensearch.security.test.helper.file.FileHelper; +import org.opensearch.security.test.helper.rest.RestHelper; +import static org.junit.Assert.assertEquals; + +/** + * Test for opendistro system indices, to restrict configured indices access to adminDn + * Refer: "plugins.security.system_indices.enabled" + * "plugins.security.system_indices.indices"; + */ + +public abstract class AbstractSystemIndicesTests extends SingleClusterTest { + + static final String ACCESSIBLE_ONLY_BY_SUPER_ADMIN = ".opendistro_security"; + static final String SYSTEM_INDEX_WITH_NO_ASSOCIATED_ROLE_PERMISSIONS = "random_system_index"; + static final List SYSTEM_INDICES = List.of( + ".system_index_1", + SYSTEM_INDEX_WITH_NO_ASSOCIATED_ROLE_PERMISSIONS, + ACCESSIBLE_ONLY_BY_SUPER_ADMIN + ); + + static final List INDICES_FOR_CREATE_REQUEST = List.of(".system_index_2"); + static final String matchAllQuery = "{\n\"query\": {\"match_all\": {}}}"; + static final String allAccessUser = "admin_all_access"; + static final Header allAccessUserHeader = encodeBasicHeader(allAccessUser, allAccessUser); + + static final String normalUser = "normal_user"; + static final Header normalUserHeader = encodeBasicHeader(normalUser, normalUser); + + static final String normalUserWithoutSystemIndex = "normal_user_without_system_index"; + static final Header normalUserWithoutSystemIndexHeader = encodeBasicHeader(normalUserWithoutSystemIndex, normalUserWithoutSystemIndex); + + static final String createIndexSettings = "{\n" + + " \"settings\" : {\n" + + " \"index\" : {\n" + + " \"number_of_shards\" : 3, \n" + + " \"number_of_replicas\" : 2 \n" + + " }\n" + + " }\n" + + + "}"; + static final String updateIndexSettings = "{\n" + " \"index\" : {\n" + " \"refresh_interval\" : null\n" + " }\n" + "}"; + static final String newMappings = "{\"properties\": {" + "\"user_name\": {" + "\"type\": \"text\"" + "}}}"; + + void setupWithSsl(boolean isSystemIndexEnabled, boolean isSystemIndexPermissionEnabled) throws Exception { + + Settings systemIndexSettings = Settings.builder() + .put(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, isSystemIndexEnabled) + .put(ConfigConstants.SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY, isSystemIndexPermissionEnabled) + .putList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, SYSTEM_INDICES) + .put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .put("path.repo", repositoryPath.getRoot().getAbsolutePath()) + .build(); + setup( + Settings.EMPTY, + new DynamicSecurityConfig().setConfig("system_indices/config.yml") + .setSecurityRoles("system_indices/roles.yml") + .setSecurityInternalUsers("system_indices/internal_users.yml") + .setSecurityRolesMapping("system_indices/roles_mapping.yml"), + systemIndexSettings, + true + ); + } + + /** + * Creates a set of test indices and indexes one document into each index. + * + */ + void createTestIndicesAndDocs() { + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + // security index is already created + if (!index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN)) { + tc.admin().indices().create(new CreateIndexRequest(index)).actionGet(); + } + tc.index( + new IndexRequest(index).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .id("document1") + .source("{ \"foo\": \"bar\" }", XContentType.JSON) + ).actionGet(); + } + } + } + + void createSnapshots() { + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + tc.admin() + .cluster() + .putRepository( + new PutRepositoryRequest(index).type("fs") + .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/" + index)) + ) + .actionGet(); + tc.admin() + .cluster() + .createSnapshot( + new CreateSnapshotRequest(index, index + "_1").indices(index).includeGlobalState(true).waitForCompletion(true) + ) + .actionGet(); + } + } + } + + RestHelper superAdminRestHelper() { + RestHelper restHelper = restHelper(); + restHelper.keystore = "kirk-keystore.jks"; + restHelper.enableHTTPClientSSL = true; + restHelper.trustHTTPServerCertificate = true; + restHelper.sendAdminCertificate = true; + return restHelper; + } + + RestHelper sslRestHelper() { + RestHelper restHelper = restHelper(); + restHelper.enableHTTPClientSSL = true; + return restHelper; + } + + void validateSearchResponse(RestHelper.HttpResponse response, int expectedHits) throws IOException { + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + + XContentParser xcp = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); + SearchResponse searchResponse = SearchResponse.fromXContent(xcp); + assertEquals(RestStatus.OK, searchResponse.status()); + assertEquals(expectedHits, searchResponse.getHits().getHits().length); + assertEquals(0, searchResponse.getFailedShards()); + assertEquals(5, searchResponse.getSuccessfulShards()); + } + + String permissionExceptionMessage(String action, String username) { + return "{\"type\":\"security_exception\",\"reason\":\"no permissions for [" + + action + + "] and User [name=" + + username + + ", backend_roles=[], requestedTenant=null]\"}"; + } + + void validateForbiddenResponse(RestHelper.HttpResponse response, String action, String user) { + assertEquals(RestStatus.FORBIDDEN.getStatus(), response.getStatusCode()); + MatcherAssert.assertThat(response.getBody(), Matchers.containsStringIgnoringCase(permissionExceptionMessage(action, user))); + } + + void shouldBeAllowedOnlyForAuthorizedIndices(String index, RestHelper.HttpResponse response, String action, String user) { + boolean isSecurityIndexRequest = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN); + boolean isRequestingAccessToNonAuthorizedSystemIndex = (!user.equals(allAccessUser) + && index.equals(SYSTEM_INDEX_WITH_NO_ASSOCIATED_ROLE_PERMISSIONS)); + if (isSecurityIndexRequest || isRequestingAccessToNonAuthorizedSystemIndex) { + validateForbiddenResponse(response, isSecurityIndexRequest ? "" : action, user); + } else { + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + } + } + +} diff --git a/src/test/java/org/opensearch/security/system_indices/SystemIndexDisabledTests.java b/src/test/java/org/opensearch/security/system_indices/SystemIndexDisabledTests.java new file mode 100644 index 0000000000..e85bddecb4 --- /dev/null +++ b/src/test/java/org/opensearch/security/system_indices/SystemIndexDisabledTests.java @@ -0,0 +1,398 @@ +/* + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.system_indices; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.junit.Before; +import org.junit.Test; +import org.opensearch.action.admin.indices.close.CloseIndexRequest; +import org.opensearch.client.Client; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.security.test.helper.rest.RestHelper; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Adds test for scenario when system index feature is disabled + */ +public class SystemIndexDisabledTests extends AbstractSystemIndicesTests { + + @Before + public void setup() throws Exception { + setupWithSsl(false, false); + createTestIndicesAndDocs(); + } + + /** + * SEARCH + */ + @Test + public void testSearchAsSuperAdmin() throws Exception { + RestHelper restHelper = superAdminRestHelper(); + + // search system indices + for (String index : SYSTEM_INDICES) { + int expectedHits = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) ? 10 : 1; + validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery), expectedHits); + } + + // search all indices + RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + } + + @Test + public void testSearchAsAdmin() throws Exception { + RestHelper restHelper = sslRestHelper(); + + // search system indices + for (String index : SYSTEM_INDICES) { + // security index remains accessible only by super-admin + int expectedHits = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) ? 0 : 1; + validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery, allAccessUserHeader), expectedHits); + } + + // search all indices + RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery, allAccessUserHeader); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + assertTrue(response.getBody().contains(SYSTEM_INDICES.get(0))); + assertFalse(response.getBody().contains(ACCESSIBLE_ONLY_BY_SUPER_ADMIN)); + } + + @Test + public void testSearchAsNormalUser() throws Exception { + testSearchWithUser(normalUser, normalUserHeader); + } + + @Test + public void testSearchAsNormalUserWithoutSystemIndexAccess() throws Exception { + testSearchWithUser(normalUserWithoutSystemIndex, normalUserWithoutSystemIndexHeader); + } + + private void testSearchWithUser(String user, Header header) throws IOException { + RestHelper restHelper = sslRestHelper(); + + // search system indices + for (String index : SYSTEM_INDICES) { + // security index is only accessible by super-admin + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_search", "", header); + if (index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) || index.startsWith(SYSTEM_INDEX_WITH_NO_ASSOCIATED_ROLE_PERMISSIONS)) { + validateForbiddenResponse(response, "indices:data/read/search", user); + } else { + validateSearchResponse(response, 1); + } + } + + // search all indices + RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", "", header); + assertEquals(RestStatus.FORBIDDEN.getStatus(), response.getStatusCode()); + validateForbiddenResponse(response, "indices:data/read/search", user); + } + + /** + * DELETE document + index + */ + @Test + public void testDeleteAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse responseDoc = restHelper.executeDeleteRequest(index + "/_doc/document1"); + assertEquals(RestStatus.OK.getStatus(), responseDoc.getStatusCode()); + + RestHelper.HttpResponse responseIndex = restHelper.executeDeleteRequest(index); + assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); + } + } + + @Test + public void testDeleteAsAdmin() { + testDeleteWithUser(allAccessUser, allAccessUserHeader, "", ""); + } + + @Test + public void testDeleteAsNormalUser() { + testDeleteWithUser(normalUser, normalUserHeader, "indices:admin/delete", "indices:data/write/delete"); + } + + @Test + public void testDeleteAsNormalUserWithoutSystemIndexAccess() { + testDeleteWithUser( + normalUserWithoutSystemIndex, + normalUserWithoutSystemIndexHeader, + "indices:admin/delete", + "indices:data/write/delete" + ); + } + + private void testDeleteWithUser(String user, Header header, String indexAction, String documentAction) { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executeDeleteRequest(index + "/_doc/document1", header); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, documentAction, user); + + response = restHelper.executeDeleteRequest(index, header); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, indexAction, user); + } + } + + /** + * CLOSE-OPEN + */ + @Test + public void testCloseOpenAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse responseClose = restHelper.executePostRequest(index + "/_close", ""); + assertEquals(RestStatus.OK.getStatus(), responseClose.getStatusCode()); + + RestHelper.HttpResponse responseOpen = restHelper.executePostRequest(index + "/_open", ""); + assertEquals(RestStatus.OK.getStatus(), responseOpen.getStatusCode()); + } + } + + @Test + public void testCloseOpenAsAdmin() { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_close", "", allAccessUserHeader); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", allAccessUser); + + // User can open the index but cannot close it + response = restHelper.executePostRequest(index + "/_open", "", allAccessUserHeader); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + } + } + + @Test + public void testCloseOpenAsNormalUser() { + testCloseOpenWithUser(normalUser, normalUserHeader); + } + + @Test + public void testCloseOpenAsNormalUserWithoutSystemIndexAccess() { + testCloseOpenWithUser(normalUserWithoutSystemIndex, normalUserWithoutSystemIndexHeader); + } + + private void testCloseOpenWithUser(String user, Header header) { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_close", "", header); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "indices:admin/close", user); + + // User can open the index but cannot close it + response = restHelper.executePostRequest(index + "/_open", "", header); + if (index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) || index.equals(SYSTEM_INDEX_WITH_NO_ASSOCIATED_ROLE_PERMISSIONS)) { + validateForbiddenResponse(response, "indices:admin/open", user); + } else { + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + } + } + } + + /** + * CREATE + */ + @Test + public void testCreateIndexAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : INDICES_FOR_CREATE_REQUEST) { + RestHelper.HttpResponse responseIndex = restHelper.executePutRequest(index, createIndexSettings); + assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); + + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_doc", "{\"foo\": \"bar\"}"); + assertEquals(RestStatus.CREATED.getStatus(), response.getStatusCode()); + } + } + + @Test + public void testCreateIndexAsAdmin() { + testCreateIndexWithUser(allAccessUserHeader); + } + + @Test + public void testCreateIndexAsNormalUser() { + testCreateIndexWithUser(normalUserHeader); + } + + @Test + public void testCreateIndexAsNormalUserWithoutSystemIndexAccess() { + testCreateIndexWithUser(normalUserWithoutSystemIndexHeader); + } + + private void testCreateIndexWithUser(Header header) { + RestHelper restHelper = sslRestHelper(); + + for (String index : INDICES_FOR_CREATE_REQUEST) { + RestHelper.HttpResponse response = restHelper.executePutRequest(index, createIndexSettings, header); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + + response = restHelper.executePostRequest(index + "/_doc", "{\"foo\": \"bar\"}", header); + assertEquals(RestStatus.CREATED.getStatus(), response.getStatusCode()); + } + } + + /** + * UPDATE settings + mappings + */ + @Test + public void testUpdateAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePutRequest(index + "/_settings", updateIndexSettings); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + + response = restHelper.executePutRequest(index + "/_mapping", newMappings); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + } + } + + @Test + public void testUpdateMappingsAsAdmin() { + testUpdateWithUser(allAccessUser, allAccessUserHeader); + } + + @Test + public void testUpdateAsNormalUser() { + testUpdateWithUser(normalUser, normalUserHeader); + } + + @Test + public void testUpdateAsNormalUserWithoutSystemIndexAccess() { + testUpdateWithUser(normalUserWithoutSystemIndex, normalUserWithoutSystemIndexHeader); + } + + private void testUpdateWithUser(String user, Header header) { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePutRequest(index + "/_mapping", newMappings, header); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "indices:admin/mapping/put", user); + + response = restHelper.executePutRequest(index + "/_settings", updateIndexSettings, header); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "indices:admin/settings/update", user); + } + } + + /** + * SNAPSHOT get + restore + */ + @Test + public void testSnapshotSystemIndicesAsSuperAdmin() { + createSnapshots(); + + RestHelper restHelper = superAdminRestHelper(); + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + tc.admin().indices().close(new CloseIndexRequest(index)).actionGet(); + } + } + + for (String index : SYSTEM_INDICES) { + assertEquals(HttpStatus.SC_OK, restHelper.executeGetRequest("_snapshot/" + index + "/" + index + "_1").getStatusCode()); + assertEquals( + HttpStatus.SC_OK, + restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "", + allAccessUserHeader + ).getStatusCode() + ); + assertEquals( + HttpStatus.SC_OK, + restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + allAccessUserHeader + ).getStatusCode() + ); + } + } + + @Test + public void testSnapshotSystemIndicesAsAdmin() { + createSnapshots(); + + RestHelper restHelper = sslRestHelper(); + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + tc.admin().indices().close(new CloseIndexRequest(index)).actionGet(); + } + } + + for (String index : SYSTEM_INDICES) { + String snapshotRequest = "_snapshot/" + index + "/" + index + "_1"; + RestHelper.HttpResponse res = restHelper.executeGetRequest(snapshotRequest); + assertEquals(HttpStatus.SC_UNAUTHORIZED, res.getStatusCode()); + + res = restHelper.executePostRequest(snapshotRequest + "/_restore?wait_for_completion=true", "", allAccessUserHeader); + shouldBeAllowedOnlyForAuthorizedIndices(index, res, "", allAccessUser); + + res = restHelper.executePostRequest( + snapshotRequest + "/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + allAccessUserHeader + ); + shouldBeAllowedOnlyForAuthorizedIndices(index, res, "", allAccessUser); + } + } + + @Test + public void testSnapshotSystemIndicesAsNormalUser() { + testSnapshotWithUser(normalUser, normalUserHeader); + } + + @Test + public void testSnapshotSystemIndicesAsNormalUserWithoutSystemIndexAccess() { + testSnapshotWithUser(normalUserWithoutSystemIndex, normalUserWithoutSystemIndexHeader); + } + + private void testSnapshotWithUser(String user, Header header) { + createSnapshots(); + + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + tc.admin().indices().close(new CloseIndexRequest(index)).actionGet(); + } + } + + RestHelper restHelper = sslRestHelper(); + for (String index : SYSTEM_INDICES) { + String snapshotRequest = "_snapshot/" + index + "/" + index + "_1"; + RestHelper.HttpResponse res = restHelper.executeGetRequest(snapshotRequest); + assertEquals(HttpStatus.SC_UNAUTHORIZED, res.getStatusCode()); + + String action = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) ? "" : "indices:data/write/index, indices:admin/create"; + + res = restHelper.executePostRequest(snapshotRequest + "/_restore?wait_for_completion=true", "", header); + shouldBeAllowedOnlyForAuthorizedIndices(index, res, action, user); + + res = restHelper.executePostRequest( + snapshotRequest + "/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + header + ); + validateForbiddenResponse(res, action, user); + } + } +} diff --git a/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionDisabledTests.java b/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionDisabledTests.java new file mode 100644 index 0000000000..c3feb70b98 --- /dev/null +++ b/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionDisabledTests.java @@ -0,0 +1,397 @@ +/* + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.system_indices; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.junit.Before; +import org.junit.Test; +import org.opensearch.action.admin.indices.close.CloseIndexRequest; +import org.opensearch.client.Client; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.security.test.helper.rest.RestHelper; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * Adds test for scenario when system index feature is enabled, but system index permission feature is disabled + */ +public class SystemIndexPermissionDisabledTests extends AbstractSystemIndicesTests { + + @Before + public void setup() throws Exception { + setupWithSsl(true, false); + createTestIndicesAndDocs(); + } + + /** + * SEARCH + */ + @Test + public void testSearchAsSuperAdmin() throws Exception { + RestHelper restHelper = superAdminRestHelper(); + + // search system indices + for (String index : SYSTEM_INDICES) { + int expectedHits = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) ? 10 : 1; + validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery), expectedHits); + } + + // search all indices + RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + } + + @Test + public void testSearchAsAdmin() throws Exception { + RestHelper restHelper = sslRestHelper(); + + // search system indices + for (String index : SYSTEM_INDICES) { + // no system indices are searchable by admin + validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery, allAccessUserHeader), 0); + } + + // search all indices + RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery, allAccessUserHeader); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + assertFalse(response.getBody().contains(SYSTEM_INDICES.get(0))); + assertFalse(response.getBody().contains(ACCESSIBLE_ONLY_BY_SUPER_ADMIN)); + } + + @Test + public void testSearchAsNormalUser() throws Exception { + testSearchWithUser(normalUser, normalUserHeader); + } + + @Test + public void testSearchAsNormalUserWithoutSystemIndexAccess() throws Exception { + testSearchWithUser(normalUserWithoutSystemIndex, normalUserWithoutSystemIndexHeader); + } + + private void testSearchWithUser(String user, Header header) throws IOException { + RestHelper restHelper = sslRestHelper(); + + // search system indices + for (String index : SYSTEM_INDICES) { + // security index is only accessible by super-admin + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_search", "", header); + if (index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) || index.equals(SYSTEM_INDEX_WITH_NO_ASSOCIATED_ROLE_PERMISSIONS)) { + validateForbiddenResponse(response, "indices:data/read/search", user); + } else { + // got 0 hits because system index permissions are not enabled + validateSearchResponse(response, 0); + } + } + + // search all indices + RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", "", header); + assertEquals(RestStatus.FORBIDDEN.getStatus(), response.getStatusCode()); + validateForbiddenResponse(response, "indices:data/read/search", user); + } + + /** + * DELETE document + index + */ + @Test + public void testDeleteAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse responseDoc = restHelper.executeDeleteRequest(index + "/_doc/document1"); + assertEquals(RestStatus.OK.getStatus(), responseDoc.getStatusCode()); + + RestHelper.HttpResponse responseIndex = restHelper.executeDeleteRequest(index); + assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); + } + } + + @Test + public void testDeleteAsAdmin() { + testDeleteWithUser(allAccessUser, allAccessUserHeader); + } + + @Test + public void testDeleteAsNormalUser() { + testDeleteWithUser(normalUser, normalUserHeader); + } + + @Test + public void testDeleteAsNormalUserWithoutSystemIndexAccess() { + testDeleteWithUser(normalUserWithoutSystemIndex, normalUserWithoutSystemIndexHeader); + } + + private void testDeleteWithUser(String user, Header header) { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executeDeleteRequest(index + "/_doc/document1", header); + validateForbiddenResponse(response, "", user); + + response = restHelper.executeDeleteRequest(index, header); + validateForbiddenResponse(response, "", user); + } + } + + /** + * CLOSE-OPEN + */ + @Test + public void testCloseOpenAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse responseClose = restHelper.executePostRequest(index + "/_close", ""); + assertEquals(RestStatus.OK.getStatus(), responseClose.getStatusCode()); + + RestHelper.HttpResponse responseOpen = restHelper.executePostRequest(index + "/_open", ""); + assertEquals(RestStatus.OK.getStatus(), responseOpen.getStatusCode()); + } + } + + @Test + public void testCloseOpenAsAdmin() { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_close", "", allAccessUserHeader); + validateForbiddenResponse(response, "", allAccessUser); + + // admin cannot close any system index but can open them + response = restHelper.executePostRequest(index + "/_open", "", allAccessUserHeader); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + } + } + + @Test + public void testCloseOpenAsNormalUser() { + testCloseOpenWithUser(normalUser, normalUserHeader); + } + + @Test + public void testCloseOpenAsNormalUserWithoutSystemIndexAccess() { + testCloseOpenWithUser(normalUserWithoutSystemIndex, normalUserWithoutSystemIndexHeader); + } + + private void testCloseOpenWithUser(String user, Header header) { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_close", "", header); + validateForbiddenResponse(response, "", user); + + // normal user cannot open or close security index + response = restHelper.executePostRequest(index + "/_open", "", header); + if (index.startsWith(".system")) { + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + } else { + validateForbiddenResponse(response, "indices:admin/open", user); + } + } + } + + /** + * CREATE + * should be allowed as any user + */ + @Test + public void testCreateIndexAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : INDICES_FOR_CREATE_REQUEST) { + RestHelper.HttpResponse responseIndex = restHelper.executePutRequest(index, createIndexSettings); + assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); + + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_doc", "{\"foo\": \"bar\"}"); + assertEquals(RestStatus.CREATED.getStatus(), response.getStatusCode()); + } + } + + @Test + public void testCreateIndexAsAdmin() { + testCreateIndexWithUser(allAccessUserHeader); + } + + @Test + public void testCreateIndexAsNormalUser() { + testCreateIndexWithUser(normalUserHeader); + } + + @Test + public void testCreateIndexAsNormalUserWithoutSystemIndexAccess() { + testCreateIndexWithUser(normalUserWithoutSystemIndexHeader); + } + + private void testCreateIndexWithUser(Header header) { + RestHelper restHelper = sslRestHelper(); + + for (String index : INDICES_FOR_CREATE_REQUEST) { + RestHelper.HttpResponse response = restHelper.executePutRequest(index, createIndexSettings, header); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + + response = restHelper.executePostRequest(index + "/_doc", "{\"foo\": \"bar\"}", header); + assertEquals(RestStatus.CREATED.getStatus(), response.getStatusCode()); + } + } + + /** + * UPDATE settings + mappings + */ + @Test + public void testUpdateAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePutRequest(index + "/_settings", updateIndexSettings); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + + response = restHelper.executePutRequest(index + "/_mapping", newMappings); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + } + } + + @Test + public void testUpdateMappingsAsAdmin() { + testUpdateWithUser(allAccessUser, allAccessUserHeader); + } + + @Test + public void testUpdateAsNormalUser() { + testUpdateWithUser(normalUser, normalUserHeader); + } + + @Test + public void testUpdateAsNormalUserWithoutSystemIndexAccess() { + testUpdateWithUser(normalUserWithoutSystemIndex, normalUserWithoutSystemIndexHeader); + } + + private void testUpdateWithUser(String user, Header header) { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePutRequest(index + "/_mapping", newMappings, header); + validateForbiddenResponse(response, "", user); + + response = restHelper.executePutRequest(index + "/_settings", updateIndexSettings, header); + validateForbiddenResponse(response, "", user); + } + } + + /** + * SNAPSHOT get + restore + */ + @Test + public void testSnapshotSystemIndicesAsSuperAdmin() { + createSnapshots(); + + RestHelper restHelper = superAdminRestHelper(); + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + tc.admin().indices().close(new CloseIndexRequest(index)).actionGet(); + } + } + + for (String index : SYSTEM_INDICES) { + assertEquals(HttpStatus.SC_OK, restHelper.executeGetRequest("_snapshot/" + index + "/" + index + "_1").getStatusCode()); + assertEquals( + HttpStatus.SC_OK, + restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "", + allAccessUserHeader + ).getStatusCode() + ); + assertEquals( + HttpStatus.SC_OK, + restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + allAccessUserHeader + ).getStatusCode() + ); + } + } + + @Test + public void testSnapshotSystemIndicesAsAdmin() { + createSnapshots(); + + RestHelper restHelper = sslRestHelper(); + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + tc.admin().indices().close(new CloseIndexRequest(index)).actionGet(); + } + } + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse res = restHelper.executeGetRequest("_snapshot/" + index + "/" + index + "_1"); + assertEquals(HttpStatus.SC_UNAUTHORIZED, res.getStatusCode()); + + res = restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "", + allAccessUserHeader + ); + validateForbiddenResponse(res, "", allAccessUser); + + res = restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + allAccessUserHeader + ); + shouldBeAllowedOnlyForAuthorizedIndices(index, res, "", allAccessUser); + } + } + + @Test + public void testSnapshotSystemIndicesAsNormalUser() { + testSnapshotSystemIndexWithUser(normalUser, normalUserHeader); + } + + @Test + public void testSnapshotSystemIndicesAsNormalUserWithoutSystemIndexAccess() { + testSnapshotSystemIndexWithUser(normalUserWithoutSystemIndex, normalUserWithoutSystemIndexHeader); + } + + private void testSnapshotSystemIndexWithUser(String user, Header header) { + createSnapshots(); + + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + tc.admin().indices().close(new CloseIndexRequest(index)).actionGet(); + } + } + + RestHelper restHelper = sslRestHelper(); + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse res = restHelper.executeGetRequest("_snapshot/" + index + "/" + index + "_1"); + assertEquals(HttpStatus.SC_UNAUTHORIZED, res.getStatusCode()); + + res = restHelper.executePostRequest("_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", "", header); + validateForbiddenResponse(res, "", user); + + res = restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + header + ); + if (index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN)) { + validateForbiddenResponse(res, "", user); + } else { + validateForbiddenResponse(res, "indices:data/write/index, indices:admin/create", user); + } + } + } +} diff --git a/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionEnabledTests.java b/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionEnabledTests.java new file mode 100644 index 0000000000..b9630011cc --- /dev/null +++ b/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionEnabledTests.java @@ -0,0 +1,446 @@ +/* + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.system_indices; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.junit.Before; +import org.junit.Test; +import org.opensearch.action.admin.indices.close.CloseIndexRequest; +import org.opensearch.client.Client; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.security.test.helper.rest.RestHelper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class SystemIndexPermissionEnabledTests extends AbstractSystemIndicesTests { + + @Before + public void setup() throws Exception { + setupWithSsl(true, true); + createTestIndicesAndDocs(); + } + + /** + * SEARCH + */ + @Test + public void testSearchAsSuperAdmin() throws Exception { + RestHelper restHelper = superAdminRestHelper(); + + // search system indices + for (String index : SYSTEM_INDICES) { + int expectedHits = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) ? 10 : 1; + validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery), expectedHits); + } + + // search all indices + RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + } + + @Test + public void testSearchAsAdmin() { + RestHelper restHelper = sslRestHelper(); + + // search system indices + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_search", matchAllQuery, allAccessUserHeader); + // no system indices are searchable by admin + validateForbiddenResponse(response, "", allAccessUser); + } + + // search all indices + RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery, allAccessUserHeader); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + assertFalse(response.getBody().contains(SYSTEM_INDICES.get(0))); + assertFalse(response.getBody().contains(ACCESSIBLE_ONLY_BY_SUPER_ADMIN)); + } + + @Test + public void testSearchAsNormalUser() throws Exception { + RestHelper restHelper = sslRestHelper(); + + // search system indices + for (String index : SYSTEM_INDICES) { + // security index is only accessible by super-admin + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_search", "", normalUserHeader); + if (index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) || index.equals(SYSTEM_INDEX_WITH_NO_ASSOCIATED_ROLE_PERMISSIONS)) { + validateForbiddenResponse(response, "", normalUser); + } else { + validateSearchResponse(response, 0); + } + } + + // search all indices + RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", "", normalUserHeader); + assertEquals(RestStatus.FORBIDDEN.getStatus(), response.getStatusCode()); + validateForbiddenResponse(response, "indices:data/read/search", normalUser); + } + + @Test + public void testSearchAsNormalUserWithoutSystemIndexAccess() { + RestHelper restHelper = sslRestHelper(); + + // search system indices + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_search", "", normalUserWithoutSystemIndexHeader); + validateForbiddenResponse(response, "", normalUserWithoutSystemIndex); + } + + // search all indices + RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", "", normalUserWithoutSystemIndexHeader); + assertEquals(RestStatus.FORBIDDEN.getStatus(), response.getStatusCode()); + validateForbiddenResponse(response, "indices:data/read/search", normalUserWithoutSystemIndex); + } + + /** + * DELETE document + index + */ + @Test + public void testDeleteAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse responseDoc = restHelper.executeDeleteRequest(index + "/_doc/document1"); + assertEquals(RestStatus.OK.getStatus(), responseDoc.getStatusCode()); + + RestHelper.HttpResponse responseIndex = restHelper.executeDeleteRequest(index); + assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); + } + } + + @Test + public void testDeleteAsAdmin() { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executeDeleteRequest(index + "/_doc/document1", allAccessUserHeader); + validateForbiddenResponse(response, "", allAccessUser); + + response = restHelper.executeDeleteRequest(index, allAccessUserHeader); + validateForbiddenResponse(response, "", allAccessUser); + } + } + + @Test + public void testDeleteAsNormalUser() { + RestHelper restHelper = sslRestHelper(); + + // allows interacting with the index it has access to: `.system_index_1` with `.system*` pattern and `system:admin/system_index` + // permission + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executeDeleteRequest(index + "/_doc/document1", normalUserHeader); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + + response = restHelper.executeDeleteRequest(index, normalUserHeader); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + } + } + + @Test + public void testDeleteAsNormalUserWithoutSystemIndexAccess() { + RestHelper restHelper = sslRestHelper(); + + // does not allow interaction with any system index as it doesn't have the permission + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executeDeleteRequest( + index + "/_doc/document1", + normalUserWithoutSystemIndexHeader + ); + validateForbiddenResponse(response, "", normalUserWithoutSystemIndex); + + response = restHelper.executeDeleteRequest(index, normalUserWithoutSystemIndexHeader); + validateForbiddenResponse(response, "", normalUserWithoutSystemIndex); + } + } + + /** + * CLOSE-OPEN + */ + @Test + public void testCloseOpenAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse responseClose = restHelper.executePostRequest(index + "/_close", ""); + assertEquals(RestStatus.OK.getStatus(), responseClose.getStatusCode()); + + RestHelper.HttpResponse responseOpen = restHelper.executePostRequest(index + "/_open", ""); + assertEquals(RestStatus.OK.getStatus(), responseOpen.getStatusCode()); + } + } + + @Test + public void testCloseOpenAsAdmin() { + testCloseOpenWithUser(allAccessUser, allAccessUserHeader); + } + + @Test + public void testCloseOpenAsNormalUser() { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_close", "", normalUserHeader); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + + // normal user cannot open or close security index + response = restHelper.executePostRequest(index + "/_open", "", normalUserHeader); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + } + } + + @Test + public void testCloseOpenAsNormalUserWithoutSystemIndexAccess() { + testCloseOpenWithUser(normalUserWithoutSystemIndex, normalUserWithoutSystemIndexHeader); + } + + private void testCloseOpenWithUser(String user, Header header) { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_close", "", header); + validateForbiddenResponse(response, "", user); + + // admin or normal user (without system index permission) cannot open or close any system index + response = restHelper.executePostRequest(index + "/_open", "", header); + validateForbiddenResponse(response, "", user); + } + } + + /** + * CREATE + * should be allowed as any user + */ + @Test + public void testCreateIndexAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : INDICES_FOR_CREATE_REQUEST) { + RestHelper.HttpResponse responseIndex = restHelper.executePutRequest(index, createIndexSettings); + assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); + + RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_doc", "{\"foo\": \"bar\"}"); + assertEquals(RestStatus.CREATED.getStatus(), response.getStatusCode()); + } + } + + @Test + public void testCreateIndexAsAdmin() { + testCreateIndexWithUser(allAccessUserHeader); + } + + @Test + public void testCreateIndexAsNormalUser() { + testCreateIndexWithUser(normalUserHeader); + } + + @Test + public void testCreateIndexAsNormalUserWithoutSystemIndexAccess() { + testCreateIndexWithUser(normalUserWithoutSystemIndexHeader); + } + + private void testCreateIndexWithUser(Header header) { + RestHelper restHelper = sslRestHelper(); + + for (String index : INDICES_FOR_CREATE_REQUEST) { + RestHelper.HttpResponse response = restHelper.executePutRequest(index, createIndexSettings, header); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + + response = restHelper.executePostRequest(index + "/_doc", "{\"foo\": \"bar\"}", header); + assertEquals(RestStatus.CREATED.getStatus(), response.getStatusCode()); + } + } + + /** + * UPDATE settings + mappings + */ + @Test + public void testUpdateAsSuperAdmin() { + RestHelper restHelper = superAdminRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePutRequest(index + "/_settings", updateIndexSettings); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + + response = restHelper.executePutRequest(index + "/_mapping", newMappings); + assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); + } + } + + @Test + public void testUpdateAsAdmin() { + testUpdateWithUser(allAccessUser, allAccessUserHeader); + } + + @Test + public void testUpdateAsNormalUser() { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePutRequest(index + "/_settings", updateIndexSettings, normalUserHeader); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + + response = restHelper.executePutRequest(index + "/_mapping", newMappings, normalUserHeader); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + } + } + + @Test + public void testUpdateAsNormalUserWithoutSystemIndexAccess() { + testUpdateWithUser(normalUserWithoutSystemIndex, normalUserWithoutSystemIndexHeader); + } + + private void testUpdateWithUser(String user, Header header) { + RestHelper restHelper = sslRestHelper(); + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse response = restHelper.executePutRequest(index + "/_settings", updateIndexSettings, header); + validateForbiddenResponse(response, "", user); + + response = restHelper.executePutRequest(index + "/_mapping", newMappings, header); + validateForbiddenResponse(response, "", user); + } + } + + /** + * SNAPSHOT get + restore + */ + @Test + public void testSnapshotSystemIndicesAsSuperAdmin() { + createSnapshots(); + + RestHelper restHelper = superAdminRestHelper(); + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + tc.admin().indices().close(new CloseIndexRequest(index)).actionGet(); + } + } + + for (String index : SYSTEM_INDICES) { + assertEquals(HttpStatus.SC_OK, restHelper.executeGetRequest("_snapshot/" + index + "/" + index + "_1").getStatusCode()); + assertEquals( + HttpStatus.SC_OK, + restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "", + allAccessUserHeader + ).getStatusCode() + ); + assertEquals( + HttpStatus.SC_OK, + restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + allAccessUserHeader + ).getStatusCode() + ); + } + } + + @Test + public void testSnapshotSystemIndicesAsAdmin() { + createSnapshots(); + + RestHelper restHelper = sslRestHelper(); + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + tc.admin().indices().close(new CloseIndexRequest(index)).actionGet(); + } + } + + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse res = restHelper.executeGetRequest("_snapshot/" + index + "/" + index + "_1"); + assertEquals(HttpStatus.SC_UNAUTHORIZED, res.getStatusCode()); + + res = restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "", + allAccessUserHeader + ); + validateForbiddenResponse(res, "", allAccessUser); + + res = restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + allAccessUserHeader + ); + shouldBeAllowedOnlyForAuthorizedIndices(index, res, "", allAccessUser); + } + } + + @Test + public void testSnapshotSystemIndicesAsNormalUser() { + createSnapshots(); + + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + tc.admin().indices().close(new CloseIndexRequest(index)).actionGet(); + } + } + + RestHelper restHelper = sslRestHelper(); + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse res = restHelper.executeGetRequest("_snapshot/" + index + "/" + index + "_1"); + assertEquals(HttpStatus.SC_UNAUTHORIZED, res.getStatusCode()); + + res = restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "", + normalUserHeader + ); + shouldBeAllowedOnlyForAuthorizedIndices(index, res, "", normalUser); + + res = restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + normalUserHeader + ); + + String action = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) ? "" : "indices:data/write/index, indices:admin/create"; + validateForbiddenResponse(res, action, normalUser); + } + } + + @Test + public void testSnapshotSystemIndicesAsNormalUserWithoutSystemIndexAccess() { + createSnapshots(); + + try (Client tc = getClient()) { + for (String index : SYSTEM_INDICES) { + tc.admin().indices().close(new CloseIndexRequest(index)).actionGet(); + } + } + + RestHelper restHelper = sslRestHelper(); + for (String index : SYSTEM_INDICES) { + RestHelper.HttpResponse res = restHelper.executeGetRequest("_snapshot/" + index + "/" + index + "_1"); + assertEquals(HttpStatus.SC_UNAUTHORIZED, res.getStatusCode()); + + res = restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "", + normalUserWithoutSystemIndexHeader + ); + validateForbiddenResponse(res, "", normalUserWithoutSystemIndex); + + res = restHelper.executePostRequest( + "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", + "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", + normalUserWithoutSystemIndexHeader + ); + String action = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) ? "" : "indices:data/write/index, indices:admin/create"; + validateForbiddenResponse(res, action, normalUserWithoutSystemIndex); + } + } +} diff --git a/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java b/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java deleted file mode 100644 index b6f3254a85..0000000000 --- a/src/test/java/org/opensearch/security/system_indices/SystemIndicesTests.java +++ /dev/null @@ -1,569 +0,0 @@ -/* - * 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. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.system_indices; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import org.apache.hc.core5.http.Header; -import org.apache.hc.core5.http.HttpStatus; -import org.junit.Test; - -import org.opensearch.action.admin.cluster.repositories.put.PutRepositoryRequest; -import org.opensearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; -import org.opensearch.action.admin.indices.close.CloseIndexRequest; -import org.opensearch.action.admin.indices.create.CreateIndexRequest; -import org.opensearch.action.index.IndexRequest; -import org.opensearch.action.search.SearchResponse; -import org.opensearch.action.support.WriteRequest; -import org.opensearch.client.Client; -import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.LoggingDeprecationHandler; -import org.opensearch.common.xcontent.XContentType; -import org.opensearch.core.xcontent.NamedXContentRegistry; -import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.core.rest.RestStatus; -import org.opensearch.security.support.ConfigConstants; -import org.opensearch.security.test.DynamicSecurityConfig; -import org.opensearch.security.test.SingleClusterTest; -import org.opensearch.security.test.helper.file.FileHelper; -import org.opensearch.security.test.helper.rest.RestHelper; - -import static junit.framework.TestCase.assertTrue; -import static org.junit.Assert.assertEquals; - -/** - * Test for opendistro system indices, to restrict configured indices access to adminDn - * Refer: "plugins.security.system_indices.enabled" - * "plugins.security.system_indices.indices"; - */ -public class SystemIndicesTests extends SingleClusterTest { - - private static final List listOfIndexesToTest = Arrays.asList("config1", "config2"); - private static final String matchAllQuery = "{\n\"query\": {\"match_all\": {}}}"; - private static final String allAccessUser = "admin_all_access"; - private static final Header allAccessUserHeader = encodeBasicHeader(allAccessUser, allAccessUser); - private static final String generalErrorMessage = String.format( - "no permissions for [] and User [name=%s, backend_roles=[], requestedTenant=null]", - allAccessUser - ); - - private void setupSystemIndicesDisabledWithSsl() throws Exception { - - Settings systemIndexSettings = Settings.builder() - .put(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, false) - .putList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, listOfIndexesToTest) - .put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .put("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .build(); - setup( - Settings.EMPTY, - new DynamicSecurityConfig().setConfig("config_system_indices.yml") - .setSecurityRoles("roles_system_indices.yml") - .setSecurityInternalUsers("internal_users_system_indices.yml") - .setSecurityRolesMapping("roles_mapping_system_indices.yml"), - systemIndexSettings, - true - ); - } - - private void setupSystemIndicesEnabledWithSsl() throws Exception { - - Settings systemIndexSettings = Settings.builder() - .put(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, true) - .putList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, listOfIndexesToTest) - .put("plugins.security.ssl.http.enabled", true) - .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) - .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) - .put("path.repo", repositoryPath.getRoot().getAbsolutePath()) - .build(); - setup( - Settings.EMPTY, - new DynamicSecurityConfig().setConfig("config_system_indices.yml") - .setSecurityRoles("roles_system_indices.yml") - .setSecurityInternalUsers("internal_users_system_indices.yml") - .setSecurityRolesMapping("roles_mapping_system_indices.yml"), - systemIndexSettings, - true - ); - } - - /** - * Creates a set of test indices and indexes one document into each index. - * - * @throws Exception - */ - private void createTestIndicesAndDocs() { - try (Client tc = getClient()) { - for (String index : listOfIndexesToTest) { - tc.admin().indices().create(new CreateIndexRequest(index)).actionGet(); - tc.index( - new IndexRequest(index).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) - .id("document1") - .source("{ \"foo\": \"bar\" }", XContentType.JSON) - ).actionGet(); - } - } - } - - private void createSnapshots() { - try (Client tc = getClient()) { - for (String index : listOfIndexesToTest) { - tc.admin() - .cluster() - .putRepository( - new PutRepositoryRequest(index).type("fs") - .settings(Settings.builder().put("location", repositoryPath.getRoot().getAbsolutePath() + "/" + index)) - ) - .actionGet(); - tc.admin() - .cluster() - .createSnapshot( - new CreateSnapshotRequest(index, index + "_1").indices(index).includeGlobalState(true).waitForCompletion(true) - ) - .actionGet(); - } - } - } - - private RestHelper keyStoreRestHelper() { - RestHelper restHelper = restHelper(); - restHelper.keystore = "kirk-keystore.jks"; - restHelper.enableHTTPClientSSL = true; - restHelper.trustHTTPServerCertificate = true; - restHelper.sendAdminCertificate = true; - return restHelper; - } - - private RestHelper sslRestHelper() { - RestHelper restHelper = restHelper(); - restHelper.enableHTTPClientSSL = true; - return restHelper; - } - - /*************************************************************************************************************************** - * Search api tests. Search is a special case. - ***************************************************************************************************************************/ - - private void validateSearchResponse(RestHelper.HttpResponse response, int expectecdHits) throws IOException { - assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - - XContentParser xcp = XContentType.JSON.xContent() - .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); - SearchResponse searchResponse = SearchResponse.fromXContent(xcp); - assertEquals(RestStatus.OK, searchResponse.status()); - assertEquals(expectecdHits, searchResponse.getHits().getHits().length); - assertEquals(0, searchResponse.getFailedShards()); - assertEquals(5, searchResponse.getSuccessfulShards()); - } - - @Test - public void testSearchAsSuperAdmin() throws Exception { - setupSystemIndicesDisabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper restHelper = keyStoreRestHelper(); - - // search system indices - for (String index : listOfIndexesToTest) { - validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery), 1); - } - - // search all indices - RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery); - assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - } - - @Test - public void testSearchAsAdmin() throws Exception { - setupSystemIndicesDisabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper restHelper = sslRestHelper(); - - // search system indices - for (String index : listOfIndexesToTest) { - validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery, allAccessUserHeader), 1); - } - - // search all indices - RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery, allAccessUserHeader); - assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - } - - @Test - public void testSearchWithSystemIndicesAsSuperAdmin() throws Exception { - setupSystemIndicesEnabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper restHelper = keyStoreRestHelper(); - - // search system indices - for (String index : listOfIndexesToTest) { - validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery), 1); - } - - // search all indices - RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery); - assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - } - - @Test - public void testSearchWithSystemIndicesAsAdmin() throws Exception { - setupSystemIndicesEnabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper restHelper = sslRestHelper(); - - for (String index : listOfIndexesToTest) { - validateSearchResponse(restHelper.executePostRequest(index + "/_search", matchAllQuery, allAccessUserHeader), 0); - } - - // search all indices - RestHelper.HttpResponse response = restHelper.executePostRequest("/_search", matchAllQuery, allAccessUserHeader); - assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - XContentParser xcp = XContentType.JSON.xContent() - .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getBody()); - SearchResponse searchResponse = SearchResponse.fromXContent(xcp); - assertEquals(RestStatus.OK, searchResponse.status()); - assertEquals(0, searchResponse.getHits().getHits().length); - } - - /*************************************************************************************************************************** - * Delete index and Delete doc - ***************************************************************************************************************************/ - - @Test - public void testDelete() throws Exception { - setupSystemIndicesDisabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper keyStoreRestHelper = keyStoreRestHelper(); - RestHelper sslRestHelper = sslRestHelper(); - - // as super-admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseDoc = keyStoreRestHelper.executeDeleteRequest(index + "/_doc/document1"); - assertEquals(RestStatus.OK.getStatus(), responseDoc.getStatusCode()); - - RestHelper.HttpResponse responseIndex = keyStoreRestHelper.executeDeleteRequest(index); - assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); - } - createTestIndicesAndDocs(); - - // as admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseDoc = sslRestHelper.executeDeleteRequest(index + "/_doc/document1", allAccessUserHeader); - assertEquals(RestStatus.OK.getStatus(), responseDoc.getStatusCode()); - - RestHelper.HttpResponse responseIndex = sslRestHelper.executeDeleteRequest(index, allAccessUserHeader); - assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); - } - } - - @Test - public void testDeleteWithSystemIndices() throws Exception { - setupSystemIndicesEnabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper keyStoreRestHelper = keyStoreRestHelper(); - RestHelper sslRestHelper = sslRestHelper(); - - // as super-admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseDoc = keyStoreRestHelper.executeDeleteRequest(index + "/_doc/document1"); - assertEquals(RestStatus.OK.getStatus(), responseDoc.getStatusCode()); - - RestHelper.HttpResponse responseIndex = keyStoreRestHelper.executeDeleteRequest(index); - assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); - } - createTestIndicesAndDocs(); - - // as admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseDoc = sslRestHelper.executeDeleteRequest(index + "/_doc/document1", allAccessUserHeader); - assertEquals(RestStatus.FORBIDDEN.getStatus(), responseDoc.getStatusCode()); - - RestHelper.HttpResponse responseIndex = sslRestHelper.executeDeleteRequest(index, allAccessUserHeader); - assertEquals(RestStatus.FORBIDDEN.getStatus(), responseIndex.getStatusCode()); - } - } - - /*************************************************************************************************************************** - * open and close index - ***************************************************************************************************************************/ - - @Test - public void testCloseOpen() throws Exception { - setupSystemIndicesDisabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper keyStoreRestHelper = keyStoreRestHelper(); - RestHelper sslRestHelper = sslRestHelper(); - - // as super-admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseClose = keyStoreRestHelper.executePostRequest(index + "/_close", ""); - assertEquals(RestStatus.OK.getStatus(), responseClose.getStatusCode()); - - RestHelper.HttpResponse responseOpen = keyStoreRestHelper.executePostRequest(index + "/_open", ""); - assertEquals(RestStatus.OK.getStatus(), responseOpen.getStatusCode()); - } - - // as admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseClose = sslRestHelper.executePostRequest(index + "/_close", "", allAccessUserHeader); - assertEquals(RestStatus.OK.getStatus(), responseClose.getStatusCode()); - - RestHelper.HttpResponse responseOpen = sslRestHelper.executePostRequest(index + "/_open", "", allAccessUserHeader); - assertEquals(RestStatus.OK.getStatus(), responseOpen.getStatusCode()); - } - } - - @Test - public void testCloseOpenWithSystemIndices() throws Exception { - setupSystemIndicesEnabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper keyStoreRestHelper = keyStoreRestHelper(); - RestHelper sslRestHelper = sslRestHelper(); - - // as super-admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseClose = keyStoreRestHelper.executePostRequest(index + "/_close", ""); - assertEquals(RestStatus.OK.getStatus(), responseClose.getStatusCode()); - - RestHelper.HttpResponse responseOpen = keyStoreRestHelper.executePostRequest(index + "/_open", ""); - assertEquals(RestStatus.OK.getStatus(), responseOpen.getStatusCode()); - } - - // as admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseClose = sslRestHelper.executePostRequest(index + "/_close", "", allAccessUserHeader); - assertEquals(RestStatus.FORBIDDEN.getStatus(), responseClose.getStatusCode()); - - RestHelper.HttpResponse responseOpen = sslRestHelper.executePostRequest(index + "/_open", "", allAccessUserHeader); - assertEquals(RestStatus.OK.getStatus(), responseOpen.getStatusCode()); - } - } - - /*************************************************************************************************************************** - * Update Index Settings - ***************************************************************************************************************************/ - - @Test - public void testUpdateIndexSettings() throws Exception { - setupSystemIndicesDisabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper keyStoreRestHelper = keyStoreRestHelper(); - RestHelper sslRestHelper = sslRestHelper(); - - String indexSettings = "{\n" + " \"index\" : {\n" + " \"refresh_interval\" : null\n" + " }\n" + "}"; - - // as super-admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = keyStoreRestHelper.executePutRequest(index + "/_settings", indexSettings); - assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - } - // as admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = sslRestHelper.executePutRequest(index + "/_settings", indexSettings, allAccessUserHeader); - assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - } - } - - @Test - public void testUpdateIndexSettingsWithSystemIndices() throws Exception { - setupSystemIndicesEnabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper keyStoreRestHelper = keyStoreRestHelper(); - RestHelper sslRestHelper = sslRestHelper(); - - String indexSettings = "{\n" + " \"index\" : {\n" + " \"refresh_interval\" : null\n" + " }\n" + "}"; - - // as super-admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = keyStoreRestHelper.executePutRequest(index + "/_settings", indexSettings); - assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - } - // as admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = sslRestHelper.executePutRequest(index + "/_settings", indexSettings, allAccessUserHeader); - assertEquals(RestStatus.FORBIDDEN.getStatus(), response.getStatusCode()); - } - } - - /*************************************************************************************************************************** - * Index mappings. indices:admin/mapping/put - ************************************************************************************************************************** */ - - @Test - public void testUpdateMappings() throws Exception { - setupSystemIndicesDisabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper keyStoreRestHelper = keyStoreRestHelper(); - RestHelper sslRestHelper = sslRestHelper(); - - String newMappings = "{\"properties\": {" + "\"user_name\": {" + "\"type\": \"text\"" + "}}}"; - - // as super-admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = keyStoreRestHelper.executePutRequest(index + "/_mapping", newMappings); - assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - - } - // as admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = sslRestHelper.executePutRequest(index + "/_mapping", newMappings, allAccessUserHeader); - assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - } - } - - @Test - public void testUpdateMappingsWithSystemIndices() throws Exception { - setupSystemIndicesEnabledWithSsl(); - createTestIndicesAndDocs(); - RestHelper keyStoreRestHelper = keyStoreRestHelper(); - RestHelper sslRestHelper = sslRestHelper(); - - String newMappings = "{\"properties\": {" + "\"user_name\": {" + "\"type\": \"text\"" + "}}}"; - - // as super-admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = keyStoreRestHelper.executePutRequest(index + "/_mapping", newMappings); - assertEquals(RestStatus.OK.getStatus(), response.getStatusCode()); - - } - // as admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse response = sslRestHelper.executePutRequest(index + "/_mapping", newMappings, allAccessUserHeader); - assertEquals(RestStatus.FORBIDDEN.getStatus(), response.getStatusCode()); - assertTrue(response.getBody().contains(generalErrorMessage)); - } - } - - /*************************************************************************************************************************** - * Create index and Create doc - ***************************************************************************************************************************/ - - @Test - public void testCreate() throws Exception { - setupSystemIndicesDisabledWithSsl(); - RestHelper keyStoreRestHelper = keyStoreRestHelper(); - RestHelper sslRestHelper = sslRestHelper(); - - String indexSettings = "{\n" - + " \"settings\" : {\n" - + " \"index\" : {\n" - + " \"number_of_shards\" : 3, \n" - + " \"number_of_replicas\" : 2 \n" - + " }\n" - + " }\n" - + "}"; - - // as super-admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseIndex = keyStoreRestHelper.executePutRequest(index, indexSettings); - assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); - - RestHelper.HttpResponse response = keyStoreRestHelper.executePostRequest(index + "/_doc", "{\"foo\": \"bar\"}"); - assertTrue(response.getStatusCode() == RestStatus.CREATED.getStatus()); - } - - for (String index : listOfIndexesToTest) { - keyStoreRestHelper.executeDeleteRequest(index); - } - - // as admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseIndex = sslRestHelper.executePutRequest(index, indexSettings, allAccessUserHeader); - assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); - - RestHelper.HttpResponse response = sslRestHelper.executePostRequest(index + "/_doc", "{\"foo\": \"bar\"}", allAccessUserHeader); - assertTrue(response.getStatusCode() == RestStatus.CREATED.getStatus()); - } - } - - @Test - public void testCreateWithSystemIndices() throws Exception { - setupSystemIndicesEnabledWithSsl(); - RestHelper keyStoreRestHelper = keyStoreRestHelper(); - RestHelper sslRestHelper = sslRestHelper(); - - String indexSettings = "{\n" - + " \"settings\" : {\n" - + " \"index\" : {\n" - + " \"number_of_shards\" : 3, \n" - + " \"number_of_replicas\" : 2 \n" - + " }\n" - + " }\n" - + "}"; - - // as super-admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseIndex = keyStoreRestHelper.executePutRequest(index, indexSettings); - assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); - - RestHelper.HttpResponse response = keyStoreRestHelper.executePostRequest(index + "/_doc", "{\"foo\": \"bar\"}"); - assertTrue(response.getStatusCode() == RestStatus.CREATED.getStatus()); - } - - for (String index : listOfIndexesToTest) { - keyStoreRestHelper.executeDeleteRequest(index); - } - - // as admin - for (String index : listOfIndexesToTest) { - RestHelper.HttpResponse responseIndex = sslRestHelper.executePutRequest(index, indexSettings, allAccessUserHeader); - assertEquals(RestStatus.OK.getStatus(), responseIndex.getStatusCode()); - - RestHelper.HttpResponse response = sslRestHelper.executePostRequest(index + "/_doc", "{\"foo\": \"bar\"}", allAccessUserHeader); - assertTrue(response.getStatusCode() == RestStatus.FORBIDDEN.getStatus()); - } - } - - /*************************************************************************************************************************** - * snapshot : since snapshot takes more time, we are testing only Enabled case. - ***************************************************************************************************************************/ - @Test - public void testSnapshotWithSystemIndices() throws Exception { - setupSystemIndicesEnabledWithSsl(); - createTestIndicesAndDocs(); - createSnapshots(); - - try (Client tc = getClient()) { - for (String index : listOfIndexesToTest) { - tc.admin().indices().close(new CloseIndexRequest(index)).actionGet(); - } - } - - RestHelper sslRestHelper = sslRestHelper(); - // as admin - for (String index : listOfIndexesToTest) { - assertEquals( - HttpStatus.SC_OK, - sslRestHelper.executeGetRequest("_snapshot/" + index + "/" + index + "_1", allAccessUserHeader).getStatusCode() - ); - assertEquals( - HttpStatus.SC_OK, - sslRestHelper.executePostRequest( - "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", - "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", - allAccessUserHeader - ).getStatusCode() - ); - assertEquals( - HttpStatus.SC_FORBIDDEN, - sslRestHelper.executePostRequest( - "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", - "", - allAccessUserHeader - ).getStatusCode() - ); - } - } -} diff --git a/src/test/resources/internal_users_system_indices.yml b/src/test/resources/internal_users_system_indices.yml deleted file mode 100644 index 8a8c990eb3..0000000000 --- a/src/test/resources/internal_users_system_indices.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -_meta: - type: "internalusers" - config_version: 2 -admin_all_access: - hash: "$2y$12$ft8tXtxb.dyO/5MrDXHLc.e1o3dktEQJMvR2e.sgVDyD/gR7G9dLS" - reserved: false - hidden: false - backend_roles: [] - attributes: {} - description: "User mapped to all cluster and index permissions but no role to view protected index" diff --git a/src/test/resources/roles_mapping_system_indices.yml b/src/test/resources/roles_mapping_system_indices.yml deleted file mode 100644 index 64b5992fa8..0000000000 --- a/src/test/resources/roles_mapping_system_indices.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -_meta: - type: "rolesmapping" - config_version: 2 - -opendistro_security_all_access: - reserved: false - hidden: false - backend_roles: [] - hosts: [] - users: - - "admin_all_access" - and_backend_roles: [] - description: "Full index permissions" diff --git a/src/test/resources/roles_system_indices.yml b/src/test/resources/roles_system_indices.yml deleted file mode 100644 index 88668a553d..0000000000 --- a/src/test/resources/roles_system_indices.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -_meta: - type: "roles" - config_version: 2 - -opendistro_security_all_access: - description: "All access role" - cluster_permissions: - - '*' - index_permissions: - - index_patterns: - - '*' - allowed_actions: - - '*' diff --git a/src/test/resources/config_system_indices.yml b/src/test/resources/system_indices/config.yml similarity index 100% rename from src/test/resources/config_system_indices.yml rename to src/test/resources/system_indices/config.yml diff --git a/src/test/resources/system_indices/internal_users.yml b/src/test/resources/system_indices/internal_users.yml new file mode 100644 index 0000000000..d792fa47be --- /dev/null +++ b/src/test/resources/system_indices/internal_users.yml @@ -0,0 +1,27 @@ +--- +_meta: + type: "internalusers" + config_version: 2 +admin_all_access: + hash: "$2y$12$ft8tXtxb.dyO/5MrDXHLc.e1o3dktEQJMvR2e.sgVDyD/gR7G9dLS" + reserved: false + hidden: false + backend_roles: [] + attributes: {} + description: "User mapped to all cluster and index permissions but no role to view protected index" + +normal_user: + hash: "$2y$12$d1ONiqarfTF9xOuqKeNukeze1bVBXC1FyXJSpuC3B6/Ekbfu3ULHm" + reserved: false + hidden: false + backend_roles: [] + attributes: {} + description: "User Mapped to role With Access to Admin:System:Indices " + +normal_user_without_system_index: + hash: "$2y$12$V3.ACgUpHP9TlSbV3CNekOGB1NVov1C6Rq3QtXWCvACKVeQnkBCgG" + reserved: false + hidden: false + backend_roles: [] + attributes: {} + description: "User Mapped to role With no access to system indices" diff --git a/src/test/resources/system_indices/roles.yml b/src/test/resources/system_indices/roles.yml new file mode 100644 index 0000000000..82ac33a92d --- /dev/null +++ b/src/test/resources/system_indices/roles.yml @@ -0,0 +1,35 @@ +--- +_meta: + type: "roles" + config_version: 2 + +opendistro_security_all_access: + description: "All access role" + cluster_permissions: + - '*' + index_permissions: + - index_patterns: + - '*' + allowed_actions: + - '*' + +normal_role: + description: "Normal user Role" + cluster_permissions: + - '*' + index_permissions: + - index_patterns: + - '.system*' + allowed_actions: + - '*' + - 'system:admin/system_index' + +normal_role_without_system_index: + description: "Normal user Role" + cluster_permissions: + - '*' + index_permissions: + - index_patterns: + - '.system*' + allowed_actions: + - '*' diff --git a/src/test/resources/system_indices/roles_mapping.yml b/src/test/resources/system_indices/roles_mapping.yml new file mode 100644 index 0000000000..8d24f94271 --- /dev/null +++ b/src/test/resources/system_indices/roles_mapping.yml @@ -0,0 +1,34 @@ +--- +_meta: + type: "rolesmapping" + config_version: 2 + +opendistro_security_all_access: + reserved: false + hidden: false + backend_roles: [] + hosts: [] + users: + - "admin_all_access" + and_backend_roles: [] + description: "Full index permissions" + +normal_role: + reserved: false + hidden: false + backend_roles: [] + hosts: [] + users: + - "normal_user" + and_backend_roles: [] + description: "System index permissions" + +normal_role_without_system_index: + reserved: false + hidden: false + backend_roles: [] + hosts: [] + users: + - "normal_user_without_system_index" + and_backend_roles: [] + description: "No access to System index permissions"