diff --git a/build.gradle b/build.gradle index 9391b26017..a22a044ba0 100644 --- a/build.gradle +++ b/build.gradle @@ -506,6 +506,9 @@ dependencies { implementation 'com.flipkart.zjsonpatch:zjsonpatch:0.4.14' implementation 'org.apache.commons:commons-collections4:4.4' + //Password generation + implementation 'org.passay:passay:1.6.3' + //JSON path implementation 'com.jayway.jsonpath:json-path:2.8.0' implementation 'net.minidev:json-smart:2.4.11' diff --git a/src/integrationTest/java/org/opensearch/security/rest/WhoAmITests.java b/src/integrationTest/java/org/opensearch/security/rest/WhoAmITests.java index 5e9992c8ad..1a41aa8ff2 100644 --- a/src/integrationTest/java/org/opensearch/security/rest/WhoAmITests.java +++ b/src/integrationTest/java/org/opensearch/security/rest/WhoAmITests.java @@ -14,17 +14,28 @@ import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; import org.apache.hc.core5.http.HttpStatus; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.opensearch.rest.RestRequest; +import org.opensearch.security.auditlog.AuditLog; +import org.opensearch.test.framework.AuditCompliance; +import org.opensearch.test.framework.AuditConfiguration; +import org.opensearch.test.framework.AuditFilters; import org.opensearch.test.framework.TestSecurityConfig; import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.audit.AuditLogsRule; import org.opensearch.test.framework.cluster.ClusterManager; import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.security.auditlog.impl.AuditCategory.GRANTED_PRIVILEGES; +import static org.opensearch.security.auditlog.impl.AuditCategory.MISSING_PRIVILEGES; import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.audit.AuditMessagePredicate.auditPredicate; +import static org.opensearch.test.framework.audit.AuditMessagePredicate.userAuthenticated; @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) @ThreadLeakScope(ThreadLeakScope.Scope.NONE) @@ -50,12 +61,35 @@ public class WhoAmITests { public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) .authc(AUTHC_HTTPBASIC_INTERNAL) .users(WHO_AM_I, WHO_AM_I_LEGACY, WHO_AM_I_NO_PERM) + .audit( + new AuditConfiguration(true).compliance(new AuditCompliance().enabled(true)) + .filters(new AuditFilters().enabledRest(true).enabledTransport(true).resolveBulkRequests(true)) + ) .build(); + @Rule + public AuditLogsRule auditLogsRule = new AuditLogsRule(); + @Test - public void testWhoAmIWithGetPermissions() throws Exception { + public void testWhoAmIWithGetPermissions() { try (TestRestClient client = cluster.getRestClient(WHO_AM_I)) { assertThat(client.get(WHOAMI_PROTECTED_ENDPOINT).getStatusCode(), equalTo(HttpStatus.SC_OK)); + + // audit log, named route + auditLogsRule.assertExactly( + 1, + userAuthenticated(WHO_AM_I).withLayer(AuditLog.Origin.REST) + .withRestMethod(RestRequest.Method.GET) + .withRequestPath("/" + WHOAMI_PROTECTED_ENDPOINT) + .withInitiatingUser(WHO_AM_I) + ); + auditLogsRule.assertExactly( + 1, + auditPredicate(GRANTED_PRIVILEGES).withLayer(AuditLog.Origin.REST) + .withRestMethod(RestRequest.Method.GET) + .withRequestPath("/" + WHOAMI_PROTECTED_ENDPOINT) + .withEffectiveUser(WHO_AM_I) + ); } try (TestRestClient client = cluster.getRestClient(WHO_AM_I)) { @@ -64,29 +98,60 @@ public void testWhoAmIWithGetPermissions() throws Exception { } @Test - public void testWhoAmIWithGetPermissionsLegacy() throws Exception { + public void testWhoAmIWithGetPermissionsLegacy() { try (TestRestClient client = cluster.getRestClient(WHO_AM_I_LEGACY)) { assertThat(client.get(WHOAMI_ENDPOINT).getStatusCode(), equalTo(HttpStatus.SC_OK)); } try (TestRestClient client = cluster.getRestClient(WHO_AM_I_LEGACY)) { assertThat(client.get(WHOAMI_PROTECTED_ENDPOINT).getStatusCode(), equalTo(HttpStatus.SC_OK)); + + // audit log, named route + auditLogsRule.assertExactly( + 1, + userAuthenticated(WHO_AM_I_LEGACY).withLayer(AuditLog.Origin.REST) + .withRestMethod(RestRequest.Method.GET) + .withRequestPath("/" + WHOAMI_PROTECTED_ENDPOINT) + .withInitiatingUser(WHO_AM_I_LEGACY) + ); + auditLogsRule.assertExactly( + 1, + auditPredicate(GRANTED_PRIVILEGES).withLayer(AuditLog.Origin.REST) + .withRestMethod(RestRequest.Method.GET) + .withRequestPath("/" + WHOAMI_PROTECTED_ENDPOINT) + .withEffectiveUser(WHO_AM_I_LEGACY) + ); } } @Test - public void testWhoAmIWithoutGetPermissions() throws Exception { + public void testWhoAmIWithoutGetPermissions() { try (TestRestClient client = cluster.getRestClient(WHO_AM_I_NO_PERM)) { assertThat(client.get(WHOAMI_ENDPOINT).getStatusCode(), equalTo(HttpStatus.SC_OK)); } try (TestRestClient client = cluster.getRestClient(WHO_AM_I_NO_PERM)) { assertThat(client.get(WHOAMI_PROTECTED_ENDPOINT).getStatusCode(), equalTo(HttpStatus.SC_UNAUTHORIZED)); + + // audit log, named route + auditLogsRule.assertExactly( + 1, + userAuthenticated(WHO_AM_I_NO_PERM).withLayer(AuditLog.Origin.REST) + .withRestMethod(RestRequest.Method.GET) + .withRequestPath("/" + WHOAMI_PROTECTED_ENDPOINT) + ); + auditLogsRule.assertExactly( + 1, + auditPredicate(MISSING_PRIVILEGES).withLayer(AuditLog.Origin.REST) + .withRestMethod(RestRequest.Method.GET) + .withRequestPath("/" + WHOAMI_PROTECTED_ENDPOINT) + .withEffectiveUser(WHO_AM_I_NO_PERM) + ); } } @Test - public void testWhoAmIPost() throws Exception { + public void testWhoAmIPost() { try (TestRestClient client = cluster.getRestClient(WHO_AM_I)) { assertThat(client.post(WHOAMI_ENDPOINT).getStatusCode(), equalTo(HttpStatus.SC_OK)); } diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index 143731088a..2fd3fc474d 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -52,7 +52,6 @@ import org.opensearch.action.index.IndexRequest; import org.opensearch.action.update.UpdateRequest; import org.opensearch.client.Client; -import org.opensearch.common.Strings; import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.xcontent.ToXContentObject; @@ -695,7 +694,7 @@ private static String configToJson(CType configType, Map> getConfigurationsFromIndex( if (logComplianceEvent && auditLog.getComplianceConfig().isEnabled()) { CType configurationType = configTypes.iterator().next(); Map fields = new HashMap(); - fields.put(configurationType.toLCString(), Strings.toString(XContentType.JSON, retVal.get(configurationType))); + fields.put(configurationType.toLCString(), Strings.toString(MediaTypeRegistry.JSON, retVal.get(configurationType))); auditLog.logDocumentRead(this.securityIndex, configurationType.toLCString(), null, fields); } diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java index de7ffe1fc2..cf2e77a25f 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java @@ -48,10 +48,10 @@ import org.opensearch.client.Client; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.common.Strings; +import org.opensearch.core.xcontent.MediaTypeRegistry; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.index.query.ParsedQuery; import org.opensearch.core.rest.RestStatus; @@ -230,10 +230,10 @@ public boolean invoke( StringBuilder sb = new StringBuilder(); if (searchRequest.source() != null) { - sb.append(Strings.toString(XContentType.JSON, searchRequest.source()) + System.lineSeparator()); + sb.append(Strings.toString(MediaTypeRegistry.JSON, searchRequest.source()) + System.lineSeparator()); } - sb.append(Strings.toString(XContentType.JSON, af) + System.lineSeparator()); + sb.append(Strings.toString(MediaTypeRegistry.JSON, af) + System.lineSeparator()); LogManager.getLogger("debuglogger").error(sb.toString()); @@ -245,7 +245,9 @@ public boolean invoke( LogManager.getLogger("debuglogger") .error( "Shard requestcache enabled for " - + (searchRequest.source() == null ? "" : Strings.toString(XContentType.JSON, searchRequest.source())) + + (searchRequest.source() == null + ? "" + : Strings.toString(MediaTypeRegistry.JSON, searchRequest.source())) ); } diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 7d8a8a7b0b..b6b1f2fe84 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -109,7 +109,7 @@ import org.opensearch.client.indices.GetIndexResponse; import org.opensearch.client.transport.NoNodeAvailableException; import org.opensearch.cluster.health.ClusterHealthStatus; -import org.opensearch.common.Strings; +import org.opensearch.core.common.Strings; import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.collect.Tuple; import org.opensearch.common.settings.Settings; @@ -118,6 +118,7 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.common.xcontent.json.JsonXContent; import org.opensearch.core.xcontent.MediaType; +import org.opensearch.core.xcontent.MediaTypeRegistry; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; @@ -1240,7 +1241,7 @@ private static String convertToYaml(String type, BytesReference bytes, boolean p builder.prettyPrint(); } builder.rawValue(new ByteArrayInputStream(parser.binaryValue()), XContentType.YAML); - return Strings.toString(builder); + return builder.toString(); } } @@ -1267,7 +1268,7 @@ protected static void generateDiagnoseTrace(final RestHighLevelClient restHighLe try { sb.append("ClusterHealthRequest:" + System.lineSeparator()); ClusterHealthResponse nir = restHighLevelClient.cluster().health(new ClusterHealthRequest(), RequestOptions.DEFAULT); - sb.append(Strings.toString(XContentType.JSON, nir, true, true)); + sb.append(Strings.toString(MediaTypeRegistry.JSON, nir, true, true)); } catch (Exception e1) { sb.append(ExceptionsHelper.stackTrace(e1)); } diff --git a/src/main/java/org/opensearch/security/user/UserService.java b/src/main/java/org/opensearch/security/user/UserService.java index 0653948a38..bf2e3e0273 100644 --- a/src/main/java/org/opensearch/security/user/UserService.java +++ b/src/main/java/org/opensearch/security/user/UserService.java @@ -13,10 +13,12 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.Random; import java.util.stream.Collectors; import com.fasterxml.jackson.core.JsonProcessingException; @@ -31,6 +33,7 @@ import org.opensearch.action.support.WriteRequest; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.Randomness; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentHelper; @@ -43,6 +46,9 @@ import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.SecurityJsonNode; +import org.passay.CharacterRule; +import org.passay.EnglishCharacterData; +import org.passay.PasswordGenerator; import static org.opensearch.security.dlic.rest.support.Utils.hash; @@ -204,13 +210,28 @@ private void verifyServiceAccount(SecurityJsonNode securityJsonNode, String acco } /** - * This will be swapped in for a real solution once one is decided on. + * Use Passay to generate an 8 - 16 character password with 1+ lowercase, 1+ uppercase, 1+ digit, 1+ special character * * @return A password for a service account. */ - private String generatePassword() { - String generatedPassword = "superSecurePassword"; - return generatedPassword; + public static String generatePassword() { + + CharacterRule lowercaseCharacterRule = new CharacterRule(EnglishCharacterData.LowerCase, 1); + CharacterRule uppercaseCharacterRule = new CharacterRule(EnglishCharacterData.UpperCase, 1); + CharacterRule numericCharacterRule = new CharacterRule(EnglishCharacterData.Digit, 1); + CharacterRule specialCharacterRule = new CharacterRule(EnglishCharacterData.Special, 1); + + List rules = Arrays.asList( + lowercaseCharacterRule, + uppercaseCharacterRule, + numericCharacterRule, + specialCharacterRule + ); + PasswordGenerator passwordGenerator = new PasswordGenerator(); + + Random random = Randomness.get(); + + return passwordGenerator.generatePassword(random.nextInt(8) + 8, rules); } /** diff --git a/src/test/java/org/opensearch/security/ConfigTests.java b/src/test/java/org/opensearch/security/ConfigTests.java index c64ca55318..7fc12ef92d 100644 --- a/src/test/java/org/opensearch/security/ConfigTests.java +++ b/src/test/java/org/opensearch/security/ConfigTests.java @@ -27,9 +27,9 @@ import org.junit.Assert; import org.junit.Test; -import org.opensearch.common.Strings; import org.opensearch.common.collect.Tuple; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.common.Strings; import org.opensearch.security.securityconf.Migration; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; diff --git a/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java b/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java index 8cc19fa0f5..33dad63e5f 100644 --- a/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java +++ b/src/test/java/org/opensearch/security/auditlog/config/AuditConfigSerializeTest.java @@ -24,7 +24,6 @@ import org.junit.Before; import org.junit.Test; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.xcontent.XContentBuilder; @@ -88,7 +87,7 @@ public void testDefaultSerialize() throws IOException { .endObject() .endObject(); - assertTrue(compareJson(Strings.toString(jsonBuilder), json)); + assertTrue(compareJson(jsonBuilder.toString(), json)); } @Test @@ -148,7 +147,7 @@ public void testDeserialize() throws IOException { .field("write_ignore_users", Collections.singletonList("test-user-3")) .endObject() .endObject(); - final String json = Strings.toString(jsonBuilder); + final String json = jsonBuilder.toString(); // act final AuditConfig auditConfig = objectMapper.readValue(json, AuditConfig.class); @@ -246,7 +245,7 @@ public void testSerialize() throws IOException { // act final String json = objectMapper.writeValueAsString(auditConfig); // assert - assertTrue(compareJson(Strings.toString(jsonBuilder), json)); + assertTrue(compareJson(jsonBuilder.toString(), json)); } @Test @@ -288,7 +287,7 @@ public void testNullSerialize() throws IOException { // act final String json = objectMapper.writeValueAsString(auditConfig); // assert - assertTrue(compareJson(Strings.toString(jsonBuilder), json)); + assertTrue(compareJson(jsonBuilder.toString(), json)); } @Test @@ -348,7 +347,7 @@ public void testCustomSettings() throws IOException { .field("write_log_diffs", false) .endObject() .endObject(); - final String json = Strings.toString(jsonBuilder); + final String json = jsonBuilder.toString(); // act final AuditConfig auditConfig = customObjectMapper.readValue(json, AuditConfig.class); diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java index 48f63ec555..d43a804d47 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java @@ -20,8 +20,8 @@ import org.opensearch.action.search.SearchRequest; import org.opensearch.action.support.WriteRequest.RefreshPolicy; import org.opensearch.client.Client; -import org.opensearch.common.Strings; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.common.Strings; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; public class DlsTest extends AbstractDlsFlsTest { diff --git a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFlatTests.java b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFlatTests.java index e4b3063138..4d3e2d1846 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFlatTests.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/FlsFlatTests.java @@ -18,7 +18,6 @@ import org.opensearch.action.index.IndexRequest; import org.opensearch.action.support.WriteRequest.RefreshPolicy; import org.opensearch.client.Client; -import org.opensearch.common.Strings; import org.opensearch.common.xcontent.XContentType; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.security.test.DynamicSecurityConfig; @@ -65,7 +64,7 @@ protected void populateData(final Client tc) { } catch (final Exception e) { throw new RuntimeException(e); } - final String mappings = Strings.toString(builder); + final String mappings = builder.toString(); final Consumer createIndexWithMapping = (indexName) -> { final CreateIndexRequest createIndex = new CreateIndexRequest(indexName); 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 707dbe614e..659074f216 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 @@ -29,10 +29,17 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; +import org.opensearch.security.user.UserService; +import org.passay.CharacterCharacteristicsRule; +import org.passay.CharacterRule; +import org.passay.EnglishCharacterData; +import org.passay.LengthRule; +import org.passay.PasswordData; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertNotEquals; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; import static org.opensearch.security.dlic.rest.api.InternalUsersApiAction.RESTRICTED_FROM_USERNAME; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; @@ -1004,4 +1011,30 @@ public void checkNullElementsInArray() throws Exception { Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); } + @Test + public void testGeneratedPasswordContents() { + String password = UserService.generatePassword(); + PasswordData data = new PasswordData(password); + + LengthRule lengthRule = new LengthRule(8, 16); + + CharacterCharacteristicsRule characteristicsRule = new CharacterCharacteristicsRule(); + + // Define M (3 in this case) + characteristicsRule.setNumberOfCharacteristics(3); + + // Define elements of N (upper, lower, digit, symbol) + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.UpperCase, 1)); + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.LowerCase, 1)); + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.Digit, 1)); + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.Special, 1)); + + org.passay.PasswordValidator validator = new org.passay.PasswordValidator(lengthRule, characteristicsRule); + validator.validate(data); + + String password2 = UserService.generatePassword(); + PasswordData data2 = new PasswordData(password2); + assertNotEquals(password, password2); + assertNotEquals(data, data2); + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java b/src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java index 55b8664188..a64de6ddeb 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java @@ -21,7 +21,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.common.bytes.BytesArray; @@ -301,7 +300,7 @@ public Map allowedKeys() { private JsonNode xContentToJsonNode(final ToXContent toXContent) throws IOException { try (final var xContentBuilder = XContentFactory.jsonBuilder()) { toXContent.toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS); - return DefaultObjectMapper.readTree(Strings.toString(xContentBuilder)); + return DefaultObjectMapper.readTree(xContentBuilder.toString()); } }