diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java index 6e775bbc62..a1cb20d99c 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AbstractRestApiUnitTest.java @@ -16,10 +16,14 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.stream.Stream; import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; @@ -90,18 +94,7 @@ protected final void setupWithRestRoles(Settings nodeOverride) throws Exception .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("restapi/truststore.jks")); - builder.put("plugins.security.restapi.roles_enabled.0", "opendistro_security_role_klingons"); - builder.put("plugins.security.restapi.roles_enabled.1", "opendistro_security_role_vulcans"); - builder.put("plugins.security.restapi.roles_enabled.2", "opendistro_security_test"); - - builder.put("plugins.security.restapi.endpoints_disabled.global.CACHE.0", "*"); - - builder.put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.conFiGuration.0", "*"); - builder.put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.wRongType.0", "WRONGType"); - builder.put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.0", "PUT"); - builder.put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.1", "DELETE"); - - builder.put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_vulcans.CONFIG.0", "*"); + builder.put(rolesSettings()); if (null != nodeOverride) { builder.put(nodeOverride); @@ -114,6 +107,20 @@ protected final void setupWithRestRoles(Settings nodeOverride) throws Exception AuditTestUtils.updateAuditConfig(rh, nodeOverride != null ? nodeOverride : Settings.EMPTY); } + protected Settings rolesSettings() { + return Settings.builder() + .put("plugins.security.restapi.roles_enabled.0", "opendistro_security_role_klingons") + .put("plugins.security.restapi.roles_enabled.1", "opendistro_security_role_vulcans") + .put("plugins.security.restapi.roles_enabled.2", "opendistro_security_test") + .put("plugins.security.restapi.endpoints_disabled.global.CACHE.0", "*") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.conFiGuration.0", "*") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.wRongType.0", "WRONGType") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.0", "PUT") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_klingons.ROLESMAPPING.1", "DELETE") + .put("plugins.security.restapi.endpoints_disabled.opendistro_security_role_vulcans.CONFIG.0", "*") + .build(); + } + protected void deleteUser(String username) throws Exception { boolean sendAdminCertificate = rh.sendAdminCertificate; rh.sendAdminCertificate = true; @@ -197,30 +204,23 @@ protected void checkGeneralAccess(int status, String username, String password) protected String checkReadAccess(int status, String username, String password, String indexName, String actionType, int id) throws Exception { - boolean sendAdminCertificate = rh.sendAdminCertificate; rh.sendAdminCertificate = false; String action = indexName + "/" + actionType + "/" + id; - HttpResponse response = rh.executeGetRequest(action, - encodeBasicHeader(username, password)); + HttpResponse response = rh.executeGetRequest(action, encodeBasicHeader(username, password)); int returnedStatus = response.getStatusCode(); Assert.assertEquals(status, returnedStatus); - rh.sendAdminCertificate = sendAdminCertificate; return response.getBody(); } protected String checkWriteAccess(int status, String username, String password, String indexName, String actionType, int id) throws Exception { - - boolean sendAdminCertificate = rh.sendAdminCertificate; rh.sendAdminCertificate = false; String action = indexName + "/" + actionType + "/" + id; String payload = "{\"value\" : \"true\"}"; - HttpResponse response = rh.executePutRequest(action, payload, - encodeBasicHeader(username, password)); + HttpResponse response = rh.executePutRequest(action, payload, encodeBasicHeader(username, password)); int returnedStatus = response.getStatusCode(); Assert.assertEquals(status, returnedStatus); - rh.sendAdminCertificate = sendAdminCertificate; return response.getBody(); } @@ -260,4 +260,27 @@ protected Map jsonStringToMap(String json) throws JsonParseExcep protected static Collection> asCollection(Class... plugins) { return Arrays.asList(plugins); } + + String createRestAdminPermissionsPayload(String... additionPerms) throws JsonProcessingException { + final ObjectNode rootNode = (ObjectNode) DefaultObjectMapper.objectMapper.createObjectNode(); + rootNode.set("cluster_permissions", clusterPermissionsForRestAdmin(additionPerms)); + return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); + } + + ArrayNode clusterPermissionsForRestAdmin(String... additionPerms) { + final ArrayNode permissionsArray = (ArrayNode) DefaultObjectMapper.objectMapper.createArrayNode(); + for (final Map.Entry entry : RestApiAdminPrivilegesEvaluator.ENDPOINTS_WITH_PERMISSIONS.entrySet()) { + if (entry.getKey() == Endpoint.SSL) { + permissionsArray + .add(entry.getValue().build("certs")) + .add(entry.getValue().build("reloadcerts")); + } else { + permissionsArray.add(entry.getValue().build()); + } + } + if (additionPerms.length != 0) { + Stream.of(additionPerms).forEach(permissionsArray::add); + } + return permissionsArray; + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java index 6323746a7f..a0d1ece77a 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java @@ -13,6 +13,9 @@ import java.util.List; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; @@ -20,6 +23,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; @@ -44,9 +48,27 @@ public void testActionGroupsApi() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; + // create index + setupStarfleetIndex(); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + rh.sendAdminCertificate = true; + verifyGetForSuperAdmin(new Header[0]); + rh.sendAdminCertificate = true; + verifyDeleteForSuperAdmin(new Header[0], true); + rh.sendAdminCertificate = true; + verifyPutForSuperAdmin(new Header[0], true); + rh.sendAdminCertificate = true; + verifyPatchForSuperAdmin(new Header[0], true); + } + + void verifyGetForSuperAdmin(final Header[] header) throws Exception { // --- GET_UT // GET_UT, actiongroup exists - HttpResponse response = rh.executeGetRequest(ENDPOINT+"/CRUD_UT", new Header[0]); + HttpResponse response = rh.executeGetRequest(ENDPOINT+"/CRUD_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); List permissions = settings.getAsList("CRUD_UT.allowed_actions"); @@ -56,61 +78,50 @@ public void testActionGroupsApi() throws Exception { Assert.assertTrue(permissions.contains("OPENDISTRO_SECURITY_WRITE")); // GET_UT, actiongroup does not exist - response = rh.executeGetRequest(ENDPOINT+"/nothinghthere", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/nothinghthere", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // GET_UT, old endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET_UT, old endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET_UT, new endpoint which replaces configuration endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET_UT, old endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET_UT, new endpoint which replaces configuration endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET_UT, new endpoint which replaces configuration endpoint - response = rh.executeGetRequest(ENDPOINT, new Header[0]); + response = rh.executeGetRequest(ENDPOINT, header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + } - // create index - setupStarfleetIndex(); - - // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - // TODO: only one doctype allowed for ES6 - // checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "public", 0); - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); - // TODO: only one doctype allowed for ES6 - //checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "public", 0); - + void verifyDeleteForSuperAdmin(final Header[] header, final boolean userAdminCert) throws Exception { // -- DELETE // Non-existing role - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = userAdminCert; - response = rh.executeDeleteRequest(ENDPOINT+"/idonotexist", new Header[0]); + HttpResponse response = rh.executeDeleteRequest(ENDPOINT+"/idonotexist", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // remove action group READ_UT, read access not possible since // opendistro_security_role_starfleet // uses this action group. - response = rh.executeDeleteRequest(ENDPOINT+"/READ_UT", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT+"/READ_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); rh.sendAdminCertificate = false; checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); - // put picard in captains role. Role opendistro_security_role_captains uses the CRUD_UT // action group // which uses READ_UT and WRITE action groups. We removed READ_UT, so only @@ -125,24 +136,25 @@ public void testActionGroupsApi() throws Exception { rh.sendAdminCertificate = false; checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + } + void verifyPutForSuperAdmin(final Header[] header, final boolean userAdminCert) throws Exception { // -- PUT - // put with empty payload, must fail - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/SOMEGROUP", "", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + HttpResponse response = rh.executePutRequest(ENDPOINT+"/SOMEGROUP", "", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason")); // put new configuration with invalid payload, must fail response = rh.executePutRequest(ENDPOINT+"/SOMEGROUP", FileHelper.loadFile("restapi/actiongroup_not_parseable.json"), - new Header[0]); + header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason")); - response = rh.executePutRequest(ENDPOINT+"/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_crud.json"), new Header[0]); + response = rh.executePutRequest(ENDPOINT+"/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_crud.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; @@ -152,8 +164,8 @@ public void testActionGroupsApi() throws Exception { checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); // restore READ_UT action groups - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/READ_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/READ_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; @@ -162,99 +174,106 @@ public void testActionGroupsApi() throws Exception { checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); // -- PUT, new JSON format including readonly flag, disallowed in REST API - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_readonly.json"), new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_readonly.json"), header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // -- DELETE read only resource, must be forbidden // superAdmin can delete read only resource - rh.sendAdminCertificate = true; - response = rh.executeDeleteRequest(ENDPOINT+"/GET_UT", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executeDeleteRequest(ENDPOINT+"/GET_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // -- PUT read only resource, must be forbidden // superAdmin can add/update read only resource - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/GET_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/GET_UT", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); Assert.assertFalse(response.getBody().contains("Resource 'GET_UT' is read-only.")); // PUT with role name - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/kibana_user", FileHelper.loadFile("restapi/actiongroup_read.json"), new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/kibana_user", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("kibana_user is an existing role. A action group cannot be named with an existing role name.")); // PUT with self-referencing action groups - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/reference_itself", "{\"allowed_actions\": [\"reference_itself\"]}", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/reference_itself", "{\"allowed_actions\": [\"reference_itself\"]}", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("reference_itself cannot be an allowed_action of itself")); // -- GET_UT hidden resource, must be 404 but super admin can find it - rh.sendAdminCertificate = true; - response = rh.executeGetRequest(ENDPOINT+"/INTERNAL", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executeGetRequest(ENDPOINT+"/INTERNAL", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"hidden\":true")); // -- DELETE hidden resource, must be 404 - rh.sendAdminCertificate = true; - response = rh.executeDeleteRequest(ENDPOINT+"/INTERNAL", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executeDeleteRequest(ENDPOINT+"/INTERNAL", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("'INTERNAL' deleted.")); // -- PUT hidden resource, must be forbidden - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT+"/INTERNAL", FileHelper.loadFile("restapi/actiongroup_read.json"), new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePutRequest(ENDPOINT+"/INTERNAL", FileHelper.loadFile("restapi/actiongroup_read.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + } + void verifyPatchForSuperAdmin(final Header[] header, final boolean userAdminCert) throws Exception { // -- PATCH // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + HttpResponse response = rh.executePatchRequest(ENDPOINT+"/imnothere", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch read only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/GET_UT", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/GET_UT", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH with self-referencing action groups - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/GET_UT", "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", \"value\": \"GET_UT\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/GET_UT", "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", \"value\": \"GET_UT\" }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("GET_UT cannot be an allowed_action of itself")); // bulk PATCH with self-referencing action groups - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"BULKNEW1\"] } }," + "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"BULKNEW1\"] } }," + + "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("BULKNEW1 cannot be an allowed_action of itself")); // PATCH hidden resource, must be not found, can be found by superadmin, but fails with no path exist error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/INTERNAL", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/INTERNAL", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody(), response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH with relative JSON pointer, must fail - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"1/INTERNAL/allowed_actions/-\", \"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"1/INTERNAL/allowed_actions/-\", " + + "\"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH new format - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", \"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT+"/CRUD_UT", "[{ \"op\": \"add\", \"path\": \"/allowed_actions/-\", " + + "\"value\": \"OPENDISTRO_SECURITY_DELETE\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT+"/CRUD_UT", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/CRUD_UT", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - permissions = settings.getAsList("CRUD_UT.allowed_actions"); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + List permissions = settings.getAsList("CRUD_UT.allowed_actions"); Assert.assertNotNull(permissions); Assert.assertEquals(3, permissions.size()); Assert.assertTrue(permissions.contains("READ_UT")); @@ -265,49 +284,50 @@ public void testActionGroupsApi() throws Exception { // -- PATCH on whole config resource // PATCH read only resource, must be forbidden // SuperAdmin can patch read only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/GET_UT/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/GET_UT/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/GET_UT/description\", \"value\": \"foo\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/GET_UT/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be bad request - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/INTERNAL/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/INTERNAL/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH delete read only resource, must be forbidden // SuperAdmin can delete read only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/GET_UT\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/GET_UT\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH delete hidden resource, must be bad request - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/INTERNAL\" }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/INTERNAL\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"message\":\"Resource updated.")); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/CRUD_UT/hidden\", \"value\": true }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/CRUD_UT/hidden\", \"value\": true }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // add new resource with hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/NEWNEWNEW\", \"value\": {\"allowed_actions\": [\"indices:data/write*\"], \"hidden\":true }}]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, + "[{ \"op\": \"add\", \"path\": \"/NEWNEWNEW\", \"value\": {\"allowed_actions\": [\"indices:data/write*\"], \"hidden\":true }}]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // add new valid resources - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"indices:data/*\", \"cluster:monitor/*\"] } }," + "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", new Header[0]); + rh.sendAdminCertificate = userAdminCert; + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"add\", \"path\": \"/BULKNEW1\", \"value\": {\"allowed_actions\": [\"indices:data/*\", \"cluster:monitor/*\"] } }," + "{ \"op\": \"add\", \"path\": \"/BULKNEW2\", \"value\": {\"allowed_actions\": [\"READ_UT\"] } }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/BULKNEW1", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); permissions = settings.getAsList("BULKNEW1.allowed_actions"); @@ -316,7 +336,7 @@ public void testActionGroupsApi() throws Exception { Assert.assertTrue(permissions.contains("indices:data/*")); Assert.assertTrue(permissions.contains("cluster:monitor/*")); - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW2", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/BULKNEW2", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); permissions = settings.getAsList("BULKNEW2.allowed_actions"); @@ -325,13 +345,13 @@ public void testActionGroupsApi() throws Exception { Assert.assertTrue(permissions.contains("READ_UT")); // delete resource - response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/BULKNEW1\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT, "[{ \"op\": \"remove\", \"path\": \"/BULKNEW1\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/BULKNEW1", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // assert other resource is still there - response = rh.executeGetRequest(ENDPOINT+"/BULKNEW2", new Header[0]); + response = rh.executeGetRequest(ENDPOINT+"/BULKNEW2", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); permissions = settings.getAsList("BULKNEW2.allowed_actions"); @@ -340,6 +360,85 @@ public void testActionGroupsApi() throws Exception { Assert.assertTrue(permissions.contains("READ_UT")); } + @Test + public void testActionGroupsApiForRestAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + // create index + setupStarfleetIndex(); + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + verifyGetForSuperAdmin(new Header[] {restApiAdminHeader}); + verifyDeleteForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyPutForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyPatchForSuperAdmin(new Header[]{restApiAdminHeader}, false); + } + + @Test + public void testActionGroupsApiForActionGroupsRestApiAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + // create index + setupStarfleetIndex(); + final Header restApiAdminActionGroupsHeader = encodeBasicHeader("rest_api_admin_actiongroups", "rest_api_admin_actiongroups"); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[] { "starfleet" }, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + verifyGetForSuperAdmin(new Header[] {restApiAdminActionGroupsHeader}); + verifyDeleteForSuperAdmin(new Header[]{restApiAdminActionGroupsHeader}, false); + verifyPutForSuperAdmin(new Header[]{restApiAdminActionGroupsHeader}, false); + verifyPatchForSuperAdmin(new Header[]{restApiAdminActionGroupsHeader}, false); + } + + @Test + public void testCreateActionGroupWithRestAdminPermissionsForbidden() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header restApiAdminActionGroupsHeader = encodeBasicHeader("rest_api_admin_actiongroups", "rest_api_admin_actiongroups"); + final Header restApiHeader = encodeBasicHeader("test", "test"); + + HttpResponse response = rh.executePutRequest(ENDPOINT + "/rest_api_admin_group", restAdminAllowedActions(), + restApiAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePutRequest(ENDPOINT + "/rest_api_admin_group", restAdminAllowedActions(), restApiAdminActionGroupsHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePutRequest(ENDPOINT + "/rest_api_admin_group", restAdminAllowedActions(), restApiHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePatchRequest(ENDPOINT, restAdminPatchBody(), restApiAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePatchRequest(ENDPOINT, restAdminPatchBody(), restApiAdminActionGroupsHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePatchRequest(ENDPOINT, restAdminPatchBody(), restApiHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + String restAdminAllowedActions() throws JsonProcessingException { + final ObjectNode rootNode = DefaultObjectMapper.objectMapper.createObjectNode(); + rootNode.set("allowed_actions", clusterPermissionsForRestAdmin("cluster/*")); + return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); + } + + String restAdminPatchBody() throws JsonProcessingException { + final ArrayNode rootNode = DefaultObjectMapper.objectMapper.createArrayNode(); + final ObjectNode opAddRootNode = DefaultObjectMapper.objectMapper.createObjectNode(); + final ObjectNode allowedActionsNode = DefaultObjectMapper.objectMapper.createObjectNode(); + allowedActionsNode.set("allowed_actions", clusterPermissionsForRestAdmin("cluster/*")); + opAddRootNode + .put("op", "add") + .put("path", "/rest_api_admin_group") + .set("value", allowedActionsNode); + rootNode.add(opAddRootNode); + return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); + } + @Test public void testActionGroupsApiForNonSuperAdmin() throws Exception { diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java index 3d9e2dfc66..05739cc4ab 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java @@ -142,7 +142,7 @@ public void testPayloadMandatory() throws Exception { */ @Test public void testAllowlistApi() throws Exception { - setupWithRestRoles(null); + setupWithRestRoles(); // No creds, no admin certificate - UNAUTHORIZED checkGetAndPutAllowlistPermissions(HttpStatus.SC_UNAUTHORIZED, false); @@ -156,6 +156,29 @@ public void testAllowlistApi() throws Exception { checkGetAndPutAllowlistPermissions(HttpStatus.SC_OK, true, nonAdminCredsHeader); } + @Test + public void testAllowlistApiWithPermissions() throws Exception { + setupWithRestRoles(); + + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header restApiAllowlistHeader = encodeBasicHeader("rest_api_admin_allowlist", "rest_api_admin_allowlist"); + final Header restApiUserHeader = encodeBasicHeader("test", "test"); + + checkGetAndPutAllowlistPermissions(HttpStatus.SC_FORBIDDEN, false, restApiUserHeader); + checkGetAndPutAllowlistPermissions(HttpStatus.SC_OK, false, restApiAdminHeader); + } + + @Test + public void testAllowlistApiWithAllowListPermissions() throws Exception { + setupWithRestRoles(); + + final Header restApiAllowlistHeader = encodeBasicHeader("rest_api_admin_allowlist", "rest_api_admin_allowlist"); + final Header restApiUserHeader = encodeBasicHeader("test", "test"); + + checkGetAndPutAllowlistPermissions(HttpStatus.SC_FORBIDDEN, false, restApiUserHeader); + checkGetAndPutAllowlistPermissions(HttpStatus.SC_OK, false, restApiAllowlistHeader); + } + @Test public void testAllowlistAuditComplianceLogging() throws Exception { Settings settings = Settings.builder() diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/LegacySslCertsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/LegacySslCertsApiTest.java new file mode 100644 index 0000000000..c73a10ec5a --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/LegacySslCertsApiTest.java @@ -0,0 +1,27 @@ +/* + * 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.dlic.rest.api; + +import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; + +public class LegacySslCertsApiTest extends SslCertsApiTest { + + @Override + String certsInfoEndpoint() { + return LEGACY_OPENDISTRO_PREFIX + "/api/ssl/certs"; + } + + @Override + String certsReloadEndpoint(String certType) { + return String.format("%s/api/ssl/%s/reloadcerts", LEGACY_OPENDISTRO_PREFIX, certType); + } +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java index ba46781e7e..03d16ee78d 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java @@ -181,6 +181,85 @@ public void testNodesDnApi() throws Exception { } } + + @Test + public void testNodesDnApiWithPermissions() throws Exception { + Settings settings = Settings.builder().put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) + .build(); + setupWithRestRoles(settings); + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header restApiNodesDnHeader = encodeBasicHeader("rest_api_admin_nodesdn", "rest_api_admin_nodesdn"); + final Header restApiUserHeader = encodeBasicHeader("test", "test"); + //full access admin + { + rh.sendAdminCertificate = false; + response = rh.executeGetRequest( + ENDPOINT + "/nodesdn", restApiAdminHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + response = rh.executePutRequest( + ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", + restApiAdminHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); + + response = rh.executePatchRequest( + ENDPOINT + "/nodesdn/c1", + "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", + restApiAdminHeader + ); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + response = rh.executeDeleteRequest(ENDPOINT + "/nodesdn/c1", restApiAdminHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + } + //NodesDN only + { + rh.sendAdminCertificate = false; + response = rh.executeGetRequest(ENDPOINT + "/nodesdn", restApiNodesDnHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + response = rh.executePutRequest( + ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", + restApiNodesDnHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); + + response = rh.executePatchRequest( + ENDPOINT + "/nodesdn/c1", + "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", + restApiNodesDnHeader + ); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + response = rh.executeDeleteRequest(ENDPOINT + "/nodesdn/c1", restApiNodesDnHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + + response = rh.executeGetRequest(ENDPOINT + "/actiongroups", restApiNodesDnHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + } + //rest api user + { + rh.sendAdminCertificate = false; + response = rh.executeGetRequest(ENDPOINT + "/nodesdn", restApiUserHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + + response = rh.executePutRequest( + ENDPOINT + "/nodesdn/c1", "{\"nodes_dn\": [\"cn=popeye\"]}", + restApiUserHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + + response = rh.executePatchRequest( + ENDPOINT + "/nodesdn/c1", + "[{ \"op\": \"add\", \"path\": \"/nodes_dn/-\", \"value\": \"bluto\" }]", + restApiUserHeader + ); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + + response = rh.executeDeleteRequest(ENDPOINT + "/nodesdn/c1", restApiUserHeader); + assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_FORBIDDEN)); + } + + } + @Test public void testNodesDnApiAuditComplianceLogging() throws Exception { Settings settings = Settings.builder().put(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, true) diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java index 01fa5b4baf..529658940a 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java @@ -13,7 +13,10 @@ import java.util.List; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; @@ -30,12 +33,13 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; public class RolesApiTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; + protected String getEndpointPrefix() { return PLUGINS_PREFIX; } - public RolesApiTest(){ + public RolesApiTest() { ENDPOINT = getEndpointPrefix() + "/api"; } @@ -67,7 +71,26 @@ public void testAllRolesForSuperAdmin() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - HttpResponse response = rh.executeGetRequest(ENDPOINT + "/roles"); + + checkSuperAdminRoles(new Header[0]); + } + + @Test + public void testAllRolesForRestAdmin() throws Exception { + setupWithRestRoles(); + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + checkSuperAdminRoles(new Header[]{restApiAdminHeader}); + } + + @Test + public void testAllRolesForRolesRestAdmin() throws Exception { + setupWithRestRoles(); + final Header restApiAdminRolesHeader = encodeBasicHeader("rest_api_admin_roles", "rest_api_admin_roles"); + checkSuperAdminRoles(new Header[]{restApiAdminRolesHeader}); + } + + void checkSuperAdminRoles(final Header[] header) { + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/roles", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertFalse(response.getBody().contains("_meta")); @@ -121,103 +144,110 @@ public void testRolesApi() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; + // create index + setupStarfleetIndex(); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + + rh.sendAdminCertificate = true; + verifyGetForSuperAdmin(new Header[0]); + rh.sendAdminCertificate = true; + verifyDeleteForSuperAdmin(new Header[0], true); + rh.sendAdminCertificate = true; + verifyPutForSuperAdmin(new Header[0], true); + rh.sendAdminCertificate = true; + verifyPatchForSuperAdmin(new Header[0], true); + } + + void verifyGetForSuperAdmin(final Header[] header) throws Exception { // check roles exists - HttpResponse response = rh.executeGetRequest(ENDPOINT + "/roles"); + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/roles", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // -- GET - // GET opendistro_security_role_starfleet - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); JsonNode settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(1, settings.size()); // GET, role does not exist - response = rh.executeGetRequest(ENDPOINT + "/roles/nothinghthere", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/nothinghthere", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/roles/", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/roles", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"cluster_permissions\":[\"*\"]")); Assert.assertFalse(response.getBody().contains("\"cluster_permissions\" : [")); - response = rh.executeGetRequest(ENDPOINT + "/roles?pretty", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles?pretty", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertFalse(response.getBody().contains("\"cluster_permissions\":[\"*\"]")); Assert.assertTrue(response.getBody().contains("\"cluster_permissions\" : [")); // Super admin should be able to describe hidden role - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_hidden", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_hidden", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"hidden\":true")); + } - // create index - setupStarfleetIndex(); - - // add user picard, role starfleet, maps to opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "starfleet", "captains" }, HttpStatus.SC_CREATED); - checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - - + void verifyDeleteForSuperAdmin(final Header[] header, final boolean sendAdminCert) throws Exception { // -- DELETE - - rh.sendAdminCertificate = true; - // Non-existing role - response = rh.executeDeleteRequest(ENDPOINT + "/roles/idonotexist", new Header[0]); + HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/roles/idonotexist", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // read only role, SuperAdmin can delete the read-only role - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_transport_client", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_transport_client", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // hidden role allowed for superadmin - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_internal", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("'opendistro_security_internal' deleted.")); // remove complete role mapping for opendistro_security_role_starfleet_captains - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - rh.sendAdminCertificate = false; - // user has only role starfleet left, role has READ access only checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); - // ES7 only supports one doc type, but OpenSearch permission checks run first // So we also get a 403 FORBIDDEN when tring to add new document type checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; // remove also starfleet role, nothing is allowed anymore - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 0); + } + void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) throws Exception { // -- PUT // put with empty roles, must fail - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", "", new Header[0]); + HttpResponse response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", "", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - settings = DefaultObjectMapper.readTree(response.getBody()); + JsonNode settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason").asText()); // put new configuration with invalid payload, must fail response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_not_parseable.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_not_parseable.json"), header); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason").asText()); // put new configuration with invalid keys, must fail response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_invalid_keys.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_invalid_keys.json"), header); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage(), settings.get("reason").asText()); @@ -227,7 +257,7 @@ public void testRolesApi() throws Exception { // put new configuration with wrong datatypes, must fail response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_wrong_datatype.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_wrong_datatype.json"), header); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason").asText()); @@ -236,17 +266,17 @@ public void testRolesApi() throws Exception { // put read only role, must be forbidden // But SuperAdmin can still create it response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_transport_client", - FileHelper.loadFile("restapi/roles_captains.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // put hidden role, must be forbidden, but allowed for super admin response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_internal", - FileHelper.loadFile("restapi/roles_captains.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // restore starfleet role response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", - FileHelper.loadFile("restapi/roles_starfleet.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_starfleet.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); @@ -258,41 +288,41 @@ public void testRolesApi() throws Exception { // - 'indices:*' checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; // restore captains role response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_complete_invalid.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_complete_invalid.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); -// rh.sendAdminCertificate = true; +// rh.sendAdminCertificate = sendAdminCert; // response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", -// FileHelper.loadFile("restapi/roles_multiple.json"), new Header[0]); +// FileHelper.loadFile("restapi/roles_multiple.json"), header); // Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_multiple_2.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_multiple_2.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // check tenants - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_tenants.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains_tenants.json"), header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(2, settings.size()); Assert.assertEquals(settings.get("status").asText(), "OK"); - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); System.out.println(response.getBody()); settings = DefaultObjectMapper.readTree(response.getBody()); @@ -305,13 +335,13 @@ public void testRolesApi() throws Exception { response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_tenants2.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains_tenants2.json"), header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(2, settings.size()); Assert.assertEquals(settings.get("status").asText(), "OK"); - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(1, settings.size()); @@ -327,13 +357,13 @@ public void testRolesApi() throws Exception { // remove tenants from role response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_no_tenants.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains_no_tenants.json"), header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(2, settings.size()); Assert.assertEquals(settings.get("status").asText(), "OK"); - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(1, settings.size()); @@ -341,32 +371,46 @@ public void testRolesApi() throws Exception { Assert.assertTrue(new SecurityJsonNode(settings).getDotted("opendistro_security_role_starfleet_captains.tenant_permissions").get(0).isNull()); response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/roles_captains_tenants_malformed.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_captains_tenants_malformed.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(settings.get("status").asText(), "error"); Assert.assertEquals(settings.get("reason").asText(), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); + } + void verifyPatchForSuperAdmin(final Header[] header, final boolean sendAdminCert) throws Exception { // -- PATCH // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + HttpResponse response = rh.executePatchRequest( + ENDPOINT + "/roles/imnothere", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch it - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_transport_client", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles/opendistro_security_transport_client", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be not found, can be found for superadmin, but will fail with no path present exception - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles/opendistro_security_internal", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles/opendistro_security_role_starfleet", + "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody(), response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); @@ -389,122 +433,328 @@ public void testRolesApi() throws Exception { // -- PATCH on whole config resource // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"add\", \"path\": \"/imnothere/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/imnothere/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH read only resource, must be forbidden - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/a\", \"value\": [ \"foo\", \"bar\" ] }]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH hidden resource, must be bad request - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH delete read only resource, must be forbidden // SuperAdmin can delete read only user - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_transport_client\" }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_transport_client\" }]", + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be bad request, but allowed for superadmin - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_internal\"}]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"remove\", \"path\": \"/opendistro_security_internal\"}]", + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"message\":\"Resource updated.")); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"add\", \"path\": \"/newnewnew\", \"value\": { \"hidden\": true, \"index_permissions\" : [ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/newnewnew\", \"value\": { \"hidden\": true, \"index_permissions\" : " + + "[ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"index_permissions\" : [ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest( + ENDPOINT + "/roles", + "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"index_permissions\" : " + + "[ {\"index_patterns\" : [ \"sf\" ],\"allowed_actions\" : [ \"OPENDISTRO_SECURITY_READ\" ]}] }}]", + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/roles/bulknew1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/bulknew1", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = DefaultObjectMapper.readTree(response.getBody()); - permissions = new SecurityJsonNode(settings).get("bulknew1").get("index_permissions").get(0).get("allowed_actions").asList(); + JsonNode settings = DefaultObjectMapper.readTree(response.getBody()); + permissions = new SecurityJsonNode(settings).get("bulknew1").get("index_permissions").get(0).get("allowed_actions").asList(); Assert.assertNotNull(permissions); Assert.assertEquals(1, permissions.size()); Assert.assertTrue(permissions.contains("OPENDISTRO_SECURITY_READ")); // delete resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"remove\", \"path\": \"/bulknew1\"}]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/roles", "[{ \"op\": \"remove\", \"path\": \"/bulknew1\"}]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/roles/bulknew1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/roles/bulknew1", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // put valid field masks response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_field_mask_valid", - FileHelper.loadFile("restapi/roles_field_masks_valid.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_field_masks_valid.json"), header); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); // put invalid field masks response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_field_mask_invalid", - FileHelper.loadFile("restapi/roles_field_masks_invalid.json"), new Header[0]); + FileHelper.loadFile("restapi/roles_field_masks_invalid.json"), header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + } + + @Test + public void testRolesApiWithAllRestApiPermissions() throws Exception { + setupWithRestRoles(); + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + + rh.sendAdminCertificate = false; + setupStarfleetIndex(); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + + verifyGetForSuperAdmin(new Header[]{restApiAdminHeader}); + verifyDeleteForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyPutForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyPatchForSuperAdmin(new Header[]{restApiAdminHeader}, false); } @Test - public void testRolesApiForNonSuperAdmin() throws Exception { + public void testRolesApiWithRestApiRolePermission() throws Exception { + setupWithRestRoles(); + + final Header restApiRolesHeader = encodeBasicHeader("rest_api_admin_roles", "rest_api_admin_roles"); + + rh.sendAdminCertificate = false; + setupStarfleetIndex(); + + // add user picard, role starfleet, maps to opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[]{"starfleet", "captains"}, HttpStatus.SC_CREATED); + checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); + + verifyGetForSuperAdmin(new Header[]{restApiRolesHeader}); + verifyDeleteForSuperAdmin(new Header[]{restApiRolesHeader}, false); + verifyPutForSuperAdmin(new Header[]{restApiRolesHeader}, false); + verifyPatchForSuperAdmin(new Header[]{restApiRolesHeader}, false); + } + + @Test + public void testCreateOrUpdateRestApiAdminRoleForbiddenForNonSuperAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header adminHeader = encodeBasicHeader("admin", "admin"); + final Header restApiHeader = encodeBasicHeader("test", "test"); + + final String restAdminPermissionsPayload = createRestAdminPermissionsPayload("cluster/*"); + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_admin_role", restAdminPermissionsPayload, restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + response = rh.executePutRequest( + ENDPOINT + "/roles/rest_admin_role_to_delete", restAdminPermissionsPayload, restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + // attempt to create a new rest admin role by admin + response = rh.executePutRequest( + ENDPOINT + "/roles/some_rest_admin_role", + restAdminPermissionsPayload, + adminHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // attempt to update exiting admin role + response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_admin_role", + restAdminPermissionsPayload, + adminHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // attempt to patch exiting admin role + response = rh.executePatchRequest( + ENDPOINT + "/roles/new_rest_admin_role", + createPatchRestAdminPermissionsPayload("replace"), + adminHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // attempt to update exiting admin role + response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_admin_role", + restAdminPermissionsPayload, + restApiHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // attempt to create a new rest admin role by admin + response = rh.executePutRequest( + ENDPOINT + "/roles/some_rest_admin_role", + restAdminPermissionsPayload, + restApiHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + // attempt to patch exiting admin role and crate a new one + response = rh.executePatchRequest( + ENDPOINT + "/roles", + createPatchRestAdminPermissionsPayload("replace"), + restApiHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePatchRequest( + ENDPOINT + "/roles", + createPatchRestAdminPermissionsPayload("add"), + restApiHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePatchRequest( + ENDPOINT + "/roles", + createPatchRestAdminPermissionsPayload("remove"), + restApiHeader); + System.out.println("RESPONSE: " + response.getBody()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + @Test + public void testDeleteRestApiAdminRoleForbiddenForNonSuperAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header adminHeader = encodeBasicHeader("admin", "admin"); + final Header restApiHeader = encodeBasicHeader("test", "test"); + + final String allRestAdminPermissionsPayload = createRestAdminPermissionsPayload("cluster/*"); + + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_admin_role", allRestAdminPermissionsPayload, restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + // attempt to update exiting admin role + response = rh.executeDeleteRequest( + ENDPOINT + "/roles/new_rest_admin_role", + adminHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + //true to change + response = rh.executeDeleteRequest( + ENDPOINT + "/roles/new_rest_admin_role", + allRestAdminPermissionsPayload, + restApiHeader); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + + private String createPatchRestAdminPermissionsPayload(final String op) throws JsonProcessingException { + final ArrayNode rootNode = (ArrayNode) DefaultObjectMapper.objectMapper.createArrayNode(); + final ObjectNode opAddObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); + final ObjectNode clusterPermissionsNode = DefaultObjectMapper.objectMapper.createObjectNode(); + clusterPermissionsNode.set("cluster_permissions", clusterPermissionsForRestAdmin("cluster/*")); + if ("add".equals(op)) { + opAddObjectNode + .put("op", "add") + .put("path", "/some_rest_admin_role") + .set("value", clusterPermissionsNode); + rootNode.add(opAddObjectNode); + } + + if ("remove".equals(op)) { + final ObjectNode opRemoveObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); + opRemoveObjectNode + .put("op", "remove") + .put("path", "/rest_admin_role_to_delete"); + rootNode.add(opRemoveObjectNode); + } + + if ("replace".equals(op)) { + final ObjectNode replaceRemoveObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); + replaceRemoveObjectNode + .put("op", "replace") + .put("path", "/new_rest_admin_role/cluster_permissions") + .set("value", clusterPermissionsForRestAdmin("*")); + + rootNode.add(replaceRemoveObjectNode); + } + return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); + } + + @Test + public void testRolesApiForNonSuperAdmin() throws Exception { setupWithRestRoles(); rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = false; rh.sendHTTPClientCredentials = true; + checkNonSuperAdminRoles(new Header[0]); + } + void checkNonSuperAdminRoles(final Header[] header) throws Exception { HttpResponse response; // Delete read only roles - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_transport_client" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_transport_client", header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Put read only roles - response = rh.executePutRequest( ENDPOINT + "/roles/opendistro_security_transport_client", - FileHelper.loadFile("restapi/roles_captains.json"), new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_transport_client", + FileHelper.loadFile("restapi/roles_captains.json"), header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch single read only roles - response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_transport_client", "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest( + ENDPOINT + "/roles/opendistro_security_transport_client", + "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", + header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch multiple read only roles - response = rh.executePatchRequest(ENDPOINT + "/roles/", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/roles/", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_transport_client/description\", \"value\": \"foo\" }]", + header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // get hidden role - response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_internal"); + response = rh.executeGetRequest(ENDPOINT + "/roles/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // delete hidden role - response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_internal" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/roles/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // put hidden role String body = FileHelper.loadFile("restapi/roles_captains.json"); - response = rh.executePutRequest( ENDPOINT+ "/roles/opendistro_security_internal", body, new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_internal", body, header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch single hidden roles - response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_internal", "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/roles/opendistro_security_internal", + "[{ \"op\": \"replace\", \"path\": \"/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch multiple hidden roles - response = rh.executePatchRequest(ENDPOINT + "/roles/", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/roles/", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", + header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - } @Test - public void checkNullElementsInArray() throws Exception{ + public void checkNullElementsInArray() throws Exception { setup(); rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; @@ -516,7 +766,7 @@ public void checkNullElementsInArray() throws Exception{ Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); body = FileHelper.loadFile("restapi/roles_null_array_element_index_permissions.json"); - response = rh.executePutRequest(ENDPOINT+ "/roles/opendistro_security_role_starfleet", body, new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", body, new Header[0]); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java index 168f15dc43..f6405e984a 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java @@ -13,6 +13,9 @@ import java.util.List; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; @@ -20,6 +23,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; @@ -44,12 +48,114 @@ public void testRolesMappingApi() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; + // create index + setupStarfleetIndex(); + // add user picard, role captains initially maps to + // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[] { "captains" }, HttpStatus.SC_CREATED); + checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + // TODO: only one doctype allowed for ES6 + //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + rh.sendAdminCertificate = true; + verifyGetForSuperAdmin(new Header[0]); + rh.sendAdminCertificate = true; + verifyDeleteForSuperAdmin(new Header[0], true); + rh.sendAdminCertificate = true; + verifyPutForSuperAdmin(new Header[0]); + verifyPatchForSuperAdmin(new Header[0]); + // mapping with several backend roles, one of the is captain + deleteAndputNewMapping(new Header[0],"rolesmapping_backendroles_captains_list.json", true); + checkAllSfAllowed(); + + // mapping with one backend role, captain + deleteAndputNewMapping(new Header[0],"rolesmapping_backendroles_captains_single.json", true); + checkAllSfAllowed(); + + // mapping with several users, one is picard + deleteAndputNewMapping(new Header[0],"rolesmapping_users_picard_list.json", true); + checkAllSfAllowed(); + + // just user picard + deleteAndputNewMapping(new Header[0],"rolesmapping_users_picard_single.json", true); + checkAllSfAllowed(); + + // hosts + deleteAndputNewMapping(new Header[0],"rolesmapping_hosts_list.json", true); + checkAllSfAllowed(); + + // hosts + deleteAndputNewMapping(new Header[0],"rolesmapping_hosts_single.json", true); + checkAllSfAllowed(); + + // full settings, access + deleteAndputNewMapping(new Header[0],"rolesmapping_all_access.json", true); + checkAllSfAllowed(); + + // full settings, no access + deleteAndputNewMapping(new Header[0],"rolesmapping_all_noaccess.json", true); + checkAllSfForbidden(); + } + + @Test + public void testRolesMappingApiWithFullPermissions() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + // create index + setupStarfleetIndex(); + // add user picard, role captains initially maps to + // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet + addUserWithPassword("picard", "picard", new String[] { "captains" }, HttpStatus.SC_CREATED); + checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + // TODO: only one doctype allowed for ES6 + //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); + + verifyGetForSuperAdmin(new Header[]{restApiAdminHeader}); + verifyDeleteForSuperAdmin(new Header[]{restApiAdminHeader}, false); + verifyPutForSuperAdmin(new Header[]{restApiAdminHeader}); + verifyPatchForSuperAdmin(new Header[]{restApiAdminHeader}); + // mapping with several backend roles, one of the is captain + deleteAndputNewMapping(new Header[]{restApiAdminHeader}, "rolesmapping_backendroles_captains_list.json", false); + checkAllSfAllowed(); + + // mapping with one backend role, captain + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_backendroles_captains_single.json", true); + checkAllSfAllowed(); + + // mapping with several users, one is picard + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_users_picard_list.json", true); + checkAllSfAllowed(); + + // just user picard + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_users_picard_single.json", true); + checkAllSfAllowed(); + + // hosts + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_hosts_list.json", true); + checkAllSfAllowed(); + + // hosts + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_hosts_single.json", true); + checkAllSfAllowed(); + + // full settings, access + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_all_access.json", true); + checkAllSfAllowed(); + + // full settings, no access + deleteAndputNewMapping(new Header[]{restApiAdminHeader},"rolesmapping_all_noaccess.json", true); + checkAllSfForbidden(); + + } + + void verifyGetForSuperAdmin(final Header[] header) throws Exception { // check rolesmapping exists, old config api - HttpResponse response = rh.executeGetRequest(ENDPOINT + "/rolesmapping"); + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // check rolesmapping exists, new API - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping"); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getContentType(), response.isJsonContentType()); @@ -61,9 +167,8 @@ public void testRolesMappingApi() throws Exception { // -- GET - // GET opendistro_security_role_starfleet, exists - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getContentType(), response.isJsonContentType()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); @@ -73,55 +178,42 @@ public void testRolesMappingApi() throws Exception { Assert.assertEquals("nagilum", settings.getAsList("opendistro_security_role_starfleet.users").get(0)); // GET, rolesmapping does not exist - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/nothinghthere", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/nothinghthere", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getContentType(), response.isJsonContentType()); // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getContentType(), response.isJsonContentType()); // Super admin should be able to describe particular hidden rolemapping - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"hidden\":true")); + } - // create index - setupStarfleetIndex(); - - // add user picard, role captains initially maps to - // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet - addUserWithPassword("picard", "picard", new String[] { "captains" }, HttpStatus.SC_CREATED); - checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); - - // TODO: only one doctype allowed for ES6 - //checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); - - // --- DELETE - - rh.sendAdminCertificate = true; - + void verifyDeleteForSuperAdmin(final Header[] header, final boolean useAdminCert) throws Exception { // Non-existing role - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/idonotexist", new Header[0]); + HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/idonotexist", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // read only role // SuperAdmin can delete read only role - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // hidden role - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("'opendistro_security_internal' deleted.")); // remove complete role mapping for opendistro_security_role_starfleet_captains - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executeGetRequest(ENDPOINT + "/configuration/rolesmapping"); rh.sendAdminCertificate = false; @@ -134,32 +226,30 @@ public void testRolesMappingApi() throws Exception { // checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); // remove also opendistro_security_role_starfleet, poor picard has no mapping left - rh.sendAdminCertificate = true; - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", new Header[0]); + rh.sendAdminCertificate = useAdminCert; + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); rh.sendAdminCertificate = false; checkAllSfForbidden(); + } - rh.sendAdminCertificate = true; - - // --- PUT - + void verifyPutForSuperAdmin(final Header[] header) throws Exception { // put with empty mapping, must fail - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", "", new Header[0]); + HttpResponse response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", "", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason")); // put new configuration with invalid payload, must fail response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_not_parseable.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_not_parseable.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason")); // put new configuration with invalid keys, must fail response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_invalid_keys.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_invalid_keys.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage(), settings.get("reason")); @@ -170,7 +260,7 @@ public void testRolesMappingApi() throws Exception { // wrong datatypes response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_backendroles_captains_single_wrong_datatype.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_backendroles_captains_single_wrong_datatype.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -179,7 +269,7 @@ public void testRolesMappingApi() throws Exception { Assert.assertTrue(settings.get("users") == null); response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_hosts_single_wrong_datatype.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_hosts_single_wrong_datatype.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -188,7 +278,7 @@ public void testRolesMappingApi() throws Exception { Assert.assertTrue(settings.get("users") == null); response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_users_picard_single_wrong_datatype.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_users_picard_single_wrong_datatype.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -199,81 +289,83 @@ public void testRolesMappingApi() throws Exception { // Read only role mapping // SuperAdmin can add read only roles - mappings response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // hidden role, allowed for super admin response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + } - // -- PATCH + void verifyPatchForSuperAdmin(final Header[] header) throws Exception { // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + HttpResponse response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch read-only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\"] }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", + "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\"] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH hidden resource, must be not found, can be found by super admin - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ " + - "\"foo\", \"bar\" ] }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", + "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ " + + "\"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", + "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", "[{ \"op\": \"add\", \"path\": \"/backend_roles/-\", \"value\": \"spring\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", + "[{ \"op\": \"add\", \"path\": \"/backend_roles/-\", \"value\": \"spring\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); List permissions = settings.getAsList("opendistro_security_role_vulcans.backend_roles"); Assert.assertNotNull(permissions); Assert.assertTrue(permissions.contains("spring")); // -- PATCH on whole config resource // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH read only resource, must be forbidden // SuperAdmin can patch read only resource rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be bad request rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_vulcans/hidden\", \"value\": true }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_vulcans/hidden\", \"value\": true }]", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"backend_roles\":[\"vulcanadmin\"]} }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", + "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"backend_roles\":[\"vulcanadmin\"]} }]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); permissions = settings.getAsList("bulknew1.backend_roles"); @@ -281,45 +373,10 @@ public void testRolesMappingApi() throws Exception { Assert.assertTrue(permissions.contains("vulcanadmin")); // PATCH delete - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"remove\", \"path\": \"/bulknew1\"}]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"remove\", \"path\": \"/bulknew1\"}]", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - - // mapping with several backend roles, one of the is captain - deleteAndputNewMapping("rolesmapping_backendroles_captains_list.json"); - checkAllSfAllowed(); - - // mapping with one backend role, captain - deleteAndputNewMapping("rolesmapping_backendroles_captains_single.json"); - checkAllSfAllowed(); - - // mapping with several users, one is picard - deleteAndputNewMapping("rolesmapping_users_picard_list.json"); - checkAllSfAllowed(); - - // just user picard - deleteAndputNewMapping("rolesmapping_users_picard_single.json"); - checkAllSfAllowed(); - - // hosts - deleteAndputNewMapping("rolesmapping_hosts_list.json"); - checkAllSfAllowed(); - - // hosts - deleteAndputNewMapping("rolesmapping_hosts_single.json"); - checkAllSfAllowed(); - - // full settings, access - deleteAndputNewMapping("rolesmapping_all_access.json"); - checkAllSfAllowed(); - - // full settings, no access - deleteAndputNewMapping("rolesmapping_all_noaccess.json"); - checkAllSfForbidden(); - } private void checkAllSfAllowed() throws Exception { @@ -334,13 +391,13 @@ private void checkAllSfForbidden() throws Exception { checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picard", "sf", "_doc", 1); } - private HttpResponse deleteAndputNewMapping(String fileName) throws Exception { - rh.sendAdminCertificate = true; + private HttpResponse deleteAndputNewMapping(final Header[] header, final String fileName, final boolean useAdminCert) throws Exception { + rh.sendAdminCertificate = useAdminCert; HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - new Header[0]); + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/"+fileName), new Header[0]); + FileHelper.loadFile("restapi/"+fileName), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); rh.sendAdminCertificate = false; return response; @@ -355,46 +412,142 @@ public void testRolesMappingApiForNonSuperAdmin() throws Exception { rh.sendAdminCertificate = false; rh.sendHTTPClientCredentials = true; + verifyNonSuperAdminUser(new Header[0]); + } + + @Test + public void testRolesMappingApiForNonSuperAdminRestApiUser() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + final Header restApiHeader = encodeBasicHeader("test", "test"); + verifyNonSuperAdminUser(new Header[] {restApiHeader}); + } + + void verifyNonSuperAdminUser(final Header[] header) throws Exception { HttpResponse response; // Delete read only roles mapping - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library" , header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Put read only roles mapping response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch single read only roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // Patch multiple read only roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); // GET, rolesmapping is hidden, allowed for super admin - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Delete hidden roles mapping - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal" , new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal" , header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Put hidden roles mapping response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), new Header[0]); + FileHelper.loadFile("restapi/rolesmapping_all_access.json"), header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch hidden roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // Patch multiple hidden roles mapping - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", new Header[0]); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); + response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", header); + Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); } + + @Test + public void testChangeRestApiAdminRoleMappingForbiddenForNonSuperAdmin() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + final Header adminHeader = encodeBasicHeader("admin", "admin"); + final Header restApiHeader = encodeBasicHeader("test", "test"); + + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_api_role", + createRestAdminPermissionsPayload(), restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + response = rh.executePutRequest( + ENDPOINT + "/roles/new_rest_api_role_without_mapping", + createRestAdminPermissionsPayload(), restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/new_rest_api_role", + createUsersPayload("a", "b", "c"), restApiAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + verifyRestApiPutAndDeleteForNonRestApiAdmin(adminHeader); + verifyRestApiPutAndDeleteForNonRestApiAdmin(restApiHeader); + verifyRestApiPatchForNonRestApiAdmin(adminHeader, false); + verifyRestApiPatchForNonRestApiAdmin(restApiHeader, false); + verifyRestApiPatchForNonRestApiAdmin(adminHeader, true); + verifyRestApiPatchForNonRestApiAdmin(restApiHeader, true); + } + + private void verifyRestApiPutAndDeleteForNonRestApiAdmin(final Header header) throws Exception { + HttpResponse response = rh.executePutRequest( + ENDPOINT + "/rolesmapping/new_rest_api_role", createUsersPayload("a", "b", "c"), header); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executeDeleteRequest( + ENDPOINT + "/rolesmapping/new_rest_api_role", "", header); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + private void verifyRestApiPatchForNonRestApiAdmin(final Header header, boolean bulk) throws Exception { + String path = ENDPOINT + "/rolesmapping"; + if (!bulk) { + path += "/new_rest_api_role"; + } + HttpResponse response = rh.executePatchRequest(path, createPathPayload("add"), header); + System.err.println(response.getBody()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePatchRequest(path, createPathPayload("replace"), header); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + response = rh.executePatchRequest(path, createPathPayload("remove"), header); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + private ObjectNode createUsersObjectNode(final String... users) { + final ArrayNode usersArray = DefaultObjectMapper.objectMapper.createArrayNode(); + for (final String user : users) { + usersArray.add(user); + } + return DefaultObjectMapper.objectMapper.createObjectNode().set("users", usersArray); + } + + private String createUsersPayload(final String... users) throws JsonProcessingException { + return DefaultObjectMapper.objectMapper.writeValueAsString(createUsersObjectNode(users)); + } + private String createPathPayload(final String op) throws JsonProcessingException { + final ArrayNode arrayNode = DefaultObjectMapper.objectMapper.createArrayNode(); + final ObjectNode opNode = DefaultObjectMapper.objectMapper.createObjectNode(); + opNode.put("op", op); + if ("add".equals(op)) { + opNode.put("path", "/new_rest_api_role_without_mapping"); + opNode.set("value", createUsersObjectNode("d", "e", "f")); + } + if ("replace".equals(op)) { + opNode.put("path", "/new_rest_api_role"); + opNode.set("value", createUsersObjectNode("g", "h", "i")); + } + if ("remove".equals(op)) { + opNode.put("path", "/new_rest_api_role"); + } + return DefaultObjectMapper.objectMapper.writeValueAsString(arrayNode.add(opNode)); } @Test diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java new file mode 100644 index 0000000000..1054b51aea --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/api/SslCertsApiTest.java @@ -0,0 +1,174 @@ +/* + * 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.dlic.rest.api; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.junit.Assert; +import org.junit.Test; + +import org.opensearch.common.settings.Settings; +import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; + +import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; + +public class SslCertsApiTest extends AbstractRestApiUnitTest { + + static final String HTTP_CERTS = "http"; + + static final String TRANSPORT_CERTS = "transport"; + + private final static List> EXPECTED_CERTIFICATES = + ImmutableList.of( + ImmutableMap.of( + "issuer_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", + "subject_dn", "CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE", + "san", "[[2, node-0.example.com], [2, localhost], [7, 127.0.0.1], [8, 1.2.3.4.5.5]]", + "not_before", "2018-05-05T14:37:09Z", + "not_after", "2028-05-02T14:37:09Z" + ), + ImmutableMap.of( + "issuer_dn", "CN=Example Com Inc. Root CA,OU=Example Com Inc. Root CA,O=Example Com Inc.,DC=example,DC=com", + "subject_dn", "CN=Example Com Inc. Signing CA,OU=Example Com Inc. Signing CA,O=Example Com Inc.,DC=example,DC=com", + "san", "", + "not_before", "2018-05-05T14:37:08Z", + "not_after", "2028-05-04T14:37:08Z" + ) + ); + + private final static String EXPECTED_CERTIFICATES_BY_TYPE; + static { + try { + EXPECTED_CERTIFICATES_BY_TYPE = DefaultObjectMapper.objectMapper.writeValueAsString( + ImmutableMap.of( + "http_certificates_list", EXPECTED_CERTIFICATES, + "transport_certificates_list", EXPECTED_CERTIFICATES + ) + ); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + private final Header restApiCertsInfoAdminHeader = encodeBasicHeader("rest_api_admin_ssl_info", "rest_api_admin_ssl_info"); + + private final Header restApiReloadCertsAdminHeader = encodeBasicHeader("rest_api_admin_ssl_reloadcerts", "rest_api_admin_ssl_reloadcerts"); + + private final Header restApiHeader = encodeBasicHeader("test", "test"); + + + String certsInfoEndpoint() { + return PLUGINS_PREFIX + "/api/ssl/certs"; + } + + String certsReloadEndpoint(final String certType) { + return String.format("%s/api/ssl/%s/reloadcerts", PLUGINS_PREFIX, certType); + } + + @Test + public void testCertsInfo() throws Exception { + setupWithRestRoles(); + final Header adminCredsHeader = encodeBasicHeader("admin", "admin"); + // No creds, no admin certificate - UNAUTHORIZED + rh.sendAdminCertificate = false; + HttpResponse response = rh.executeGetRequest(certsInfoEndpoint()); + Assert.assertEquals(response.getBody(), HttpStatus.SC_UNAUTHORIZED, response.getStatusCode()); + + rh.sendAdminCertificate = false; + response = rh.executeGetRequest(certsInfoEndpoint(), adminCredsHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + sendAdminCert(); + response = rh.executeGetRequest(certsInfoEndpoint()); + Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); + Assert.assertEquals(EXPECTED_CERTIFICATES_BY_TYPE, response.getBody()); + + rh.sendAdminCertificate = false; + Assert.assertEquals(EXPECTED_CERTIFICATES_BY_TYPE, loadCerts(restApiAdminHeader)); + Assert.assertEquals(EXPECTED_CERTIFICATES_BY_TYPE, loadCerts(restApiCertsInfoAdminHeader)); + + response = rh.executeGetRequest(certsInfoEndpoint(), restApiHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + private String loadCerts(final Header... header) throws Exception { + HttpResponse response = rh.executeGetRequest(certsInfoEndpoint(), restApiAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); + return response.getBody(); + } + + @Test + public void testReloadCertsNotAvailableByDefault() throws Exception { + setupWithRestRoles(); + + sendAdminCert(); + verifyReloadCertsNotAvailable(); + + rh.sendAdminCertificate = false; + verifyReloadCertsNotAvailable(restApiAdminHeader); + verifyReloadCertsNotAvailable(restApiReloadCertsAdminHeader); + + HttpResponse response = rh.executePutRequest(certsReloadEndpoint(HTTP_CERTS), "{}", restApiHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePutRequest(certsReloadEndpoint(TRANSPORT_CERTS), "{}", restApiHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + } + + private void verifyReloadCertsNotAvailable(final Header... header) { + HttpResponse response = rh.executePutRequest(certsReloadEndpoint(HTTP_CERTS), "{}", header); + Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + response = rh.executePutRequest(certsReloadEndpoint(TRANSPORT_CERTS), "{}", header); + Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + } + + @Test + public void testReloadCertsWrongCertsType() throws Exception { + setupWithRestRoles(reloadEnabled()); + sendAdminCert(); + HttpResponse response = rh.executePutRequest(certsReloadEndpoint("aaaaa"), "{}"); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + rh.sendAdminCertificate = false; + response = rh.executePutRequest(certsReloadEndpoint("bbbb"), "{}", restApiAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + response = rh.executePutRequest(certsReloadEndpoint("cccc"), "{}", restApiReloadCertsAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + + } + + @Test + public void testReloadCerts() throws Exception { + setupWithRestRoles(reloadEnabled()); + } + + + private void sendAdminCert() { + rh.keystore = "restapi/kirk-keystore.jks"; + rh.sendAdminCertificate = true; + } + + Settings reloadEnabled() { + return Settings.builder() + .put(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, true) + .build(); + } + +} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index f100b4ba7f..88eab72766 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -58,7 +58,7 @@ public void testSecurityRoles() throws Exception { .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(56, settings.size()); + Assert.assertEquals(133, settings.size()); response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/newuser\", \"value\": {\"password\": \"newuser\", \"opendistro_security_roles\": [\"opendistro_security_all_access\"] } }]", new Header[0]); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); @@ -104,50 +104,57 @@ public void testUserApi() throws Exception { rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; - // initial configuration, 6 users - HttpResponse response = rh - .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); + // initial configuration + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(56, settings.size()); - // --- GET + Assert.assertEquals(133, settings.size()); + verifyGet(); + verifyPut(); + verifyPatch(true); + // create index first + setupStarfleetIndex(); + verifyRoles(true); + } + private void verifyGet(final Header... header) throws Exception { + // --- GET // GET, user admin, exists - response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", new Header[0]); + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/internalusers/admin", header); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(7, settings.size()); // hash must be filtered Assert.assertEquals("", settings.get("admin.hash")); // GET, user does not exist - response = rh.executeGetRequest(ENDPOINT + "/internalusers/nothinghthere", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/nothinghthere", header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/user/", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/user/", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/user", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/user", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + } + private void verifyPut(final Header... header) throws Exception { // -- PUT - // no username given - response = rh.executePutRequest(ENDPOINT + "/internalusers/", "{\"hash\": \"123\"}", new Header[0]); + HttpResponse response = rh.executePutRequest(ENDPOINT + "/internalusers/", "{\"hash\": \"123\"}", header); Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode()); // Faulty JSON payload response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{some: \"thing\" asd other: \"thing\"}", - new Header[0]); + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage()); // Missing quotes in JSON - parseable in 6.x, but wrong config keys - response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{some: \"thing\", other: \"thing\"}", - new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{some: \"thing\", other: \"thing\"}", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); //JK: this should be "Could not parse content of request." because JSON is truly invalid @@ -156,102 +163,105 @@ public void testUserApi() throws Exception { //Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("other")); // Get hidden role - response = rh.executeGetRequest(ENDPOINT + "/internalusers/hide" , new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/hide" , header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("\"hidden\":true")); // Associating with hidden role is allowed (for superadmin) response = rh.executePutRequest(ENDPOINT + "/internalusers/test", "{ \"opendistro_security_roles\": " + - "[\"opendistro_security_hidden\"]}", new Header[0]); + "[\"opendistro_security_hidden\"]}", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // Associating with reserved role is allowed (for superadmin) response = rh.executePutRequest(ENDPOINT + "/internalusers/test", "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"], " + - "\"hash\": \"123\"}", - new Header[0]); + "\"hash\": \"123\"}", + header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // Associating with non-existent role is not allowed response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{ \"opendistro_security_roles\": [\"non_existent\"]}", - new Header[0]); + header); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("message"), "Role 'non_existent' is not available for role-mapping."); // Wrong config keys response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{\"some\": \"thing\", \"other\": \"thing\"}", - new Header[0]); + header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("some")); Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("other")); + } + private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) throws Exception { // -- PATCH // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + HttpResponse response = rh.executePatchRequest(ENDPOINT + "/internalusers/imnothere", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // PATCH read only resource, must be forbidden, // but SuperAdmin can PATCH read-only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/sarek", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers/sarek", "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // PATCH hidden resource, must be not found, can be found for super admin - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/q", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers/q", "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH password - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/password\", \"value\": \"neu\" }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers/test", "[{ \"op\": \"add\", \"path\": \"/password\", \"value\": \"neu\" }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/internalusers/test", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/test", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertFalse(settings.hasValue("test.password")); Assert.assertTrue(settings.hasValue("test.hash")); // -- PATCH on whole config resource // PATCH on non-existing resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH read only resource, must be forbidden, // but SuperAdmin can PATCH read only resouce - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/sarek/description\", \"value\": \"foo\" }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); rh.sendAdminCertificate = false; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/sarek/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/sarek/a\", \"value\": [ \"foo\", \"bar\" ] }]"); Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusCode()); // PATCH hidden resource, must be bad request - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/q/a\", \"value\": [ \"foo\", \"bar\" ] }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/q/a\", \"value\": [ \"foo\", \"bar\" ] }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/test/hidden\", \"value\": true }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/test/hidden\", \"value\": true }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); // PATCH - rh.sendAdminCertificate = true; - response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePatchRequest(ENDPOINT + "/internalusers", + "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": {\"password\": \"bla\", \"backend_roles\": [\"vulcan\"] } }]", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/internalusers/bulknew1", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/bulknew1", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertFalse(settings.hasValue("bulknew1.password")); @@ -267,17 +277,17 @@ public void testUserApi() throws Exception { // add/update user, user is read only, forbidden // SuperAdmin can add read only users - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; addUserWithHash("sarek", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_OK); // add/update user, user is hidden, forbidden, allowed for super admin - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; addUserWithHash("q", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_OK); // add users - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; addUserWithHash("nagilum", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); @@ -285,20 +295,20 @@ public void testUserApi() throws Exception { checkGeneralAccess(HttpStatus.SC_OK, "nagilum", "nagilum"); // try remove user, no username - rh.sendAdminCertificate = true; - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers", restAdminHeader); Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode()); // try remove user, nonexisting user - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/picard", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/picard", restAdminHeader); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); // try remove readonly user - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/sarek", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/sarek", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); // try remove hidden user, allowed for super admin - response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/q", new Header[0]); + response = rh.executeDeleteRequest(ENDPOINT + "/internalusers/q", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Assert.assertTrue(response.getBody().contains("'q' deleted.")); // now really remove user @@ -309,7 +319,7 @@ public void testUserApi() throws Exception { checkGeneralAccess(HttpStatus.SC_UNAUTHORIZED, "nagilum", "nagilum"); // use password instead of hash - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; addUserWithPassword("nagilum", "correctpassword", HttpStatus.SC_CREATED); rh.sendAdminCertificate = false; @@ -319,7 +329,7 @@ public void testUserApi() throws Exception { deleteUser("nagilum"); // Check unchanged password functionality - rh.sendAdminCertificate = true; + rh.sendAdminCertificate = sendAdminCert; // new user, password or hash is mandatory addUserWithoutPasswordOrHash("nagilum", new String[]{"starfleet"}, HttpStatus.SC_BAD_REQUEST); @@ -329,35 +339,32 @@ public void testUserApi() throws Exception { // update user, do not specify hash or password, hash must remain the same addUserWithoutPasswordOrHash("nagilum", new String[]{"starfleet"}, HttpStatus.SC_OK); // get user, check hash, must be untouched - response = rh.executeGetRequest(ENDPOINT + "/internalusers/nagilum", new Header[0]); + response = rh.executeGetRequest(ENDPOINT + "/internalusers/nagilum", restAdminHeader); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertTrue(settings.get("nagilum.hash").equals("")); + } - - // ROLES - // create index first - setupStarfleetIndex(); - + private void verifyRoles(final boolean sendAdminCert, Header... header) throws Exception { // wrong datatypes in roles file - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes.json"), new Header[0]); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + rh.sendAdminCertificate = sendAdminCert; + HttpResponse response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes.json"), header); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); rh.sendAdminCertificate = false; - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes.json"), new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); rh.sendAdminCertificate = false; - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes2.json"), new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes2.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -365,8 +372,8 @@ public void testUserApi() throws Exception { Assert.assertTrue(settings.get("backend_roles") == null); rh.sendAdminCertificate = false; - rh.sendAdminCertificate = true; - response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes3.json"), new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", FileHelper.loadFile("restapi/users_wrong_datatypes3.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); @@ -395,12 +402,12 @@ public void testUserApi() throws Exception { checkReadAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 0); checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); - rh.sendAdminCertificate = true; - response = rh.executeGetRequest(ENDPOINT + "/internalusers/picard", new Header[0]); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executeGetRequest(ENDPOINT + "/internalusers/picard", header); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals("", settings.get("picard.hash")); - roles = settings.getAsList("picard.backend_roles"); + List roles = settings.getAsList("picard.backend_roles"); Assert.assertNotNull(roles); Assert.assertEquals(2, roles.size()); Assert.assertTrue(roles.contains("starfleet")); @@ -411,10 +418,46 @@ public void testUserApi() throws Exception { // check tabs in json - response = rh.executePutRequest(ENDPOINT + "/internalusers/userwithtabs", "\t{\"hash\": \t \"123\"\t} ", new Header[0]); + response = rh.executePutRequest(ENDPOINT + "/internalusers/userwithtabs", "\t{\"hash\": \t \"123\"\t} ", header); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); } + @Test + public void testUserApiWithRestAdminPermissions() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); + // initial configuration + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString(), restApiAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(133, settings.size()); + verifyGet(restApiAdminHeader); + verifyPut(restApiAdminHeader); + verifyPatch(false, restApiAdminHeader); + // create index first + setupStarfleetIndex(); + verifyRoles(false, restApiAdminHeader); + } + + @Test + public void testUserApiWithRestInternalUsersAdminPermissions() throws Exception { + setupWithRestRoles(); + rh.sendAdminCertificate = false; + final Header restApiInternalUsersAdminHeader = encodeBasicHeader("rest_api_admin_internalusers", "rest_api_admin_internalusers"); + // initial configuration + HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString(), restApiInternalUsersAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); + Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); + Assert.assertEquals(133, settings.size()); + verifyGet(restApiInternalUsersAdminHeader); + verifyPut(restApiInternalUsersAdminHeader); + verifyPatch(false, restApiInternalUsersAdminHeader); + // create index first + setupStarfleetIndex(); + verifyRoles(false, restApiInternalUsersAdminHeader); + } + @Test public void testPasswordRules() throws Exception { @@ -436,7 +479,7 @@ public void testPasswordRules() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); System.out.println(response.getBody()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(56, settings.size()); + Assert.assertEquals(133, settings.size()); addUserWithPassword("tooshoort", "", HttpStatus.SC_BAD_REQUEST); addUserWithPassword("tooshoort", "123", HttpStatus.SC_BAD_REQUEST); @@ -516,7 +559,7 @@ public void testUserApiWithDots() throws Exception { .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(56, settings.size()); + Assert.assertEquals(133, settings.size()); addUserWithPassword(".my.dotuser0", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); diff --git a/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java b/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java index ea78ee043c..f7684a9c51 100644 --- a/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java +++ b/src/test/java/org/opensearch/security/ssl/SecuritySSLReloadCertsActionTests.java @@ -127,32 +127,6 @@ public void testReloadHttpSSLCertsPass() throws Exception { assertReloadCertificateSuccess(rh, "http", getUpdatedCertDetailsExpectedResponse("http")); } - @Test - public void testReloadHttpSSLCerts_FailWrongUri() throws Exception { - initClusterWithTestCerts(); - RestHelper rh = getRestHelperAdminUser(); - - RestHelper.HttpResponse reloadCertsResponse = rh.executePutRequest("_opendistro/_security/api/ssl/wrong/reloadcerts", null); - JSONObject expectedResponse = new JSONObject(); - // Note: toString and toJSONString replace / with \/. This helps get rid of the additional \ character. - expectedResponse.put("message", "invalid uri path, please use /_opendistro/_security/api/ssl/http/reload or /_opendistro/_security/api/ssl/transport/reload"); - final String expectedResponseString = expectedResponse.toString().replace("\\", ""); - Assert.assertEquals(expectedResponseString, reloadCertsResponse.getBody()); - } - - - @Test - public void testSSLReloadFail_UnAuthorizedUser() throws Exception { - initClusterWithTestCerts(); - // Test endpoint for non-admin user - RestHelper rh = getRestHelperNonAdminUser(); - - final RestHelper.HttpResponse reloadCertsResponse = rh.executePutRequest(RELOAD_TRANSPORT_CERTS_ENDPOINT, null); - Assert.assertEquals(401, reloadCertsResponse.getStatusCode()); - Assert.assertEquals("Unauthorized", reloadCertsResponse.getStatusReason()); - } - - @Test public void testSSLReloadFail_InvalidDNAndDate() throws Exception { initClusterWithTestCerts(); @@ -169,24 +143,6 @@ public void testSSLReloadFail_InvalidDNAndDate() throws Exception { Assert.assertEquals(expectedResponse.toString(), reloadCertsResponse.getBody()); } - @Test - public void testSSLReloadFail_NoReloadSet() throws Exception { - updateFiles(defaultCertFilePath, pemCertFilePath); - updateFiles(defaultKeyFilePath, pemKeyFilePath); - // This is when SSLCertReload property is set to false - initTestCluster(pemCertFilePath, pemKeyFilePath, pemCertFilePath, pemKeyFilePath, false); - - RestHelper rh = getRestHelperAdminUser(); - - final RestHelper.HttpResponse reloadCertsResponse = rh.executePutRequest(RELOAD_TRANSPORT_CERTS_ENDPOINT, null); - Assert.assertEquals(400, reloadCertsResponse.getStatusCode()); - JSONObject expectedResponse = new JSONObject(); - expectedResponse.appendField("error", "no handler found for uri [/_opendistro/_security/api/ssl/transport/reloadcerts] and method [PUT]"); - // Note: toString and toJSONString replace / with \/. This helps get rid of the additional \ character. - final String expectedResponseString = expectedResponse.toString().replace("\\", ""); - Assert.assertEquals(expectedResponseString, reloadCertsResponse.getBody()); - } - @Test public void testReloadTransportSSLSameCertsPass() throws Exception { initClusterWithTestCerts(); diff --git a/src/test/resources/restapi/internal_users.yml b/src/test/resources/restapi/internal_users.yml index 0049ab8c86..658d3f3aa1 100644 --- a/src/test/resources/restapi/internal_users.yml +++ b/src/test/resources/restapi/internal_users.yml @@ -61,3 +61,69 @@ admin_all_access: - "vulcan" attributes: {} description: "sample user with all_access, used to test whitelisting" +rest_api_admin_user: + hash: "$2y$12$X5ZamIheHYc2bihGTbK66Oe1.1vJ19akH0OFGF7TvI2BhbbED.KcO" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Full Access admin user" +rest_api_admin_actiongroups: + hash: "$2y$12$XE9zXOgyYBBxnzoWMowIsO1w6o6oIc5w3vgwYjdEE44m9MxFZEuR." + reserved: false + hidden: false + backend_roles: [] + description: "REST API Action groups admin user" +rest_api_admin_allowlist: + hash: "$2y$12$W5AdCO/j08KiDu7EF/1Zf.nkcQM/7s.TtAdN2pRpbDM31xXcIIJUq" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Allow list admin user" +rest_api_admin_audit: + hash: "$2y$12$UEbBqz9S6xuEefbK3LDvge5MwX4V1GvJYzUP8M24ItkfXMXg/NSh6" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Audit admin user" +rest_api_admin_internalusers: + hash: "$2y$12$pUn1a6jdIeR.stkvEqNe5uK3rOY7Dj3uQfE8Cvd2bjNjTQ2HbsBMK" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Internal users admin user" +rest_api_admin_nodesdn: + hash: "$2y$12$xFUIepz0vILRMzMkZMGY1Ow1P1eJo8TJ2oGiaFXaenGrOMsmDnKZS" + reserved: false + hidden: false + backend_roles: [] + description: "REST API NodesDN admin user" +rest_api_admin_roles: + hash: "$2y$12$BR.CBsElNLj8v2dzpHJ7bOKVLwWKWjKDhlEvBIvAe9b6/m0xWy2Bq" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Roles admin user" +rest_api_admin_rolesmapping: + hash: "$2y$12$WQb7PsnRRr04zxjuZsDwU.F7QEr7W0f/rJLjUNLf50hpoJuTqqnaS" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Roles Mapping admin user" +rest_api_admin_ssl_info: + hash: "$2y$12$irI4k0eKE8z9OXEd1jO4eeQfPV8WRMfttzutAhEeRBWy5XNXOlpr." + reserved: false + hidden: false + backend_roles: [] + description: "REST API SSL Certs admin user" +rest_api_admin_ssl_reloadcerts: + hash: "$2y$12$DxNdaBBMvTq5wO5XlnwlTeGSaC7yNoFoJt2N5TVtraopxPnGjMol2" + reserved: false + hidden: false + backend_roles: [] + description: "REST API SSL Reload Certs admin user" +rest_api_admin_tenants: + hash: "$2y$12$q05T7m7DFtkLLj.MVJ6jjuZkAywG4ZwaNi9fiYn6XCJelN2TUXCy2" + reserved: false + hidden: false + backend_roles: [] + description: "REST API Tenats admin user" diff --git a/src/test/resources/restapi/roles.yml b/src/test/resources/restapi/roles.yml index d82382e4f6..6deb194e9b 100644 --- a/src/test/resources/restapi/roles.yml +++ b/src/test/resources/restapi/roles.yml @@ -393,3 +393,51 @@ opendistro_security_role_starfleet_captains: allowed_actions: - "CRUD_UT" tenant_permissions: [] +rest_api_admin_full_access: + reserved: true + cluster_permissions: + - 'restapi:admin/actiongroups' + - 'restapi:admin/allowlist' + - 'restapi:admin/internalusers' + - 'restapi:admin/nodesdn' + - 'restapi:admin/roles' + - 'restapi:admin/rolesmapping' + - 'restapi:admin/ssl/certs/info' + - 'restapi:admin/ssl/certs/reload' + - 'restapi:admin/tenants' +rest_api_admin_actiongroups_only: + reserved: true + cluster_permissions: + - 'restapi:admin/actiongroups' +rest_api_admin_allowlist_only: + reserved: true + cluster_permissions: + - 'restapi:admin/allowlist' +rest_api_admin_internalusers_only: + reserved: true + cluster_permissions: + - 'restapi:admin/internalusers' +rest_api_admin_nodesdn_only: + reserved: true + cluster_permissions: + - 'restapi:admin/nodesdn' +rest_api_admin_roles_only: + reserved: true + cluster_permissions: + - 'restapi:admin/roles' +rest_api_admin_rolesmapping_only: + reserved: true + cluster_permissions: + - 'restapi:admin/rolesmapping' +rest_api_admin_ssl_info_only: + reserved: true + cluster_permissions: + - 'restapi:admin/ssl/certs/info' +rest_api_admin_ssl_reloadcerts_only: + reserved: true + cluster_permissions: + - 'restapi:admin/ssl/certs/reload' +rest_api_admin_tenants_only: + reserved: true + cluster_permissions: + - 'restapi:admin/tenants' diff --git a/src/test/resources/restapi/roles_mapping.yml b/src/test/resources/restapi/roles_mapping.yml index 8c46942854..a87287d5ff 100644 --- a/src/test/resources/restapi/roles_mapping.yml +++ b/src/test/resources/restapi/roles_mapping.yml @@ -185,6 +185,16 @@ opendistro_security_test: hosts: [] users: - "test" + - "rest_api_admin_user" + - "rest_api_admin_nodesdn" + - "rest_api_admin_allowlist" + - "rest_api_admin_roles" + - "rest_api_admin_rolesmapping" + - "rest_api_admin_actiongroups" + - "rest_api_admin_internalusers" + - "rest_api_admin_tenants" + - "rest_api_admin_ssl_info" + - "rest_api_admin_ssl_reloadcerts" and_backend_roles: [] description: "Migrated from v6" opendistro_security_role_starfleet_captains: @@ -206,3 +216,47 @@ opendistro_security_role_host2: - "opendistro_security_host_localhost" and_backend_roles: [] description: "Migrated from v6" +rest_api_admin_full_access: + reserved: false + hidden: true + users: [rest_api_admin_user] +rest_api_admin_actiongroups_only: + reserved: false + hidden: true + users: [rest_api_admin_actiongroups] +rest_api_admin_allowlist_only: + reserved: false + hidden: true + users: [rest_api_admin_allowlist] +rest_api_admin_audit_only: + reserved: false + hidden: true + users: [rest_api_admin_audit] +rest_api_admin_internalusers_only: + reserved: false + hidden: true + users: [rest_api_admin_internalusers] +rest_api_admin_nodesdn_only: + reserved: false + hidden: true + users: [rest_api_admin_nodesdn] +rest_api_admin_roles_only: + reserved: false + hidden: true + users: [rest_api_admin_roles] +rest_api_admin_rolesmapping_only: + reserved: false + hidden: true + users: [rest_api_admin_rolesmapping] +rest_api_admin_ssl_info_only: + reserved: false + hidden: true + users: [rest_api_admin_ssl_info] +rest_api_admin_ssl_reloadcerts_only: + reserved: false + hidden: true + users: [rest_api_admin_ssl_reloadcerts] +rest_api_admin_tenants_only: + reserved: false + hidden: true + users: [rest_api_admin_tenants] diff --git a/src/test/resources/restapi/roles_tenants.yml b/src/test/resources/restapi/roles_tenants.yml index 93b510dd16..e9b724e342 100644 --- a/src/test/resources/restapi/roles_tenants.yml +++ b/src/test/resources/restapi/roles_tenants.yml @@ -2,3 +2,13 @@ _meta: type: "tenants" config_version: 2 +some_admin_tenant: + reserved: false + description: "Demo tenant for admin user" +hidden_tenant: + reserved: true + hidden: true + description: "Hidden tenant" +reserved_tenant: + reserved: true + description: "Reserved tenant"