diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7245b0195..ebcc316bcf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,55 +8,69 @@ env: jobs: build: name: build - runs-on: ubuntu-latest strategy: fail-fast: false matrix: jdk: [8, 11, 14] + platform: ["ubuntu-latest", "windows-latest"] + runs-on: ${{ matrix.platform }} steps: - - name: Set up JDK + - name: Set up JDK for build and test on 8 and 11 + if: matrix.jdk != '14' + uses: actions/setup-java@v2 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: ${{ matrix.jdk }} + + - name: Set up JDK for build and test on 14 + if: matrix.jdk == '14' uses: actions/setup-java@v1 with: + distribution: temurin java-version: ${{ matrix.jdk }} - name: Checkout security uses: actions/checkout@v2 - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + - name: Build and Test + uses: gradle/gradle-build-action@v2 with: - languages: java + arguments: | + build test -Dbuild.snapshot=false + -x checkstyleMain + -x checkstyleTest + + - name: Coverage + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./build/reports/jacoco/test/jacocoTestReport.xml - - name: Cache Gradle packages - uses: actions/cache@v2 + - uses: actions/upload-artifact@v3 + if: always() with: + name: ${{ matrix.platform }}-JDK${{ matrix.jdk }}-reports path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - - name: Checkstyle - run: ./gradlew clean checkstyleMain checkstyleTest - - - name: Package - run: ./gradlew clean build -Dbuild.snapshot=false -x test - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + ./build/reports/ - - name: Test - run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test -i + - name: check archive for debugging + if: always() + run: echo "Check the artifact ${{ matrix.platform }}-JDK${{ matrix.jdk }}-reports for detailed test results" - - name: Coverage - uses: codecov/codecov-action@v1 + code-ql: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./build/jacoco/test/jacocoTestReport.xml + java-version: 11 + - uses: github/codeql-action/init@v1 + with: + languages: java + - run: ./gradlew clean build -Dbuild.snapshot=false -x test + - uses: github/codeql-action/analyze@v1 build-artifact-names: runs-on: ubuntu-latest @@ -72,18 +86,15 @@ jobs: security_plugin_version_no_snapshot=$(echo $security_plugin_version | sed 's/-SNAPSHOT//g') security_plugin_version_only_number=$(echo $security_plugin_version_no_snapshot | cut -d- -f1) test_qualifier=alpha2 - echo "SECURITY_PLUGIN_VERSION=$security_plugin_version" >> $GITHUB_ENV echo "SECURITY_PLUGIN_VERSION_NO_SNAPSHOT=$security_plugin_version_no_snapshot" >> $GITHUB_ENV echo "SECURITY_PLUGIN_VERSION_ONLY_NUMBER=$security_plugin_version_only_number" >> $GITHUB_ENV echo "TEST_QUALIFIER=$test_qualifier" >> $GITHUB_ENV - - run: | echo ${{ env.SECURITY_PLUGIN_VERSION }} echo ${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }} echo ${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }} echo ${{ env.TEST_QUALIFIER }} - - run: ./gradlew clean assemble && test -s ./build/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.jar - run: ./gradlew clean assemble -Dbuild.snapshot=false && test -s ./build/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.jar @@ -96,10 +107,9 @@ jobs: ## EXISTING_OS_VERSION outputs the major version, example as 2 EXISTING_OS_VERSION=$(./gradlew properties | grep opensearch.version | cut -d':' -f2- | awk '{$1=$1};1' | cut -d '-' -f1 | cut -d '.' -f1) ## INCREMENT_OS_VERSION in an increment of 1, example if EXISTING_OS_VERSION is 2, INCREMENT_OS_VERSION is 3 - INCREMENT_OS_VERSION=$((++EXISTING_OS_VERSION)) + INCREMENT_OS_VERSION=$((++EXISTING_OS_VERSION)) ./gradlew clean updateVersion -DnewVersion=$INCREMENT_OS_VERSION.0.0-SNAPSHOT test `./gradlew properties | grep opensearch.version | cut -d':' -f2- | awk '{$1=$1};1'` = $INCREMENT_OS_VERSION.0.0-SNAPSHOT - - name: List files in the build directory if there was an error run: ls -al ./build/ if: failure() diff --git a/build.gradle b/build.gradle index 4162538228..b91a25c390 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,7 @@ plugins { id "nebula.ospackage" version "9.0.0" id "com.google.osdetector" version "1.7.0" id "org.gradle.test-retry" version "1.3.1" + id "com.github.spotbugs" version "5.0.13" } import org.gradle.crypto.checksum.Checksum @@ -164,6 +165,7 @@ publishing { tasks.withType(JavaCompile) { options.encoding = 'UTF-8' + options.warnings = false } static def getTimestamp() { @@ -223,6 +225,13 @@ testsJar { libsDirName = '.' } +spotbugs { + includeFilter = file('spotbugs-include.xml') +} + +spotbugsTest { + enabled = false +} test { maxParallelForks = 3 diff --git a/spotbugs-include.xml b/spotbugs-include.xml new file mode 100644 index 0000000000..dd6062700d --- /dev/null +++ b/spotbugs-include.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/AuthTokenProcessorHandler.java b/src/main/java/com/amazon/dlic/auth/http/saml/AuthTokenProcessorHandler.java index 25c547e4e2..c2791a02b8 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/AuthTokenProcessorHandler.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/AuthTokenProcessorHandler.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; @@ -155,7 +156,7 @@ private AuthTokenProcessorAction.Response handleImpl(RestRequest restRequest, Re SettingsException { if (token_log.isDebugEnabled()) { try { - token_log.debug("SAMLResponse for {}\n{}", samlRequestId, new String(Util.base64decoder(samlResponseBase64), "UTF-8")); + token_log.debug("SAMLResponse for {}\n{}", samlRequestId, new String(Util.base64decoder(samlResponseBase64), StandardCharsets.UTF_8)); } catch (Exception e) { token_log.warn( "SAMLResponse for {} cannot be decoded from base64\n{}", diff --git a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java index 04dd285b5c..bd479c0db2 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java @@ -454,7 +454,7 @@ public void logDocumentWritten(ShardId shardId, GetResult originalResult, Index try (XContentParser parser = XContentHelper.createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, originalResult.internalSourceRef(), XContentType.JSON)) { Object base64 = parser.map().values().iterator().next(); if(base64 instanceof String) { - originalSource = (new String(BaseEncoding.base64().decode((String) base64))); + originalSource = (new String(BaseEncoding.base64().decode((String) base64), StandardCharsets.UTF_8)); } else { originalSource = XContentHelper.convertToJson(originalResult.internalSourceRef(), false, XContentType.JSON); } @@ -465,7 +465,7 @@ public void logDocumentWritten(ShardId shardId, GetResult originalResult, Index try (XContentParser parser = XContentHelper.createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, currentIndex.source(), XContentType.JSON)) { Object base64 = parser.map().values().iterator().next(); if(base64 instanceof String) { - currentSource = (new String(BaseEncoding.base64().decode((String) base64))); + currentSource = new String(BaseEncoding.base64().decode((String) base64), StandardCharsets.UTF_8); } else { currentSource = XContentHelper.convertToJson(currentIndex.source(), false, XContentType.JSON); } @@ -492,7 +492,7 @@ public void logDocumentWritten(ShardId shardId, GetResult originalResult, Index try (XContentParser parser = XContentHelper.createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, currentIndex.source(), XContentType.JSON)) { Object base64 = parser.map().values().iterator().next(); if(base64 instanceof String) { - msg.addSecurityConfigContentToRequestBody(new String(BaseEncoding.base64().decode((String) base64)), id); + msg.addSecurityConfigContentToRequestBody(new String(BaseEncoding.base64().decode((String) base64), StandardCharsets.UTF_8), id); } else { msg.addSecurityConfigTupleToRequestBody(new Tuple(XContentType.JSON, currentIndex.source()), id); } diff --git a/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java b/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java index cf348ab120..def54fb041 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java @@ -1,16 +1,12 @@ /* - * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. */ package org.opensearch.security.auditlog.impl; @@ -433,10 +429,38 @@ public String getRequestType() { return (String) this.auditInfo.get(TRANSPORT_REQUEST_TYPE); } + public RestRequest.Method getRequestMethod() { + return (RestRequest.Method) this.auditInfo.get(REST_REQUEST_METHOD); + } + public AuditCategory getCategory() { return msgCategory; } + public Origin getOrigin() { + return (Origin) this.auditInfo.get(ORIGIN); + } + + public String getPrivilege() { + return (String) this.auditInfo.get(PRIVILEGE); + } + + public String getExceptionStackTrace() { + return (String) this.auditInfo.get(EXCEPTION); + } + + public String getRequestBody() { + return (String) this.auditInfo.get(REQUEST_BODY); + } + + public String getNodeId() { + return (String) this.auditInfo.get(NODE_ID); + } + + public String getDocId() { + return (String) this.auditInfo.get(ID); + } + @Override public String toString() { try { diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java index 0043373c47..8b98b1e039 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java @@ -31,6 +31,7 @@ package org.opensearch.security.configuration; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -275,7 +276,7 @@ private SecurityDynamicConfiguration toConfig(GetResponse singleGetResponse, parser.nextToken(); - final String jsonAsString = SecurityUtils.replaceEnvVars(new String(parser.binaryValue()), settings); + final String jsonAsString = SecurityUtils.replaceEnvVars(new String(parser.binaryValue(), StandardCharsets.UTF_8), settings); final JsonNode jsonNode = DefaultObjectMapper.readTree(jsonAsString); int configVersion = 1; diff --git a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java index c84e8a05de..982364fd73 100644 --- a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java @@ -28,6 +28,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.File; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; diff --git a/src/main/java/org/opensearch/security/support/ConfigHelper.java b/src/main/java/org/opensearch/security/support/ConfigHelper.java index 17f6ea5135..de3e772d8b 100644 --- a/src/main/java/org/opensearch/security/support/ConfigHelper.java +++ b/src/main/java/org/opensearch/security/support/ConfigHelper.java @@ -31,10 +31,13 @@ package org.opensearch.security.support; import java.io.File; +import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; +import java.nio.charset.StandardCharsets; import org.opensearch.security.securityconf.impl.Meta; import org.apache.logging.log4j.Logger; @@ -96,7 +99,7 @@ public static void uploadFile(Client tc, String filepath, String index, CType cT public static Reader createFileOrStringReader(CType cType, int configVersion, String filepath, boolean populateEmptyIfFileMissing) throws Exception { Reader reader; if (!populateEmptyIfFileMissing || new File(filepath).exists()) { - reader = new FileReader(filepath); + reader = new InputStreamReader(new FileInputStream(filepath), StandardCharsets.UTF_8); } else { reader = new StringReader(createEmptySdcYaml(cType, configVersion)); } @@ -148,7 +151,7 @@ public static SecurityDynamicConfiguration fromYamlReader(Reader yamlRead } public static SecurityDynamicConfiguration fromYamlFile(String filepath, CType ctype, int version, long seqNo, long primaryTerm) throws IOException { - return fromYamlReader(new FileReader(filepath), ctype, version, seqNo, primaryTerm); + return fromYamlReader(new InputStreamReader(new FileInputStream(filepath), StandardCharsets.UTF_8), ctype, version, seqNo, primaryTerm); } public static SecurityDynamicConfiguration fromYamlString(String yamlString, CType ctype, int version, long seqNo, long primaryTerm) throws IOException { diff --git a/src/main/java/org/opensearch/security/support/SecurityUtils.java b/src/main/java/org/opensearch/security/support/SecurityUtils.java index 2da1d0e252..61b3eb0242 100644 --- a/src/main/java/org/opensearch/security/support/SecurityUtils.java +++ b/src/main/java/org/opensearch/security/support/SecurityUtils.java @@ -50,9 +50,10 @@ public final class SecurityUtils { protected final static Logger log = LogManager.getLogger(SecurityUtils.class); - private static final Pattern ENV_PATTERN = Pattern.compile("\\$\\{env\\.([\\w]+)((\\:\\-)?[\\w]*)\\}"); - private static final Pattern ENVBC_PATTERN = Pattern.compile("\\$\\{envbc\\.([\\w]+)((\\:\\-)?[\\w]*)\\}"); - private static final Pattern ENVBASE64_PATTERN = Pattern.compile("\\$\\{envbase64\\.([\\w]+)((\\:\\-)?[\\w]*)\\}"); + private static final String ENV_PATTERN_SUFFIX = "\\.([\\w=():\\-_.]+?)(\\:\\-[\\w=():\\-_.]*)?\\}"; + static final Pattern ENV_PATTERN = Pattern.compile("\\$\\{env" + ENV_PATTERN_SUFFIX); + static final Pattern ENVBC_PATTERN = Pattern.compile("\\$\\{envbc" + ENV_PATTERN_SUFFIX); + static final Pattern ENVBASE64_PATTERN = Pattern.compile("\\$\\{envbase64" + ENV_PATTERN_SUFFIX); public static Locale EN_Locale = forEN(); diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index a80f60c560..5681efbf6e 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -33,8 +33,10 @@ import java.io.ByteArrayInputStream; import java.io.Console; import java.io.File; +import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; +import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.net.InetSocketAddress; @@ -911,8 +913,8 @@ private static boolean retrieveFile(final Client tc, final String filepath, fina } - System.out.println("Will retrieve '"+type+"/" +id+"' into "+filepath+" "+(legacy?"(legacy mode)":"")); - try (Writer writer = new FileWriter(filepath)) { + System.out.println("Will retrieve '"+"/" +id+"' into "+filepath+" "+(legacy?"(legacy mode)":"")); + try (Writer writer = new OutputStreamWriter(new FileOutputStream(filepath), StandardCharsets.UTF_8)) { final GetResponse response = tc.get(new GetRequest(index).type(type).id(id).refresh(true).realtime(false)).actionGet(); diff --git a/src/test/java/org/opensearch/security/auditlog/AuditLogTestSuite.java b/src/test/java/org/opensearch/security/auditlog/AuditLogTestSuite.java deleted file mode 100644 index 9c3be2c528..0000000000 --- a/src/test/java/org/opensearch/security/auditlog/AuditLogTestSuite.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package org.opensearch.security.auditlog; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -import org.opensearch.security.auditlog.compliance.ComplianceAuditlogTest; -import org.opensearch.security.auditlog.compliance.RestApiComplianceAuditlogTest; -import org.opensearch.security.auditlog.impl.AuditlogTest; -import org.opensearch.security.auditlog.impl.DelegateTest; -import org.opensearch.security.auditlog.impl.DisabledCategoriesTest; -import org.opensearch.security.auditlog.impl.IgnoreAuditUsersTest; -import org.opensearch.security.auditlog.impl.TracingTests; -import org.opensearch.security.auditlog.integration.BasicAuditlogTest; -import org.opensearch.security.auditlog.integration.SSLAuditlogTest; -import org.opensearch.security.auditlog.routing.FallbackTest; -import org.opensearch.security.auditlog.routing.RouterTest; -import org.opensearch.security.auditlog.routing.RoutingConfigurationTest; -import org.opensearch.security.auditlog.sink.KafkaSinkTest; -import org.opensearch.security.auditlog.sink.SinkProviderTLSTest; -import org.opensearch.security.auditlog.sink.SinkProviderTest; -import org.opensearch.security.auditlog.sink.WebhookAuditLogTest; - -@RunWith(Suite.class) - -@Suite.SuiteClasses({ - ComplianceAuditlogTest.class, - RestApiComplianceAuditlogTest.class, - AuditlogTest.class, - DelegateTest.class, - DisabledCategoriesTest.class, - IgnoreAuditUsersTest.class, - TracingTests.class, - BasicAuditlogTest.class, - SSLAuditlogTest.class, - FallbackTest.class, - RouterTest.class, - RoutingConfigurationTest.class, - SinkProviderTest.class, - SinkProviderTLSTest.class, - WebhookAuditLogTest.class, - KafkaSinkTest.class -}) -public class AuditLogTestSuite { - -} diff --git a/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java index 4f6d1444e8..5ae312f248 100644 --- a/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java @@ -1,56 +1,60 @@ /* - * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. */ package org.opensearch.security.auditlog.compliance; -import org.opensearch.security.auditlog.AuditTestUtils; -import org.opensearch.security.auditlog.config.AuditConfig; -import org.opensearch.security.compliance.ComplianceConfig; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + import com.google.common.collect.ImmutableMap; import org.apache.http.Header; import org.apache.http.HttpStatus; -import org.opensearch.action.index.IndexRequest; -import org.opensearch.action.support.WriteRequest.RefreshPolicy; import org.opensearch.client.transport.TransportClient; -import org.opensearch.common.settings.Settings; import org.junit.Assert; import org.junit.Test; -import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest.RefreshPolicy; +import org.opensearch.common.settings.Settings; import org.opensearch.security.auditlog.AbstractAuditlogiUnitTest; +import org.opensearch.security.auditlog.AuditTestUtils; +import org.opensearch.security.auditlog.AuditLog.Origin; +import org.opensearch.security.auditlog.config.AuditConfig; +import org.opensearch.security.auditlog.impl.AuditCategory; +import org.opensearch.security.auditlog.impl.AuditMessage; import org.opensearch.security.auditlog.integration.TestAuditlogImpl; +import org.opensearch.security.auditlog.integration.TestAuditlogImpl.MessagesNotFoundException; +import org.opensearch.security.compliance.ComplianceConfig; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.DynamicSecurityConfig; -import org.opensearch.security.test.helper.rest.RestHelper; - -import java.util.Collections; +import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.core.AnyOf.anyOf; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThrows; public class ComplianceAuditlogTest extends AbstractAuditlogiUnitTest { @Test public void testSourceFilter() throws Exception { - Settings additionalSettings = Settings.builder() .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) - //.put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, "emp") .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, "emp") .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") @@ -61,13 +65,12 @@ public void testSourceFilter() throws Exception { final String keystore = rh.keystore; rh.sendAdminCertificate = true; rh.keystore = "auditlog/kirk-keystore.jks"; - rh.executePutRequest("emp/doc/0?refresh", "{\"Designation\" : \"CEO\", \"Gender\" : \"female\", \"Salary\" : 100}", new Header[0]); - rh.executePutRequest("emp/doc/1?refresh", "{\"Designation\" : \"IT\", \"Gender\" : \"male\", \"Salary\" : 200}", new Header[0]); - rh.executePutRequest("emp/doc/2?refresh", "{\"Designation\" : \"IT\", \"Gender\" : \"female\", \"Salary\" : 300}", new Header[0]); + rh.executePutRequest("emp/_doc/0?refresh", "{\"Designation\" : \"CEO\", \"Gender\" : \"female\", \"Salary\" : 100}", new Header[0]); + rh.executePutRequest("emp/_doc/1?refresh", "{\"Designation\" : \"IT\", \"Gender\" : \"male\", \"Salary\" : 200}", new Header[0]); + rh.executePutRequest("emp/_doc/2?refresh", "{\"Designation\" : \"IT\", \"Gender\" : \"female\", \"Salary\" : 300}", new Header[0]); rh.sendAdminCertificate = sendAdminCertificate; rh.keystore = keystore; - System.out.println("#### test source includes"); String search = "{" + " \"_source\":[" + " \"Gender\""+ @@ -81,17 +84,16 @@ public void testSourceFilter() throws Exception { " }" + "}"; - TestAuditlogImpl.clear(); - HttpResponse response = rh.executePostRequest("_search?pretty", search, encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - System.out.println(response.getBody()); - Thread.sleep(1500); - System.out.println(TestAuditlogImpl.sb.toString()); - Assert.assertTrue(TestAuditlogImpl.messages.size() >= 1); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_DOC_READ")); - Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("Designation")); - Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("Salary")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("Gender")); + final AuditMessage message = TestAuditlogImpl.doThenWaitForMessage(() -> { + final HttpResponse response = rh.executePostRequest("_search?pretty", search, encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + }); + + assertThat(message.getCategory(), equalTo(AuditCategory.COMPLIANCE_DOC_READ)); + assertThat(message.getRequestBody(), not(containsString("Designation"))); + assertThat(message.getRequestBody(), not(containsString("Salary"))); + assertThat(message.getRequestBody(), containsString("Gender")); + Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } @@ -103,8 +105,6 @@ public void testComplianceEnable() throws Exception { setup(additionalSettings); - final boolean sendAdminCertificate = rh.sendAdminCertificate; - final String keystore = rh.keystore; rh.sendAdminCertificate = true; rh.keystore = "auditlog/kirk-keystore.jks"; @@ -113,21 +113,38 @@ public void testComplianceEnable() throws Exception { updateAuditConfig(AuditTestUtils.createAuditPayload(auditConfig)); // make an event happen - TestAuditlogImpl.clear(); - rh.executePutRequest("emp/doc/0?refresh", "{\"Designation\" : \"CEO\", \"Gender\" : \"female\", \"Salary\" : 100}"); - assertTrue(TestAuditlogImpl.messages.toString().contains("COMPLIANCE_DOC_WRITE")); + List messages; + try { + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + rh.executePutRequest("emp/_doc/0?refresh", "{\"Designation\" : \"CEO\", \"Gender\" : \"female\", \"Salary\" : 100}"); + System.out.println(rh.executeGetRequest("_cat/shards?v")); + }, 7); + } catch (final MessagesNotFoundException ex) { + // indices:admin/mapping/auto_put can be logged twice, this handles if they were not found + assertThat("Too many missing audit log messages", ex.getMissingCount(), equalTo(2)); + messages = ex.getFoundMessages(); + } + + messages.stream().filter(msg -> msg.getCategory().equals(AuditCategory.COMPLIANCE_DOC_WRITE)) + .findFirst().orElseThrow(() -> new RuntimeException("Missing COMPLIANCE message")); + final List indexCreation = messages.stream().filter(msg -> "indices:admin/auto_create".equals(msg.getPrivilege())).collect(Collectors.toList()); + assertThat(indexCreation.size(), equalTo(2)); + + final List mappingCreation = messages.stream().filter(msg -> "indices:admin/mapping/auto_put".equals(msg.getPrivilege())).collect(Collectors.toList()); + assertThat(mappingCreation.size(), anyOf(equalTo(4), equalTo(2))); + // disable compliance auditConfig = new AuditConfig(true, AuditConfig.Filter.DEFAULT , ComplianceConfig.from(ImmutableMap.of("enabled", false, "write_watched_indices", Collections.singletonList("emp")), additionalSettings)); updateAuditConfig(AuditTestUtils.createAuditPayload(auditConfig)); - // make an event happen - TestAuditlogImpl.clear(); - rh.executePutRequest("emp/doc/1?refresh", "{\"Designation\" : \"CEO\", \"Gender\" : \"female\", \"Salary\" : 100}"); - assertFalse(TestAuditlogImpl.messages.toString().contains("COMPLIANCE_DOC_WRITE")); - - rh.sendAdminCertificate = sendAdminCertificate; - rh.keystore = keystore; + // trigger an event that it not captured by the audit log + final MessagesNotFoundException ex = assertThrows(MessagesNotFoundException.class, () -> { + TestAuditlogImpl.doThenWaitForMessage(() -> { + rh.executePutRequest("emp/_doc/1?refresh", "{\"Designation\" : \"CEO\", \"Gender\" : \"female\", \"Salary\" : 100}"); + }); + }); + assertThat(ex.getMissingCount(), equalTo(1)); } @Test @@ -149,13 +166,12 @@ public void testSourceFilterMsearch() throws Exception { final String keystore = rh.keystore; rh.sendAdminCertificate = true; rh.keystore = "auditlog/kirk-keystore.jks"; - rh.executePutRequest("emp/doc/0?refresh", "{\"Designation\" : \"CEO\", \"Gender\" : \"female\", \"Salary\" : 100}", new Header[0]); - rh.executePutRequest("emp/doc/1?refresh", "{\"Designation\" : \"IT\", \"Gender\" : \"male\", \"Salary\" : 200}", new Header[0]); - rh.executePutRequest("emp/doc/2?refresh", "{\"Designation\" : \"IT\", \"Gender\" : \"female\", \"Salary\" : 300}", new Header[0]); + rh.executePutRequest("emp/_doc/0?refresh", "{\"Designation\" : \"CEO\", \"Gender\" : \"female\", \"Salary\" : 100}", new Header[0]); + rh.executePutRequest("emp/_doc/1?refresh", "{\"Designation\" : \"IT\", \"Gender\" : \"male\", \"Salary\" : 200}", new Header[0]); + rh.executePutRequest("emp/_doc/2?refresh", "{\"Designation\" : \"IT\", \"Gender\" : \"female\", \"Salary\" : 300}", new Header[0]); rh.sendAdminCertificate = sendAdminCertificate; rh.keystore = keystore; - System.out.println("#### test source includes"); String search = "{}"+System.lineSeparator() + "{" + " \"_source\":[" + @@ -184,18 +200,24 @@ public void testSourceFilterMsearch() throws Exception { " }" + "}"+System.lineSeparator(); - TestAuditlogImpl.clear(); - HttpResponse response = rh.executePostRequest("_msearch?pretty", search, encodeBasicHeader("admin", "admin")); - assertNotContains(response, "*exception*"); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Thread.sleep(1500); - System.out.println(TestAuditlogImpl.sb.toString()); - Assert.assertTrue("Was "+TestAuditlogImpl.messages.size(), TestAuditlogImpl.messages.size() == 2); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_DOC_READ")); - Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("Salary")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("Gender")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("Designation")); - Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); + final List messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + HttpResponse response = rh.executePostRequest("_msearch?pretty", search, encodeBasicHeader("admin", "admin")); + assertNotContains(response, "*exception*"); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + }, 2); + + + final AuditMessage desginationMsg = messages.stream().filter(msg -> msg.getRequestBody().contains("Designation")).findFirst().get(); + assertThat(desginationMsg.getCategory(), equalTo(AuditCategory.COMPLIANCE_DOC_READ)); + assertThat(desginationMsg.getRequestBody(), containsString("Designation")); + assertThat(desginationMsg.getRequestBody(), not(containsString("Salary"))); + + final AuditMessage genderMsg = messages.stream().filter(msg -> msg.getRequestBody().contains("Gender")).findFirst().get(); + assertThat(genderMsg.getCategory(), equalTo(AuditCategory.COMPLIANCE_DOC_READ)); + assertThat(genderMsg.getRequestBody(), containsString("Gender")); + assertThat(genderMsg.getRequestBody(), not(containsString("Salary"))); + + Assert.assertTrue(validateMsgs(messages)); } @Test @@ -213,42 +235,40 @@ public void testInternalConfig() throws Exception { .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") .build(); - TestAuditlogImpl.clear(); setup(additionalSettings); - try (TransportClient tc = getInternalTransportClient()) { - for(IndexRequest ir: new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { - tc.index(ir).actionGet(); + final List expectedDocumentsTypes = Arrays.asList("config", "actiongroups", "internalusers", "roles", "rolesmapping", "tenants", "audit"); + final List messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + try (TransportClient tc = getInternalTransportClient()) { + for(IndexRequest ir: new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { + tc.index(ir).actionGet(); + } + } - } + HttpResponse response = rh.executeGetRequest("_search?pretty", encodeBasicHeader("admin", "admin")); + assertThat(response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + }, 7); - HttpResponse response = rh.executeGetRequest("_search?pretty", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Thread.sleep(1500); - System.out.println(TestAuditlogImpl.sb.toString()); - Assert.assertTrue(TestAuditlogImpl.messages.size() > 25); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_INTERNAL_CONFIG_READ")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_INTERNAL_CONFIG_WRITE")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("anonymous_auth_enabled")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("indices:data/read/suggest")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("internalusers")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("opendistro_security_all_access")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("indices:data/read/suggest")); - Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("eyJzZWFyY2hndWFy")); - Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("eyJBTEwiOlsiaW")); - Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("eyJhZG1pbiI6e")); - Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("eyJzZ19hb")); - Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("eyJzZ19hbGx")); - Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("dvcmYiOnsiY2x")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\\\"op\\\":\\\"remove\\\",\\\"path\\\":\\\"/opendistro_security_worf\\\"")); - Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); + final List documentIds = messages.stream().map(AuditMessage::getDocId).distinct().collect(Collectors.toList()); + assertThat(documentIds, equalTo(expectedDocumentsTypes)); + + messages.stream().collect(Collectors.groupingBy(AuditMessage::getDocId)).entrySet().forEach((e) -> { + final String docId = e.getKey(); + final List messagesByDocId = e.getValue(); + assertThat("Doc " + docId + " should have a write config message", + messagesByDocId.stream().map(AuditMessage::getCategory).collect(Collectors.toList()), + equalTo(Arrays.asList(AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE)) + ); + }); + + Assert.assertTrue(validateMsgs(messages)); } @Test public void testExternalConfig() throws Exception { - Settings additionalSettings = Settings.builder() + final Settings additionalSettings = Settings.builder() .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) @@ -259,27 +279,39 @@ public void testExternalConfig() throws Exception { .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES") .build(); - TestAuditlogImpl.clear(); - - setup(additionalSettings); + final List allMessages = TestAuditlogImpl.doThenWaitForMessages(() -> { + try { + setup(additionalSettings); + } catch (final Exception ex) { + throw new RuntimeException(ex); + } - try (TransportClient tc = getInternalTransportClient()) { + try (TransportClient tc = getInternalTransportClient()) { - for(IndexRequest ir: new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { - tc.index(ir).actionGet(); + for(IndexRequest ir: new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) { + tc.index(ir).actionGet(); + } + } - } + final HttpResponse response = rh.executeGetRequest("_search?pretty", encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + }, 18); - HttpResponse response = rh.executeGetRequest("_search?pretty", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - System.out.println(response.getBody()); - Thread.sleep(1500); - System.out.println(TestAuditlogImpl.sb.toString()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("external_configuration")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_EXTERNAL_CONFIG")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("opensearch_yml")); - Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); + // Filter out transport layer messages + final List messages = allMessages.stream().filter(msg -> msg.getOrigin() != Origin.TRANSPORT).collect(Collectors.toList()); + + // Record the updated config, and then for each node record that the config was updated + assertThat(messages.get(0).getCategory(), equalTo(AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE)); + assertThat(messages.get(1).getCategory(), equalTo(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG)); + assertThat(messages.get(2).getCategory(), equalTo(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG)); + assertThat(messages.get(3).getCategory(), equalTo(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG)); + + // Make sure that the config update messsages are for each node in the cluster + assertThat(messages.get(1).getNodeId(), not(equalTo(messages.get(2).getNodeId()))); + assertThat(messages.get(2).getNodeId(), not(equalTo(messages.get(3).getNodeId()))); + + Assert.assertTrue(validateMsgs(messages)); } @Test @@ -307,68 +339,29 @@ public void testUpdate() throws Exception { .actionGet(); } - TestAuditlogImpl.clear(); + final MessagesNotFoundException ex1 = assertThrows(MessagesNotFoundException.class, () -> { + TestAuditlogImpl.doThenWaitForMessage(() -> { + final String body = "{\"doc\": {\"Age\":123}}"; + final HttpResponse response = rh.executePostRequest("humanresources/_doc/100?pretty", body, encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + }); + }); + assertThat(ex1.getMissingCount(), equalTo(1)); + + + final MessagesNotFoundException ex2 = assertThrows(MessagesNotFoundException.class, () -> { + TestAuditlogImpl.doThenWaitForMessage(() -> { + final String body = "{\"doc\": {\"Age\":456}}"; + final HttpResponse response = rh.executePostRequest("humanresources/_update/100?pretty", body, encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + }); + }); + assertThat(ex2.getMissingCount(), equalTo(1)); - String body = "{\"doc\": {\"Age\":123}}"; - - HttpResponse response = rh.executePostRequest("humanresources/employees/100/_update?pretty", body, encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - System.out.println(response.getBody()); - Thread.sleep(1500); Assert.assertTrue(TestAuditlogImpl.messages.isEmpty()); Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } - @Test - public void testUpdatePerf() throws Exception { - - Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false) - .put(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, false) - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, "humanresources") - .put(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, "humanresources,*") - .build(); - - setup(additionalSettings); - TestAuditlogImpl.clear(); - - /*try (TransportClient tc = getInternalTransportClient()) { - for(int i=0; i<5000; i++) { - - tc.prepareIndex("humanresources", "employees") - //.setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .setSource("Age", 456+i) - .execute(); - } - }*/ - - - - for(int i=0; i<1; i++) { - HttpResponse response = rh.executePostRequest("humanresources/employees/"+i+"", "{\"customer\": {\"Age\":"+i+"}}", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - response = rh.executePostRequest("humanresources/employees/"+i+"", "{\"customer\": {\"Age\":"+(i+2)+"}}", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executePostRequest("humanresources/employees/"+i+"/_update?pretty", "{\"doc\": {\"doesel\":"+(i+3)+"}}", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - } - - /*Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - System.out.println(response.getBody()); - Thread.sleep(1500); - Assert.assertTrue(TestAuditlogImpl.messages.isEmpty()); - Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages));*/ - - Thread.sleep(1500); - System.out.println("Messages: "+TestAuditlogImpl.messages.size()); - //System.out.println(TestAuditlogImpl.sb.toString()); - - } - @Test public void testWriteHistory() throws Exception { @@ -392,24 +385,18 @@ public void testWriteHistory() throws Exception { .actionGet(); } - TestAuditlogImpl.clear(); - - String body = "{\"doc\": {\"Age\":123}}"; - - HttpResponse response = rh.executePostRequest("humanresources/employees/100/_update?pretty", body, encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - System.out.println(response.getBody()); - Thread.sleep(1500); - System.out.println(TestAuditlogImpl.sb.toString()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().split(".*audit_compliance_diff_content.*replace.*").length == 2); - - body = "{\"Age\":555}"; - TestAuditlogImpl.clear(); - response = rh.executePostRequest("humanresources/employees/100?pretty", body, encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - System.out.println(response.getBody()); - Thread.sleep(1500); - System.out.println(TestAuditlogImpl.sb.toString()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().split(".*audit_compliance_diff_content.*replace.*").length == 2); + TestAuditlogImpl.doThenWaitForMessage(() -> { + final String body = "{\"doc\": {\"Age\":123}}"; + final HttpResponse response = rh.executePostRequest("humanresources/_doc/100?pretty", body, encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + }); + Assert.assertTrue(TestAuditlogImpl.sb.toString().split(".*audit_compliance_diff_content.*replace.*").length == 1); + + TestAuditlogImpl.doThenWaitForMessage(() -> { + final String body = "{\"doc\": {\"Age\":555}}"; + final HttpResponse response = rh.executePostRequest("humanresources/_update/100?pretty", body, encodeBasicHeader("admin", "admin")); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + }); + Assert.assertTrue(TestAuditlogImpl.sb.toString().split(".*audit_compliance_diff_content.*replace.*").length == 1); } } diff --git a/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java b/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java index 9c436849ae..779e528cf5 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java @@ -1,29 +1,26 @@ /* - * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. */ package org.opensearch.security.auditlog.integration; -import org.opensearch.security.auditlog.AuditTestUtils; -import org.opensearch.security.auditlog.config.AuditConfig; -import org.opensearch.security.auditlog.impl.AuditCategory; -import org.opensearch.security.compliance.ComplianceConfig; +import java.util.Collections; +import java.util.List; + import com.google.common.collect.ImmutableMap; import org.apache.http.Header; import org.apache.http.HttpStatus; -import org.apache.http.NoHttpResponseException; import org.apache.http.message.BasicHeader; +import org.junit.Assert; +import org.junit.Test; + import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest; import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions; import org.opensearch.action.admin.indices.create.CreateIndexRequest; @@ -32,19 +29,28 @@ import org.opensearch.action.search.SearchResponse; import org.opensearch.action.support.WriteRequest.RefreshPolicy; import org.opensearch.client.transport.TransportClient; +import org.opensearch.client.Client; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext.StoredContext; import org.opensearch.common.xcontent.XContentType; -import org.junit.Assert; -import org.junit.Test; - import org.opensearch.security.auditlog.AbstractAuditlogiUnitTest; +import org.opensearch.security.auditlog.AuditLog.Origin; +import org.opensearch.security.auditlog.AuditTestUtils; +import org.opensearch.security.auditlog.config.AuditConfig; +import org.opensearch.security.auditlog.impl.AuditCategory; import org.opensearch.security.auditlog.impl.AuditMessage; +import org.opensearch.security.compliance.ComplianceConfig; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; -import java.util.Collections; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.rest.RestRequest.Method.DELETE; +import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.rest.RestRequest.Method.PATCH; +import static org.opensearch.rest.RestRequest.Method.POST; +import static org.opensearch.rest.RestRequest.Method.PUT; public class BasicAuditlogTest extends AbstractAuditlogiUnitTest { @@ -77,30 +83,29 @@ public void testAuditLogEnable() throws Exception { @Test public void testSimpleAuthenticated() throws Exception { + final Settings settings = Settings.builder() + .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) + .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "NONE") + .build(); + verifyAuthenticated(settings); + } - Settings additionalSettings = Settings.builder() - .put("plugins.security.audit.type", TestAuditlogImpl.class.getName()) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, true) - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, "authenticated") - .put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated") - .build(); + private void verifyAuthenticated(final Settings settings) throws Exception { + setup(settings); - setup(additionalSettings); - setupStarfleetIndex(); - TestAuditlogImpl.clear(); - System.out.println("#### testSimpleAuthenticated"); - HttpResponse response = rh.executeGetRequest("_search", encodeBasicHeader("admin", "admin")); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Thread.sleep(1500); - Assert.assertEquals(1, TestAuditlogImpl.messages.size()); - System.out.println(TestAuditlogImpl.sb.toString()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("GRANTED_PRIVILEGES")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("indices:data/read/search")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("REST")); - Assert.assertFalse(TestAuditlogImpl.sb.toString().toLowerCase().contains("authorization")); - Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); + final List messages = TestAuditlogImpl.doThenWaitForMessages( + () -> { + final HttpResponse response = rh.executeGetRequest("_search", encodeBasicHeader("admin", "admin")); + assertThat(response.getStatusCode(), equalTo(HttpStatus.SC_OK)); + }, + /* expectedCount*/ 1); + + assertThat(messages.size(), equalTo(1)); + + assertThat(messages.get(0).getCategory(), equalTo(AuditCategory.GRANTED_PRIVILEGES)); + assertThat(messages.get(0).getOrigin(), equalTo(Origin.REST)); + assertThat(messages.get(0).getPrivilege(), equalTo("indices:data/read/search")); } @Test @@ -118,22 +123,18 @@ public void testSSLPlainText() throws Exception { .build(); setup(additionalSettings); - TestAuditlogImpl.clear(); - - try { - nonSslRestHelper().executeGetRequest("_search", encodeBasicHeader("admin", "admin")); - Assert.fail(); - } catch (NoHttpResponseException e) { - //expected - } - - Thread.sleep(1500); - System.out.println(TestAuditlogImpl.sb.toString()); - Assert.assertFalse(TestAuditlogImpl.messages.isEmpty()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("SSL_EXCEPTION")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("exception_stacktrace")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("not an SSL/TLS record")); - Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); + final List messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + final RuntimeException ex = Assert.assertThrows(RuntimeException.class, + () -> nonSslRestHelper().executeGetRequest("_search", encodeBasicHeader("admin", "admin"))); + Assert.assertEquals("org.apache.http.NoHttpResponseException", ex.getCause().getClass().getName()); + }, 4); + + // All of the messages should be the same as the http client is attempting multiple times. + messages.stream().forEach((message) -> { + Assert.assertEquals(AuditCategory.SSL_EXCEPTION, message.getCategory()); + Assert.assertTrue(message.getExceptionStackTrace().contains("not an SSL/TLS record")); + }); + Assert.assertTrue(validateMsgs(messages)); } @Test @@ -396,7 +397,7 @@ public void testJustAuthenticated() throws Exception { public void testSecurityIndexAttempt() throws Exception { - HttpResponse response = rh.executePutRequest(".opendistro_security/config/0", "{}", encodeBasicHeader("admin", "admin")); + HttpResponse response = rh.executePutRequest(".opendistro_security/_doc/0", "{}", encodeBasicHeader("admin", "admin")); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("MISSING_PRIVILEGES")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("OPENDISTRO_SECURITY_INDEX_ATTEMPT")); @@ -460,15 +461,15 @@ public void testBulkAuth() throws Exception { System.out.println("#### testBulkAuth"); String bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_type\" : \"type1\", \"_id\" : \"1\" } }"+System.lineSeparator()+ + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"worf\", \"_type\" : \"type1\", \"_id\" : \"2\" } }"+System.lineSeparator()+ + "{ \"index\" : { \"_index\" : \"worf\", \"_id\" : \"2\" } }"+System.lineSeparator()+ "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"update\" : {\"_id\" : \"1\", \"_type\" : \"type1\", \"_index\" : \"test\"} }"+System.lineSeparator()+ + "{ \"update\" : {\"_id\" : \"1\", \"_index\" : \"test\"} }"+System.lineSeparator()+ "{ \"doc\" : {\"field\" : \"valuex\"} }"+System.lineSeparator()+ - "{ \"delete\" : { \"_index\" : \"test\", \"_type\" : \"type1\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"create\" : { \"_index\" : \"test\", \"_type\" : \"type1\", \"_id\" : \"1\" } }"+System.lineSeparator()+ + "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ + "{ \"create\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ "{ \"field1\" : \"value3x\" }"+System.lineSeparator(); @@ -490,15 +491,15 @@ public void testBulkAuth() throws Exception { public void testBulkNonAuth() throws Exception { String bulkBody = - "{ \"index\" : { \"_index\" : \"test\", \"_type\" : \"type1\", \"_id\" : \"1\" } }"+System.lineSeparator()+ + "{ \"index\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ "{ \"field1\" : \"value1\" }" +System.lineSeparator()+ - "{ \"index\" : { \"_index\" : \"worf\", \"_type\" : \"type1\", \"_id\" : \"2\" } }"+System.lineSeparator()+ + "{ \"index\" : { \"_index\" : \"worf\", \"_id\" : \"2\" } }"+System.lineSeparator()+ "{ \"field2\" : \"value2\" }"+System.lineSeparator()+ - "{ \"update\" : {\"_id\" : \"1\", \"_type\" : \"type1\", \"_index\" : \"test\"} }"+System.lineSeparator()+ + "{ \"update\" : {\"_id\" : \"1\", \"_index\" : \"test\"} }"+System.lineSeparator()+ "{ \"doc\" : {\"field\" : \"valuex\"} }"+System.lineSeparator()+ - "{ \"delete\" : { \"_index\" : \"test\", \"_type\" : \"type1\", \"_id\" : \"1\" } }"+System.lineSeparator()+ - "{ \"create\" : { \"_index\" : \"test\", \"_type\" : \"type1\", \"_id\" : \"1\" } }"+System.lineSeparator()+ + "{ \"delete\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ + "{ \"create\" : { \"_index\" : \"test\", \"_id\" : \"1\" } }"+System.lineSeparator()+ "{ \"field1\" : \"value3x\" }"+System.lineSeparator(); HttpResponse response = rh.executePostRequest("_bulk", bulkBody, encodeBasicHeader("worf", "worf")); @@ -522,10 +523,10 @@ public void testUpdateSettings() throws Exception { String json = "{"+ "\"persistent\" : {"+ - "\"discovery.zen.minimum_master_nodes\" : 1"+ + "\"indices.recovery.*\" : null"+ "},"+ "\"transient\" : {"+ - "\"discovery.zen.minimum_master_nodes\" : 1"+ + "\"indices.recovery.*\" : null"+ "}"+ "}"; @@ -534,8 +535,8 @@ public void testUpdateSettings() throws Exception { System.out.println(TestAuditlogImpl.sb.toString()); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("AUTHENTICATED")); Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("cluster:admin/settings/update")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("discovery.zen.minimum_master_nodes")); - //may vary because we log may hit master directly or not + Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("indices.recovery.*")); + //may vary because we log may hit cluster manager directly or not Assert.assertTrue(TestAuditlogImpl.messages.size() > 1); Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages)); } @@ -813,6 +814,10 @@ public void testIndexRequests() throws Exception { Assert.assertTrue(auditlogs.contains("\"audit_transport_request_type\" : \"DeleteIndexRequest\",")); } + private String messageRestRequestMethod(AuditMessage msg) { + return msg.getAsMap().get("audit_rest_request_method").toString(); + } + @Test public void testRestMethod() throws Exception { final Settings settings = Settings.builder() @@ -823,66 +828,70 @@ public void testRestMethod() throws Exception { .build(); setup(settings); final Header adminHeader = encodeBasicHeader("admin", "admin"); + List messages; // test GET - TestAuditlogImpl.clear(); - rh.executeGetRequest("test", adminHeader); - Assert.assertEquals(1, TestAuditlogImpl.messages.size()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\"audit_rest_request_method\" : \"GET\"")); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + rh.executeGetRequest("test", adminHeader); + }, 1); + Assert.assertEquals(GET, messages.get(0).getRequestMethod()); // test PUT - TestAuditlogImpl.clear(); - rh.executePutRequest("test/_doc/0", "{}", adminHeader); - Assert.assertEquals(1, TestAuditlogImpl.messages.size()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\"audit_rest_request_method\" : \"PUT\"")); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + rh.executePutRequest("test/_doc/0", "{}", adminHeader); + }, 1); + Assert.assertEquals(PUT, messages.get(0).getRequestMethod()); // test DELETE - TestAuditlogImpl.clear(); - rh.executeDeleteRequest("test", adminHeader); - Assert.assertEquals(1, TestAuditlogImpl.messages.size()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\"audit_rest_request_method\" : \"DELETE\"")); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + rh.executeDeleteRequest("test", adminHeader); + }, 1); + Assert.assertEquals(DELETE, messages.get(0).getRequestMethod()); // test POST - TestAuditlogImpl.clear(); - rh.executePostRequest("test/_doc", "{}", adminHeader); - Assert.assertEquals(1, TestAuditlogImpl.messages.size()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\"audit_rest_request_method\" : \"POST\"")); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + rh.executePostRequest("test/_doc", "{}", adminHeader); + }, 1); + Assert.assertEquals(POST, messages.get(0).getRequestMethod()); // test PATCH - TestAuditlogImpl.clear(); - rh.executePatchRequest("/_opendistro/_security/api/audit", "[]"); - Assert.assertEquals(1, TestAuditlogImpl.messages.size()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\"audit_rest_request_method\" : \"PATCH\"")); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + rh.executePatchRequest("/_opendistro/_security/api/audit", "[]"); + }, 1); + Assert.assertEquals(PATCH, messages.get(0).getRequestMethod()); // test MISSING_PRIVILEGES // admin does not have REST role here - TestAuditlogImpl.clear(); - rh.executePatchRequest("/_opendistro/_security/api/audit", "[]", adminHeader); - Assert.assertEquals(2, TestAuditlogImpl.messages.size()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("MISSING_PRIVILEGES")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("AUTHENTICATED")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\"audit_rest_request_method\" : \"PATCH\"")); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + rh.executePatchRequest("/_opendistro/_security/api/audit", "[]", adminHeader); + }, 2); + // The intital request is authenicated + Assert.assertEquals(PATCH, messages.get(0).getRequestMethod()); + Assert.assertEquals(AuditCategory.AUTHENTICATED, messages.get(0).getCategory()); + // The secondary request does not have permissions + Assert.assertEquals(PATCH, messages.get(1).getRequestMethod()); + Assert.assertEquals(AuditCategory.MISSING_PRIVILEGES, messages.get(1).getCategory()); // test AUTHENTICATED - TestAuditlogImpl.clear(); - rh.executeGetRequest("test", adminHeader); - Assert.assertEquals(1, TestAuditlogImpl.messages.size()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("AUTHENTICATED")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\"audit_rest_request_method\" : \"GET\"")); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + rh.executeGetRequest("test", adminHeader); + }, 1); + Assert.assertEquals(AuditCategory.AUTHENTICATED, messages.get(0).getCategory()); + Assert.assertEquals(GET, messages.get(0).getRequestMethod()); // test FAILED_LOGIN - TestAuditlogImpl.clear(); - rh.executeGetRequest("test", encodeBasicHeader("random", "random")); - Assert.assertEquals(1, TestAuditlogImpl.messages.size()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("FAILED_LOGIN")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\"audit_rest_request_method\" : \"GET\"")); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + rh.executeGetRequest("test", encodeBasicHeader("random", "random")); + }, 1); + Assert.assertEquals(AuditCategory.FAILED_LOGIN, messages.get(0).getCategory()); + Assert.assertEquals(GET, messages.get(0).getRequestMethod()); // test BAD_HEADERS - TestAuditlogImpl.clear(); - rh.executeGetRequest("test", new BasicHeader("_opendistro_security_user", "xxx")); - Assert.assertEquals(1, TestAuditlogImpl.messages.size()); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("BAD_HEADERS")); - Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\"audit_rest_request_method\" : \"GET\"")); + messages = TestAuditlogImpl.doThenWaitForMessages(() -> { + rh.executeGetRequest("test", new BasicHeader("_opendistro_security_user", "xxx")); + }, 1); + Assert.assertEquals(AuditCategory.BAD_HEADERS, messages.get(0).getCategory()); + Assert.assertEquals(GET, messages.get(0).getRequestMethod()); } @Test diff --git a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java index ecafbfa469..b91ddee9fe 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java @@ -1,47 +1,128 @@ /* - * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. */ package org.opensearch.security.auditlog.integration; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; import org.opensearch.common.settings.Settings; - import org.opensearch.security.auditlog.impl.AuditMessage; import org.opensearch.security.auditlog.sink.AuditLogSink; public class TestAuditlogImpl extends AuditLogSink { + /** Use the results of `doThenWaitForMessages(...)` instead */ + @Deprecated public static List messages = new ArrayList(100); + /** Check messages indvidually instead of searching this string */ + @Deprecated public static StringBuffer sb = new StringBuffer(); + private static final AtomicReference countDownRef = new AtomicReference<>(); + private static final AtomicReference> messagesRef = new AtomicReference<>(); public TestAuditlogImpl(String name, Settings settings, String settingsPrefix, AuditLogSink fallbackSink) { super(name, settings, null, fallbackSink); } - - public synchronized boolean doStore(AuditMessage msg) { + public synchronized boolean doStore(AuditMessage msg) { + if (messagesRef.get() == null || countDownRef.get() == null) { + // Ignore any messages that are sent before TestAuditlogImpl is waiting. + return true; + } sb.append(msg.toPrettyString()+System.lineSeparator()); - messages.add(msg); + messagesRef.get().add(msg); + countDownRef.get().countDown(); return true; } + /** Unneeded after switching to `doThenWaitForMessages(...)` as data is automatically flushed */ + @Deprecated public static synchronized void clear() { - sb.setLength(0); - messages.clear(); + doThenWaitForMessages(() -> {}, 0); + } + + /** + * Perform an action and then wait until the expected number of messages have been found. + */ + public static List doThenWaitForMessages(final Runnable action, final int expectedCount) { + final List missedMessages = new ArrayList<>(); + final List messages = new ArrayList<>(); + final CountDownLatch latch = resetAuditStorage(expectedCount, messages); + + try { + action.run(); + final int maxSecondsToWaitForMessages = 1; + boolean foundAll = false; + foundAll = latch.await(maxSecondsToWaitForMessages, TimeUnit.SECONDS); + // After the wait has prevent any new messages from being recieved + resetAuditStorage(0, missedMessages); + if (!foundAll || messages.size() != expectedCount) { + throw new MessagesNotFoundException(expectedCount, messages); + } + } catch (final InterruptedException e) { + throw new RuntimeException("Unexpected exception", e); + } + + // Do not check for missed messages if no messages were expected + if (expectedCount != 0) { + try { + Thread.sleep(100); + if (missedMessages.size() != 0) { + final String missedMessagesErrorMessage = new StringBuilder() + .append("Audit messages were missed! ") + .append("Found " + (missedMessages.size()) + " messages.") + .append("Messages found during this time: \n\n") + .append(missedMessages.stream() + .map(AuditMessage::toString) + .collect(Collectors.joining("\n"))) + .toString(); + + throw new RuntimeException(missedMessagesErrorMessage); + } + } catch (final Exception e) { + throw new RuntimeException("Unexpected exception", e); + } + } + + // Next usage of this class might be using raw stringbuilder / list so reset before that test might run + resetAuditStorage(0, new ArrayList<>()); + return new ArrayList<>(messages); + } + + /** + * Resets all of the mechanics for fresh messages to be captured + * + * @param expectedMessageCount The number of messages before the latch is signalled, indicating all messages have been recieved + * @param message Where messages will be stored after being recieved + */ + private static CountDownLatch resetAuditStorage(int expectedMessageCount, List messages) { + final CountDownLatch latch = new CountDownLatch(expectedMessageCount); + countDownRef.set(latch); + messagesRef.set(messages); + + TestAuditlogImpl.sb = new StringBuffer(); + TestAuditlogImpl.messages = messages; + return latch; + } + + /** + * Perform an action and then wait until a single message has been found. + */ + public static AuditMessage doThenWaitForMessage(final Runnable action) { + return doThenWaitForMessages(action, 1).get(0); } @Override @@ -49,5 +130,38 @@ public boolean isHandlingBackpressure() { return true; } + public static class MessagesNotFoundException extends RuntimeException { + private final int expectedCount; + private final int missingCount; + private final List foundMessages; + public MessagesNotFoundException(final int expectedCount, List foundMessages) { + super(MessagesNotFoundException.createDetailMessage(expectedCount, foundMessages)); + this.expectedCount = expectedCount; + this.missingCount = expectedCount - foundMessages.size(); + this.foundMessages = foundMessages; + } + public int getExpectedCount() { + return expectedCount; + } + + public int getMissingCount() { + return missingCount; + } + + public List getFoundMessages() { + return foundMessages; + } + + private static String createDetailMessage(final int expectedCount, final List foundMessages) { + return new StringBuilder() + .append("Did not receive all " + expectedCount + " audit messages after a short wait. ") + .append("Missing " + (expectedCount - foundMessages.size()) + " messages.") + .append("Messages found during this time: \n\n") + .append(foundMessages.stream() + .map(AuditMessage::toString) + .collect(Collectors.joining("\n"))) + .toString(); + } + } } diff --git a/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java b/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java index 4f71ca577c..f59420fee4 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java @@ -15,8 +15,11 @@ package org.opensearch.security.auditlog.sink; +import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.InputStream; +import java.net.ServerSocket; +import java.nio.charset.StandardCharsets; import java.security.KeyStore; import javax.net.ssl.KeyManagerFactory; @@ -59,11 +62,13 @@ public void testTlsConfigurationNoFallback() throws Exception { TestHttpHandler handler = new TestHttpHandler(); - server = ServerBootstrap.bootstrap().setListenerPort(8083).setServerInfo("Test/1.1").setSslContext(createSSLContext()).registerHandler("*", handler).create(); + int port = findFreePort(); + server = ServerBootstrap.bootstrap().setListenerPort(port).setServerInfo("Test/1.1").setSslContext(createSSLContext()).registerHandler("*", handler).create(); server.start(); - Builder builder = Settings.builder().loadFromPath(FileHelper.getAbsoluteFilePathFromClassPath("auditlog/endpoints/sink/configuration_tls.yml")); + final byte[] configAsBytes = getConfigurationAsString(port).getBytes(StandardCharsets.UTF_8); + Builder builder = Settings.builder().loadFromStream("configuration_tls.yml", new ByteArrayInputStream(configAsBytes), false); builder.put("path.home", "/"); // replace some values with absolute paths for unit tests @@ -141,4 +146,58 @@ private void assertStringContainsAllKeysAndValues(String in) { Assert.assertTrue(in, in.contains("8.8.8.8")); //Assert.assertTrue(in, in.contains("CN=kirk,OU=client,O=client,L=test,C=DE")); } + + private int findFreePort() { + try (ServerSocket serverSocket = new ServerSocket(0)) { + return serverSocket.getLocalPort(); + } catch (Exception e) { + throw new RuntimeException("Failed to find free port", e); + } + } + + private String getConfigurationAsString(final int port) { + return "plugins.security.ssl.transport.enabled: true\n" + +"plugins.security.ssl.transport.keystore_filepath: \"transport.keystore_filepath\"\n" + +"plugins.security.ssl.transport.truststore_filepath: \"transport.truststore_filepath\"\n" + +"plugins.security.ssl.transport.enforce_hostname_verification: true\n" + +"plugins.security.ssl.transport.resolve_hostname: true\n" + +"plugins.security.ssl.transport.enable_openssl_if_available: true\n" + +"plugins.security.ssl.http.enabled: true\n" + +"plugins.security.ssl.http.keystore_filepath: \"http.keystore_filepath\"\n" + +"plugins.security.ssl.http.truststore_filepath: \"http.truststore_filepath\"\n" + +"plugins.security.ssl.http.enable_openssl_if_available: true\n" + +"plugins.security.ssl.http.clientauth_mode: OPTIONAL\n" + +"\n" + +"plugins.security:\n" + +" audit:\n" + +" type: webhook\n" + +" config:\n" + +" webhook:\n" + +" url: https://localhost:" + port + "\n" + +" format: JSON\n" + +" ssl:\n" + +" verify: true\n" + +" pemtrustedcas_filepath: dyn\n" + +" endpoints:\n" + +" endpoint1:\n" + +" type: webhook\n" + +" config:\n" + +" webhook:\n" + +" url: https://localhost:" + port + "\n" + +" format: JSON\n" + +" ssl:\n" + +" verify: true\n" + +" pemtrustedcas_filepath: dyn\n" + +" endpoint2:\n" + +" type: webhook\n" + +" config:\n" + +" webhook:\n" + +" url: https://localhost:" + port + "\n" + +" format: JSON\n" + +" ssl:\n" + +" verify: true\n" + +" pemtrustedcas_content: dyn\n" + +" fallback:\n" + +" type: org.opensearch.security.auditlog.helper.LoggingSink"; + } } diff --git a/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java b/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java index e28f818397..2efe9ad40c 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java @@ -16,7 +16,9 @@ package org.opensearch.security.auditlog.sink; import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; +import java.net.ServerSocket; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.security.KeyStore; @@ -222,15 +224,16 @@ public void noServerRunningHttpTest() throws Exception { public void postGetHttpTest() throws Exception { TestHttpHandler handler = new TestHttpHandler(); + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(8080) + .setListenerPort(port) .setServerInfo("Test/1.1") .registerHandler("*", handler) .create(); server.start(); - String url = "http://localhost:8080/endpoint"; + String url = "http://localhost:" + port + "/endpoint"; // SLACK Settings settings = Settings.builder() @@ -327,15 +330,16 @@ public void httpsTestWithoutTLSServer() throws Exception { TestHttpHandler handler = new TestHttpHandler(); + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(8081) + .setListenerPort(port) .setServerInfo("Test/1.1") .registerHandler("*", handler) .create(); server.start(); - String url = "https://localhost:8081/endpoint"; + String url = "https://localhost:" + port + "/endpoint"; Settings settings = Settings.builder() .put("plugins.security.audit.config.webhook.url", url) @@ -363,9 +367,9 @@ public void httpsTestWithoutTLSServer() throws Exception { public void httpsTest() throws Exception { TestHttpHandler handler = new TestHttpHandler(); - + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(8090) + .setListenerPort(port) .setServerInfo("Test/1.1") .setSslContext(createSSLContext()) .registerHandler("*", handler) @@ -374,7 +378,7 @@ public void httpsTest() throws Exception { server.start(); AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - String url = "https://localhost:8090/endpoint"; + String url = "https://localhost:" + port + "/endpoint"; // try with ssl verification on, no trust ca, must fail Settings settings = Settings.builder() @@ -445,8 +449,8 @@ public void httpsTest() throws Exception { @Test public void httpsTestPemDefault() throws Exception { - final int port = 8088; - TestHttpHandler handler = new TestHttpHandler(); + final int port = findFreePort(); + TestHttpHandler handler = new TestHttpHandler(); server = ServerBootstrap.bootstrap() .setListenerPort(port) @@ -561,9 +565,10 @@ public void httpsTestPemDefault() throws Exception { public void httpsTestPemEndpoint() throws Exception { TestHttpHandler handler = new TestHttpHandler(); + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(8091) + .setListenerPort(port) .setServerInfo("Test/1.1") .setSslContext(createSSLContext()) .registerHandler("*", handler) @@ -573,7 +578,7 @@ public void httpsTestPemEndpoint() throws Exception { AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); - String url = "https://localhost:8091/endpoint"; + String url = "https://localhost:" + port + "/endpoint"; // test default with filepath handler.reset(); @@ -658,9 +663,10 @@ public void httpsTestPemEndpoint() throws Exception { public void httpsTestPemContentEndpoint() throws Exception { TestHttpHandler handler = new TestHttpHandler(); + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(8086) + .setListenerPort(port) .setServerInfo("Test/1.1") .setSslContext(createSSLContext()) .registerHandler("*", handler) @@ -670,7 +676,7 @@ public void httpsTestPemContentEndpoint() throws Exception { AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); - String url = "https://localhost:8086/endpoint"; + String url = "https://localhost:" + port + "/endpoint"; // test with filecontent handler.reset(); @@ -731,4 +737,12 @@ private void assertStringContainsAllKeysAndValues(String in) { Assert.assertTrue(in, in.contains("8.8.8.8")); //Assert.assertTrue(in, in.contains("CN=kirk,OU=client,O=client,L=test,C=DE")); } + + private int findFreePort() { + try (ServerSocket serverSocket = new ServerSocket(0)) { + return serverSocket.getLocalPort(); + } catch (IOException e) { + throw new RuntimeException("Failed to find free port", e); + } + } } diff --git a/src/test/java/org/opensearch/security/auth/limiting/HeapBasedRateTrackerTest.java b/src/test/java/org/opensearch/security/auth/limiting/HeapBasedRateTrackerTest.java index 3637211eec..ae0fa500d2 100644 --- a/src/test/java/org/opensearch/security/auth/limiting/HeapBasedRateTrackerTest.java +++ b/src/test/java/org/opensearch/security/auth/limiting/HeapBasedRateTrackerTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import org.junit.Ignore; import org.junit.Test; import org.opensearch.security.util.ratetracking.HeapBasedRateTracker; @@ -39,6 +40,7 @@ public void simpleTest() throws Exception { } @Test + @Ignore // https://github.com/opensearch-project/security/issues/2193 public void expiryTest() throws Exception { HeapBasedRateTracker tracker = new HeapBasedRateTracker<>(100, 5, 100_000); @@ -78,6 +80,7 @@ public void expiryTest() throws Exception { } @Test + @Ignore // https://github.com/opensearch-project/security/issues/2193 public void maxTwoTriesTest() throws Exception { HeapBasedRateTracker tracker = new HeapBasedRateTracker<>(100, 2, 100_000); 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 3d4f290652..cf1cb18eb2 100644 --- a/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java +++ b/src/test/java/org/opensearch/security/dlic/dlsfls/DlsTest.java @@ -375,7 +375,7 @@ public void testDlsWithMinDocCountZeroAggregations() throws Exception { // Significant Text Aggregation is not impacted. // Non-admin user with setting "min_doc_count=0". Expected to only have access to buckets for dept_manager". - String query3 = "{\"aggregations\":{\"significant_termX\":{\"significant_terms\":{\"field\":\"termX.keyword\",\"min_doc_count\":0}}}}"; + String query3 = "{\"size\":100,\"aggregations\":{\"significant_termX\":{\"significant_terms\":{\"field\":\"termX.keyword\",\"min_doc_count\":0}}}}"; HttpResponse response5 = rh.executePostRequest("logs*/_search", query3, encodeBasicHeader("dept_manager", "password")); Assert.assertEquals(HttpStatus.SC_OK, response5.getStatusCode()); @@ -386,7 +386,7 @@ public void testDlsWithMinDocCountZeroAggregations() throws Exception { Assert.assertFalse(response5.getBody(), response5.getBody().contains("\"termX\":\"E\"")); // Non-admin user without setting "min_doc_count". Expected to only have access to buckets for dept_manager". - String query4 = "{\"aggregations\":{\"significant_termX\":{\"significant_terms\":{\"field\":\"termX.keyword\"}}}}"; + String query4 = "{\"size\":100,\"aggregations\":{\"significant_termX\":{\"significant_terms\":{\"field\":\"termX.keyword\"}}}}"; HttpResponse response6 = rh.executePostRequest("logs*/_search", query4, encodeBasicHeader("dept_manager", "password")); @@ -419,7 +419,7 @@ public void testDlsWithMinDocCountZeroAggregations() throws Exception { // Histogram Aggregation is not impacted. // Non-admin user with setting "min_doc_count=0". Expected to only have access to buckets for dept_manager". - String query5 = "{\"aggs\":{\"amount\":{\"histogram\":{\"field\":\"amount\",\"interval\":1,\"min_doc_count\":0}}}}"; + String query5 = "{\"size\":100,\"aggs\":{\"amount\":{\"histogram\":{\"field\":\"amount\",\"interval\":1,\"min_doc_count\":0}}}}"; HttpResponse response9 = rh.executePostRequest("logs*/_search", query5, encodeBasicHeader("dept_manager", "password")); @@ -431,7 +431,7 @@ public void testDlsWithMinDocCountZeroAggregations() throws Exception { Assert.assertFalse(response9.getBody(), response9.getBody().contains("\"termX\":\"E\"")); // Non-admin user without setting "min_doc_count". Expected to only have access to buckets for dept_manager". - String query6 = "{\"aggs\":{\"amount\":{\"histogram\":{\"field\":\"amount\",\"interval\":1}}}}"; + String query6 = "{\"size\":100,\"aggs\":{\"amount\":{\"histogram\":{\"field\":\"amount\",\"interval\":1}}}}"; HttpResponse response10 = rh.executePostRequest("logs*/_search", query6, encodeBasicHeader("dept_manager", "password")); @@ -465,7 +465,7 @@ public void testDlsWithMinDocCountZeroAggregations() throws Exception { // Date Histogram Aggregation is not impacted. // Non-admin user with setting "min_doc_count=0". Expected to only have access to buckets for dept_manager". - String query7 = "{\"aggs\":{\"timestamp\":{\"date_histogram\":{\"field\":\"timestamp\",\"calendar_interval\":\"month\",\"min_doc_count\":0}}}}"; + String query7 = "{\"size\":100,\"aggs\":{\"timestamp\":{\"date_histogram\":{\"field\":\"timestamp\",\"calendar_interval\":\"month\",\"min_doc_count\":0}}}}"; HttpResponse response13 = rh.executePostRequest("logs*/_search", query7, encodeBasicHeader("dept_manager", "password")); @@ -477,7 +477,7 @@ public void testDlsWithMinDocCountZeroAggregations() throws Exception { Assert.assertFalse(response13.getBody(), response13.getBody().contains("\"termX\":\"E\"")); // Non-admin user without setting "min_doc_count". Expected to only have access to buckets for dept_manager". - String query8 = "{\"aggs\":{\"timestamp\":{\"date_histogram\":{\"field\":\"timestamp\",\"calendar_interval\":\"month\"}}}}"; + String query8 = "{\"size\":100,\"aggs\":{\"timestamp\":{\"date_histogram\":{\"field\":\"timestamp\",\"calendar_interval\":\"month\"}}}}"; HttpResponse response14 = rh.executePostRequest("logs*/_search", query8, encodeBasicHeader("dept_manager", "password")); diff --git a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java index 3474901274..0b74dfb5d2 100644 --- a/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/OpenSSLTest.java @@ -65,23 +65,10 @@ public static void restoreNettyDefaultAllocator() { @Before public void setup() { + Assume.assumeFalse(PlatformDependent.isWindows()); allowOpenSSL = true; } - @Test - public void testEnsureOpenSSLAvailability() { - //Assert.assertTrue("OpenSSL not available: "+String.valueOf(OpenSsl.unavailabilityCause()), OpenSsl.isAvailable()); - - final String openSSLOptional = System.getenv("OPENDISTRO_SECURITY_TEST_OPENSSL_OPT"); - System.out.println("OPENDISTRO_SECURITY_TEST_OPENSSL_OPT "+openSSLOptional); - if(!Boolean.parseBoolean(openSSLOptional)) { - System.out.println("OpenSSL must be available"); - Assert.assertTrue("OpenSSL not available: "+String.valueOf(OpenSsl.unavailabilityCause()), OpenSsl.isAvailable()); - } else { - System.out.println("OpenSSL can be available"); - } - } - @Override @Test public void testHttps() throws Exception { diff --git a/src/test/java/org/opensearch/security/ssl/SSLTest.java b/src/test/java/org/opensearch/security/ssl/SSLTest.java index e19b68bd5f..eb3560cb2f 100644 --- a/src/test/java/org/opensearch/security/ssl/SSLTest.java +++ b/src/test/java/org/opensearch/security/ssl/SSLTest.java @@ -61,6 +61,7 @@ import org.opensearch.transport.Netty4Plugin; import org.junit.Assert; import org.junit.Assume; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -522,6 +523,7 @@ public void testTransportClientSSL() throws Exception { } @Test + @Ignore // Has an external dependency that is flaky, not used in main public void testTransportClientSSLExternalContext() throws Exception { final Settings settings = Settings.builder().put("plugins.security.ssl.transport.enabled", true) diff --git a/src/test/java/org/opensearch/security/support/SecurityUtilsTest.java b/src/test/java/org/opensearch/security/support/SecurityUtilsTest.java new file mode 100644 index 0000000000..920bc4596d --- /dev/null +++ b/src/test/java/org/opensearch/security/support/SecurityUtilsTest.java @@ -0,0 +1,79 @@ +/* + * 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.support; + +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.security.support.SecurityUtils.ENVBASE64_PATTERN; +import static org.opensearch.security.support.SecurityUtils.ENVBC_PATTERN; +import static org.opensearch.security.support.SecurityUtils.ENV_PATTERN; + +public class SecurityUtilsTest { + + private final Collection interestingEnvKeyNames = Arrays.asList( + "=ExitCode", + "=C:", + "ProgramFiles(x86)", + "INPUT_GRADLE-HOME-CACHE-CLEANUP", + "MYENV", + "MYENV:", + "MYENV::", + "JAVA_HOME_14.0.2_x64" + ); + private final Collection namesFromThisRuntimeEnvironment = System.getenv().keySet(); + + @Test + public void checkInterestingNamesForEnvPattern() { + checkKeysWithPredicate(interestingEnvKeyNames, "env", asMatchPredicate(ENV_PATTERN)); + } + + @Test + public void checkRuntimeKeyNamesForEnvPattern() { + checkKeysWithPredicate(namesFromThisRuntimeEnvironment, "env", asMatchPredicate(ENV_PATTERN)); + } + + @Test + public void checkInterestingNamesForEnvbcPattern() { + checkKeysWithPredicate(interestingEnvKeyNames, "envbc", asMatchPredicate(ENVBC_PATTERN)); + } + + @Test + public void checkInterestingNamesForEnvBase64Pattern() { + checkKeysWithPredicate(interestingEnvKeyNames, "envbase64", asMatchPredicate(ENVBASE64_PATTERN)); + } + + private Predicate asMatchPredicate(final Pattern p) { + return (String s) -> p.matcher(s).matches(); + } + + private void checkKeysWithPredicate(Collection keys, String predicateName, Predicate predicate) { + keys.forEach(envKeyName -> { + final String prefixWithKeyName = "${" + predicateName + "." + envKeyName; + + final String baseKeyName = prefixWithKeyName + "}"; + assertThat("Testing " + envKeyName + ", " + baseKeyName, + predicate.test(baseKeyName), + equalTo(true)); + + final String baseKeyNameWithDefault = prefixWithKeyName + ":-tTt}"; + assertThat("Testing " + envKeyName + " with defaultValue, " + baseKeyNameWithDefault, + predicate.test(baseKeyNameWithDefault), + equalTo(true)); + }); + } +} diff --git a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java index 5b72a6a03a..8108fc0482 100644 --- a/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java +++ b/src/test/java/org/opensearch/security/test/AbstractSecurityUnitTest.java @@ -199,23 +199,7 @@ protected void initialize(ClusterInfo info, Settings initTransportClientSettings Assert.assertEquals(info.numNodes, cur.getNodes().size()); SearchResponse sr = tc.search(new SearchRequest(".opendistro_security")).actionGet(); - //Assert.assertEquals(5L, sr.getHits().getTotalHits()); - sr = tc.search(new SearchRequest(".opendistro_security")).actionGet(); - //Assert.assertEquals(5L, sr.getHits().getTotalHits()); - - String type=securityConfig.getType(); - - Assert.assertTrue(tc.get(new GetRequest(".opendistro_security", type, "config")).actionGet().isExists()); - Assert.assertTrue(tc.get(new GetRequest(".opendistro_security", type,"internalusers")).actionGet().isExists()); - Assert.assertTrue(tc.get(new GetRequest(".opendistro_security", type,"roles")).actionGet().isExists()); - Assert.assertTrue(tc.get(new GetRequest(".opendistro_security", type,"rolesmapping")).actionGet().isExists()); - Assert.assertTrue(tc.get(new GetRequest(".opendistro_security", type,"actiongroups")).actionGet().isExists()); - Assert.assertFalse(tc.get(new GetRequest(".opendistro_security", type,"rolesmapping_xcvdnghtu165759i99465")).actionGet().isExists()); - Assert.assertTrue(tc.get(new GetRequest(".opendistro_security", type,"config")).actionGet().isExists()); - if (indexRequests.stream().anyMatch(i -> CType.NODESDN.toLCString().equals(i.id()))) { - Assert.assertTrue(tc.get(new GetRequest(".opendistro_security", type,"nodesdn")).actionGet().isExists()); - } } } diff --git a/src/test/java/org/opensearch/security/test/helper/rest/RestHelper.java b/src/test/java/org/opensearch/security/test/helper/rest/RestHelper.java index c35a7a01be..32e8d993f8 100644 --- a/src/test/java/org/opensearch/security/test/helper/rest/RestHelper.java +++ b/src/test/java/org/opensearch/security/test/helper/rest/RestHelper.java @@ -32,6 +32,7 @@ import java.io.FileInputStream; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.util.Arrays; @@ -143,48 +144,48 @@ public HttpResponse[] executeMultipleAsyncPutRequest(final int numOfRequests, fi .toArray(s -> new HttpResponse[s]); } - public HttpResponse executeGetRequest(final String request, Header... header) throws Exception { + public HttpResponse executeGetRequest(final String request, Header... header) { return executeRequest(new HttpGet(getHttpServerUri() + "/" + request), header); } - public HttpResponse executeHeadRequest(final String request, Header... header) throws Exception { + public HttpResponse executeHeadRequest(final String request, Header... header) { return executeRequest(new HttpHead(getHttpServerUri() + "/" + request), header); } - public HttpResponse executeOptionsRequest(final String request) throws Exception { + public HttpResponse executeOptionsRequest(final String request) { return executeRequest(new HttpOptions(getHttpServerUri() + "/" + request)); } - public HttpResponse executePutRequest(final String request, String body, Header... header) throws Exception { + public HttpResponse executePutRequest(final String request, String body, Header... header) { HttpPut uriRequest = new HttpPut(getHttpServerUri() + "/" + request); if (body != null && !body.isEmpty()) { - uriRequest.setEntity(new StringEntity(body)); + uriRequest.setEntity(createStringEntity(body)); } return executeRequest(uriRequest, header); } - public HttpResponse executeDeleteRequest(final String request, Header... header) throws Exception { + public HttpResponse executeDeleteRequest(final String request, Header... header) { return executeRequest(new HttpDelete(getHttpServerUri() + "/" + request), header); } - public HttpResponse executePostRequest(final String request, String body, Header... header) throws Exception { + public HttpResponse executePostRequest(final String request, String body, Header... header) { HttpPost uriRequest = new HttpPost(getHttpServerUri() + "/" + request); if (body != null && !body.isEmpty()) { - uriRequest.setEntity(new StringEntity(body)); + uriRequest.setEntity(createStringEntity(body)); } return executeRequest(uriRequest, header); } - public HttpResponse executePatchRequest(final String request, String body, Header... header) throws Exception { + public HttpResponse executePatchRequest(final String request, String body, Header... header) { HttpPatch uriRequest = new HttpPatch(getHttpServerUri() + "/" + request); if (body != null && !body.isEmpty()) { - uriRequest.setEntity(new StringEntity(body)); + uriRequest.setEntity(createStringEntity(body)); } return executeRequest(uriRequest, header); } - public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... header) throws Exception { + public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... header) { CloseableHttpClient httpClient = null; try { @@ -204,13 +205,27 @@ public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... header) HttpResponse res = new HttpResponse(httpClient.execute(uriRequest)); log.debug(res.getBody()); return res; + } catch (final Exception e) { + throw new RuntimeException(e); } finally { if (httpClient != null) { - httpClient.close(); + try { + httpClient.close(); + } catch (final Exception e) { + throw new RuntimeException(e); + } } } } + + private StringEntity createStringEntity(String body) { + try { + return new StringEntity(body); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } protected final String getHttpServerUri() { final String address = "http" + (enableHTTPClientSSL ? "s" : "") + "://" + clusterInfo.httpHost + ":" + clusterInfo.httpPort;